fix(m5): scenario reorder 500 — wrong pg_advisory_xact_lock overload
Editing a scenario and saving (with or without changes) returned 500: function pg_advisory_xact_lock(smallint, bigint) does not exist Postgres only ships (int4, int4) and (bigint) variants. The two-arg call passed `m = hash(uuid) & 0xFFFFFFFF` which can reach 2^32-1, so psycopg promoted it to bigint and no overload matched. Switched to the single-arg bigint form. While there, replaced Python's built-in hash() with hashlib.blake2b(...) — the built-in is randomised per process via PYTHONHASHSEED, so gunicorn workers were computing different lock keys for the same scenario and the lock wasn't actually serialising across workers. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,7 @@ The same test_template may legitimately appear multiple times in a scenario
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import uuid
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timezone
|
||||
@@ -217,10 +218,16 @@ def set_scenario_tests(
|
||||
"""
|
||||
with session_scope() as s:
|
||||
# Lock keyed on the scenario UUID — different scenarios don't block
|
||||
# each other. Two-int form: high-32 = constant, low-32 = hash of UUID.
|
||||
# each other. Single bigint form so we don't have to juggle int32
|
||||
# signed ranges. blake2b is used instead of Python's built-in hash()
|
||||
# because the latter is randomised per-process (PYTHONHASHSEED), so
|
||||
# two gunicorn workers would compute different keys for the same
|
||||
# scenario and the lock wouldn't serialise across them.
|
||||
digest = hashlib.blake2b(scenario_id.bytes, digest_size=8).digest()
|
||||
lock_key = int.from_bytes(digest, "big", signed=True)
|
||||
s.execute(
|
||||
text("SELECT pg_advisory_xact_lock(:n, :m)"),
|
||||
{"n": 0x5C3, "m": hash(scenario_id) & 0xFFFFFFFF},
|
||||
text("SELECT pg_advisory_xact_lock(CAST(:key AS bigint))"),
|
||||
{"key": lock_key},
|
||||
)
|
||||
sc = s.get(ScenarioTemplate, scenario_id)
|
||||
if sc is None or sc.deleted_at is not None:
|
||||
|
||||
Reference in New Issue
Block a user