Summary
An agent sends its start report over the session and its outcome over REST (/complete), so the two can reach the server in either order. _mark_agent_job_started (app/api/agents.py) checks job.status in FINAL_AGENT_JOB_STATUSES on the job as its session loaded it, then writes job.status = "running" as a plain attribute. If the completion (recorded in a worker thread with its own session since #1324) commits after that load, the start report's commit sets the completed job back to running.
On PostgreSQL the window is wide: the report's guarded start_notified_at UPDATE waits for the completion's lock on the job row, so the report has always loaded the job before the completion committed.
Found while fixing #1366, which covers the same race for the linked operation: with that fix the operation keeps its verdict, but the agent job's own status is not covered there.
Steps to reproduce (unit level)
- An agent job in
claimed, started_at and start_notified_at unset.
- Load the job in one session.
- Record the completion through
_record_job_completion (its own session, commits completed).
- Call
_mark_agent_job_started with the job loaded in step 2 and commit.
The job ends running; the linked operation stays completed.
Actual behavior
The agent job is running after a successful completion. A waiter (wait_for_agent_repository_operation_job) keeps polling until its timeout, and admission holds the repository for the job until the stale-job reaper ends it.
Expected behavior
A start report never moves a job out of a final status: the transition to running is a guarded write (WHERE status NOT IN FINAL_AGENT_JOB_STATUSES), like the terminal transition in _claim_terminal_transition.
Borg UI version
main at 196a74a
Additional context
It needs the first start report to reach the server after the REST completion, i.e. a job shorter than the session's delivery lag, so it is rarer than #1366. Not observed in the field so far.
Summary
An agent sends its start report over the session and its outcome over REST (
/complete), so the two can reach the server in either order._mark_agent_job_started(app/api/agents.py) checksjob.status in FINAL_AGENT_JOB_STATUSESon the job as its session loaded it, then writesjob.status = "running"as a plain attribute. If the completion (recorded in a worker thread with its own session since #1324) commits after that load, the start report's commit sets thecompletedjob back torunning.On PostgreSQL the window is wide: the report's guarded
start_notified_atUPDATE waits for the completion's lock on the job row, so the report has always loaded the job before the completion committed.Found while fixing #1366, which covers the same race for the linked operation: with that fix the operation keeps its verdict, but the agent job's own status is not covered there.
Steps to reproduce (unit level)
claimed,started_atandstart_notified_atunset._record_job_completion(its own session, commitscompleted)._mark_agent_job_startedwith the job loaded in step 2 and commit.The job ends
running; the linked operation stayscompleted.Actual behavior
The agent job is
runningafter a successful completion. A waiter (wait_for_agent_repository_operation_job) keeps polling until its timeout, and admission holds the repository for the job until the stale-job reaper ends it.Expected behavior
A start report never moves a job out of a final status: the transition to
runningis a guarded write (WHERE status NOT IN FINAL_AGENT_JOB_STATUSES), like the terminal transition in_claim_terminal_transition.Borg UI version
main at 196a74a
Additional context
It needs the first start report to reach the server after the REST completion, i.e. a job shorter than the session's delivery lag, so it is rarer than #1366. Not observed in the field so far.