Skip to content

feat(worker): Add metrics for tasks lost during shutdown - #791

Open
evanh wants to merge 1 commit into
mainfrom
evanh/feat/shutdown-loss-metrics
Open

feat(worker): Add metrics for tasks lost during shutdown#791
evanh wants to merge 1 commit into
mainfrom
evanh/feat/shutdown-loss-metrics

Conversation

@evanh

@evanh evanh commented Sep 1, 2026

Copy link
Copy Markdown
Member

Worker shutdown drops tasks in three places and none of them are measurable today:

  • tasks queued in child_tasks are discarded — nothing drains that queue
  • children still running at WORKER_CHILD_JOIN_TIMEOUT_SEC are SIGKILLed without producing a result
  • the final result drain drops a batch on its first RPC error rather than requeueing it

Anything lost this way stays processing on the broker until its deadline lapses, which burns a processing attempt; at_most_once tasks are failed outright rather than retried.

This adds instrumentation only, so the loss can be sized before any behaviour changes.

Metrics

Metric What it shows
taskworker.worker.shutdown.child_tasks_discarded Queued tasks thrown away
taskworker.worker.shutdown.children_killed Children that needed SIGKILL
taskworker.worker.shutdown.results_drained Results the drain reported
taskworker.worker.shutdown.results_dropped Results lost to a failing broker
taskworker.worker.shutdown.result_thread joined vs timeout
taskworker.worker.shutdown.stage_duration Per-stage timing, tagged by stage
taskworker.worker.shutdown.duration Total shutdown time

All tagged with processing_pool. Counters are emitted even at zero so dashboards can tell "nothing lost" from "no data".

Stage durations are emitted as each stage completes rather than once at the end, so a pod that is SIGKILLed part way through still reports how far it got. They also give the numbers needed to size the pod termination grace period.

Behaviour change

One, and it is a no-op in effect: shutdown() now empties child_tasks in order to count it. Nothing consumed that queue once the children were gone, so those tasks were already being discarded when the process exited.

child_tasks_discarded is a lower bound — mp.Queue.get_nowait() also raises Empty while items sit unflushed in the pipe.

Testing

8 new tests in tests/worker/test_worker.py covering each counter, the drop-vs-requeue split between draining and normal operation, stage ordering, and the result-thread outcome. Full client suite passes (tests/test_metrics.py::test_track_memory_usage fails identically with and without this change — a pre-existing RSS-delta flake under full-suite conditions).

🤖 Generated with Claude Code

Shutdown drops tasks in three places and none of them are measurable today:
tasks queued in `child_tasks` are discarded, children still running at the join
timeout are SIGKILLed without producing a result, and the final result drain
drops a batch on its first RPC error rather than requeueing it.

Adds counters for each, a joined/timeout outcome for the result thread, and
per-stage durations emitted as each stage completes so a pod killed part way
through still reports how far it got.

No behaviour change beyond emptying `child_tasks` at the end of shutdown, which
was already discarded when the process exited.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@evanh
evanh requested a review from a team as a code owner September 1, 2026 21:31
Comment thread clients/python/src/taskbroker_client/worker/worker.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant