Skip to content

Fire job callbacks once a job reaches a terminal state - #1059

Draft
epompeii wants to merge 1 commit into
u/ep/callback/attachfrom
u/ep/callback/fire
Draft

epompeii wants to merge 1 commit into
u/ep/callback/attachfrom
u/ep/callback/fire

Conversation

@epompeii

@epompeii epompeii commented Sep 26, 2026 •

Copy link
Copy Markdown
Member

A callback is sent once, after its job reaches a terminal state, however many paths finish the job.

  • The fire. Every write that makes a job processed, failed, or canceled (nine sites across the runner channel, the heartbeat timeout, and the reprocessing of completed jobs) fires the job's callback once its update commits, which hands it to a delivery task. A job has at most one delivery task at a time, tracked in memory, and the task sends only a pending callback of a terminal job, so a second fire, a skipped callback, or no callback sends nothing. completed never fires.
  • The delivery. The task opens the sealed request, which validates again, and renders its body with the job's values. It builds the job's report, exactly as the report endpoint returns it, only for a callback without a body or one whose body sends {{ report }}, on a read connection so a large report never holds the writer, and at most two at a time, so a burst of callbacks cannot take every worker thread. Without a free read connection, it tries again each pool timeout. A request that does not open or does not validate, or whose report does not build, is never sent and fails.
  • The attempts. Up to three per callback, 4 s and 16 s apart. A timeout, a connection error, 408, 429, and any 5xx retry; any other status and a blocked address are final. Each attempt, and the settle of the one that ends the delivery, is one update filtered on pending, and a delivery whose write finds nothing pending stops. The count lives on the row, so a delivery started again continues from it.
  • Logs. One line per attempt with the job, organization, project, destination host, attempt number, outcome, status, and duration, plus the refused address of a block and the cause chain of a connection error, whose URL is stripped. Never a header value, the URL beyond its host, or the body.
  • Ownership. The API context owns every delivery task. Shutdown cuts a delivery short, including its wait for a report build or a read connection, without a write, so its callback stays pending, and waits for every task once the rate limiting state is saved.
  • Startup. The server builds the callback client at boot: if the build fails, it logs the cause once and starts anyway, and every attempt is then a connection error. Job recovery, which runs once the server accepts connections, ends by firing every pending callback on a terminal job, even when its query for in-flight jobs fails.

Nothing creates a callback yet. The tests inject a scripted or recording sender; no test and no configuration reaches the network.

Needed for #1046

@epompeii
epompeii marked this pull request as ready for review September 26, 2026 00:32
@github-actions

github-actions Bot commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

🐰 Bencher Report

ProjectBencher
Branchu/ep/callback/fire
Testbedintel-v1
Click to view all benchmark results
BenchmarkLatencyBenchmark Result
microseconds (µs)
(Result Δ%)
Upper Boundary
microseconds (µs)
(Limit %)
Adapter::Json📈 view plot
🚷 view threshold
5.18 µs
(+5.25%)Baseline: 4.92 µs
5.99 µs
(86.43%)
Adapter::Magic (JSON)📈 view plot
🚷 view threshold
4.97 µs
(+4.60%)Baseline: 4.75 µs
5.68 µs
(87.63%)
Adapter::Magic (Rust)📈 view plot
🚷 view threshold
27.53 µs
(+3.31%)Baseline: 26.64 µs
30.29 µs
(90.87%)
Adapter::Rust📈 view plot
🚷 view threshold
4.66 µs
(+15.47%)Baseline: 4.04 µs
6.34 µs
(73.60%)
Adapter::RustBench📈 view plot
🚷 view threshold
4.64 µs
(+15.19%)Baseline: 4.03 µs
6.31 µs
(73.57%)
🐰 View full continuous benchmarking report in Bencher

@epompeii
epompeii added this pull request to stack #1054 September 26, 2026 02:44
@epompeii
epompeii marked this pull request as draft September 26, 2026 03:30
@epompeii
epompeii marked this pull request as ready for review September 26, 2026 04:19
@epompeii
epompeii marked this pull request as draft September 26, 2026 04:27
@epompeii
epompeii marked this pull request as ready for review September 26, 2026 14:13
@epompeii
epompeii marked this pull request as draft September 26, 2026 17:39
@epompeii
epompeii force-pushed the u/ep/callback/fire branch 2 times, most recently from 1ff09b7 to a41efc9 Compare September 26, 2026 22:04
@epompeii
epompeii force-pushed the u/ep/callback/fire branch 2 times, most recently from 8736c22 to ea5e5b1 Compare September 27, 2026 02:45
Every write that makes a job Processed, Failed, or Canceled now fires the job's callback, which hands it to a delivery task. A job has at most one delivery task at a time, and the task sends only a pending callback of a terminal job, so a callback is sent once however many paths finish the job. The task opens the sealed request and renders its body with the job's values. It builds the job's report, exactly as the report endpoint returns it, only for a callback without a body or one whose body sends the report, and on a read connection, so a large report never holds the writer. At most two reports build at once, so a burst of callbacks cannot take every worker thread, and a delivery that finds no read connection keeps waiting for one. It makes up to three attempts, 4 s and 16 s apart, retrying a timeout, a connection error, 408, 429, or a 5xx. Each attempt is recorded and logged with the destination host only, never the body, and the attempt that ends a delivery is recorded in the update that settles it. The count survives a restart, so a delivery started again continues from it.

The API context owns the delivery tasks. Shutdown cuts a delivery short, including its wait to build a report, without a write, so its callback stays pending, and waits for every task once the rate limiting state is saved. Job recovery fires every pending callback on a terminal job. The server builds the callback client at boot: if the build fails, it logs the cause once and starts anyway, and every attempt is then a connection error. Nothing creates a callback yet.

This branch was successfully deployed

1 active (outdated) deployment
Cloudflare — 5376e931 Deployed Sep 26, 2026 by epompeii via Build / Build API Docker #1703
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