Skip to main content

v2.79.0

Binaries and the merged-pull-request list are on the GitHub releases page.

A waiting job keeps the run's state​

A job waiting for an upstream in another process — a --job runner waiting on its dependencies, a lane job waiting on another lane — is woken by the run's next event. Until now every wake then re-read the run's whole event log and re-downloaded the plan object to learn the job graph, and read the runnable frontier besides: three requests and a growing download per wake, discarding the very events the wake had returned.

The process now keeps a view of the run: the folded state, the plan's dependency graph (seeded from the plan it holds, or fetched once) and the timestamps status shows. Each long-poll's events are folded onto it, the frontier is derived from it, and a read made right after a wake asks the server nothing. A view that has gone unsynced reads only the events past its last sequence number. A waiting job costs the platform one request per wake, and a wake's cost no longer grows with the run. orun status --watch, logs and tui read through the same view.

The first read is one request​

A process that joins a run it did not create, or status opening a run, had to assemble its first view from the run's whole event log and the plan object. Orun Cloud now answers the run's folded state in one read (GET …/runs/{id}/state: the jobs, the runnable frontier, the sequence number it is current to, and the timestamps status shows), and the first read asks for it: one request whose size is the number of jobs, not the run's length. On a platform without that read the first view is still built from the log.

A waiting job is woken by progress, not by heartbeats​

Every running job renews its lease every 20 seconds, and each renewal is an event in the run's stream, so every job waiting on an upstream was woken by every heartbeat in the run: with ten jobs running, a wake every two seconds, each one a request. The runner's waits now ask the platform to hold through lease renewals (wake=progress on the event long-poll): a wait is woken when a job is claimed, finishes, fails or is re-queued, and otherwise lapses on its own schedule, with the renewals still on the page so the run's lease view stays whole. A platform without the parameter wakes as before, and the wait still treats a renewal as no news. orun status --watch keeps the plain wait, since it shows the heartbeats.

Logs are uploaded beside the run, not in its way​

A step's log block was uploaded inside the step's completion callback, under one lock for the whole process: a step waited for its upload to land before the job went on, and the jobs of a lane serialised on that lock. The pipeline now queues the block and returns; a worker per job uploads in order beside the run (up to four jobs at a time), and a rate-limited answer's Retry-After is honoured on the spot instead of falling into the backoff and the spill file. Buffering, spilling and the drain at exit are unchanged.

logs --follow waits for output instead of polling​

The live tail asked the platform for new output every 1.5 seconds. Each read is now held on the server until output lands, the job ends, or 15 seconds pass (?wait=), so a tail costs one request per wait. On a platform without the held read it polls as before.

Fewer requests outside the run​

  • orun catalog push (and the autosync after orun plan) no longer advances a head to the snapshot it last advanced it to: the platform already holds it, and the advance would add a history row and trigger the org-global projection for nothing. --force advances it again.
  • orun work sync lists an epic's tasks once instead of reading each declared task, and lists its pushed docs once so a doc whose committed sha the platform already holds at its path is not sent again.
  • A composition source the lockfile pins (.orun/compositions.lock.yaml) is taken at its locked digest, from the cache when it holds it, instead of resolving the tag against the registry on every orun plan.

The plan is uploaded once per run​

The plan object every process of a run negotiates with the object plane carried the time it was converted, so its digest changed every second and each matrix job or lane uploaded the same plan again. The blob is now content-addressed as it was meant to be: the first process uploads it and the rest find it present. Blobs older clients wrote still decode.

One connection pool, and the session read once​

The coordination verbs and long-polls used Go's default HTTP client — two idle connections per host and no timeout — beside the client the rest of the run uses. They now share that client's keep-alive transport under a 45 s timeout. The CLI session token, which was read and parsed from the store (a file, or the keychain) on every request, is reused for 30 seconds while it is not near expiry.

Also in this release​

  • A lane-scoped install may follow the setup actions it needs. A composition's scope: lane install step could not come after the setup-node and setup-pnpm actions that put its tools on PATH, so no real composition could share its install. A use: step may now precede a lane-scoped step; a job-scoped run: step still may not (#761).
  • A step's relative working-directory is applied once. The runner joined it against the job's directory twice, and the GitHub Actions engine a third time, so ../.. from apps/web-console-next ran outside the repository (ERR_PNPM_NO_PKG_MANIFEST). Both apply it once (#764, #765).

The interim tags v2.78.2, v2.78.3 and v2.78.4 carried this work as it merged; v2.79.0 is the release it is documented under.

The analysis behind these changes is specs/orun-sync-efficiency in the repository (milestones SE1 to SE6).