fix(issue-33): subscriber exit codes are named on both sides; wait keeps a spent budget at rc 2; G-8 is deterministic
- job_subscriber.py returns only named codes (RC_COMPLETED 0, RC_ERROR 1, RC_NO_EVENT 2, RC_BROKER_UNAVAILABLE 3, RC_WALL_CLOCK 4). The wrapper mirrors them as SUB_RC_* and compares subscriber codes only by name; a test keeps the mirror equal to the Python constants and forbids numeric comparisons of $sub_rc/$sub_exit and numeric `return` in the subscriber. - `delegate-job wait` passed the subscriber's code straight through, so a spent budget would have reached its callers as 4; it is 2 again (B7 fails without the mapping). - tier1 G-8 gave both clocks 0.5 s and raced; the wall clock now runs out first by construction (idle 30 s) and the test asserts RC_WALL_CLOCK. Other subscriber rc asserts use the names. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9ec0569401
commit
1673818bb5
6 files changed
+77
-37
No files matched your search
@@ -49,11 +49,15 @@ logger = logging.getLogger("delegate_job.job_subscriber")
|
||||
TERMINAL_EVENTS = ("completed", "error")
|
||||
TERMINAL_STATUSES = ("completed", "error", "cancelled")
|
||||
STATE_EVENT = "state"
|
||||
# Exit codes. The wrapper mirrors them as SUB_RC_* (a test keeps the two equal).
|
||||
RC_COMPLETED = 0
|
||||
RC_ERROR = 1
|
||||
# Issue #33: which clock ended the wait. The wrapper maps both to a timeout
|
||||
# (caller rc 2) and labels the close from them: rc 2 -> deadline_timeout
|
||||
# (no event for the idle window, K x N), rc 4 -> wall_clock_timeout (the job
|
||||
# budget, --timeout, ran out while events may still have been arriving).
|
||||
RC_NO_EVENT = 2
|
||||
RC_BROKER_UNAVAILABLE = 3
|
||||
RC_WALL_CLOCK = 4
|
||||
|
||||
|
||||
@@ -350,9 +354,9 @@ def _run_subscriber(args) -> int:
|
||||
jobs = _collect_jobs(args)
|
||||
except FileNotFoundError as exc:
|
||||
logger.error("%s", exc)
|
||||
return 2
|
||||
return RC_NO_EVENT # nothing to watch: reported like silence, as before #33
|
||||
if not jobs:
|
||||
return 2
|
||||
return RC_NO_EVENT
|
||||
|
||||
expected_ids: Set[str] = {j["job_id"] for j in jobs}
|
||||
tokens = {j["job_id"]: j.get("auth_token") for j in jobs}
|
||||
@@ -451,8 +455,8 @@ def _run_subscriber(args) -> int:
|
||||
_check_disk_fallback(pending, args.registry_dir, terminal, dispatch_tokens, attempts)
|
||||
if not pending:
|
||||
if any(state == "error" for state in terminal.values()):
|
||||
return 1
|
||||
return 0
|
||||
return RC_ERROR
|
||||
return RC_COMPLETED
|
||||
|
||||
try:
|
||||
mqtt_common.with_retry(
|
||||
@@ -467,10 +471,10 @@ def _run_subscriber(args) -> int:
|
||||
_check_disk_fallback(pending, args.registry_dir, terminal, dispatch_tokens, attempts)
|
||||
if not pending:
|
||||
if any(state == "error" for state in terminal.values()):
|
||||
return 1
|
||||
return 0
|
||||
return RC_ERROR
|
||||
return RC_COMPLETED
|
||||
logger.error("broker connection failed: %s", exc)
|
||||
return 3
|
||||
return RC_BROKER_UNAVAILABLE
|
||||
|
||||
try:
|
||||
while pending:
|
||||
@@ -552,8 +556,8 @@ def _run_subscriber(args) -> int:
|
||||
|
||||
# All jobs reached a terminal state. error wins over completed.
|
||||
if any(state == "error" for state in terminal.values()):
|
||||
return 1
|
||||
return 0
|
||||
return RC_ERROR
|
||||
return RC_COMPLETED
|
||||
|
||||
|
||||
def main(argv=None) -> int:
|
||||
@@ -590,7 +594,7 @@ def main(argv=None) -> int:
|
||||
return _run_subscriber(args)
|
||||
except Exception as exc:
|
||||
logger.error("subscriber fatal error: %s", exc)
|
||||
return 3
|
||||
return RC_BROKER_UNAVAILABLE
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in new issue
Block a user