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 afterorun 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.--forceadvances it again.orun work synclists 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 everyorun 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: laneinstall step could not come after thesetup-nodeandsetup-pnpmactions that put its tools on PATH, so no real composition could share its install. Ause:step may now precede a lane-scoped step; a job-scopedrun:step still may not (#761). - A step's relative
working-directoryis applied once. The runner joined it against the job's directory twice, and the GitHub Actions engine a third time, so../..fromapps/web-console-nextran 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).