Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/vole.git

Write over SSH with a registered key:

git clone ssh://git@rodent.software:2222/vole.git

A free and open-source forge that lives in your Git repo.

Phase 4: the queue is reconciled from the refs, not only from the spool

a945ae5581394065c78c3c1d927e1d558ef56fdc · Keenan Nemetz · 17d ago · parent 592710c2

Phase 4: the queue is reconciled from the refs, not only from the spool

A push reached CI through exactly one statement: a spool file written by
post-receive. The hook runs after the refs move and is allowed to fail that write
without failing the push, so a crash or a full disk between the two left a live
commit with no build and nothing anywhere that would ever notice. Deleting
runs.db had the same shape: queued jobs vanished, while the package header claimed
only in-flight work was lost.

Both are the same question, so they now have the same answer: a ref tip is the
durable statement that a commit is live. ref_cursors records the tip each ref had
when its work was queued; every 30 seconds — and once at startup, so a wiped queue
has no blind spot — reconcileRefs compares the refs against those cursors and
queues whatever the queue has never seen. The spool stays exactly what it should
be: the sub-second path, not the promise.

Costs, stated rather than discovered later: a tip no cursor has recorded is work
owed, so an instance upgrading onto this (or one whose runs.db was deleted) queues
its current tips once. The unique index on (repo, ref, sha, workflow, job) stops a
tip that already ran from running twice once its row is restored from the records
on the metadata branch by `vole index rebuild`.

Measured by TestReconcileQueuesATipTheQueueHasNeverSeen (a tip is queued once, a
second pass queues nothing), TestReconcileRebuildsAQueueThatWasDeleted (runs.db and
its WAL deleted, the live tip is queued again), TestReconcileIgnoresTheMetadataBranch
and TestReconcileDropsTheCursorOfADeletedBranch.

internal/server/server.go +1

@@ -136,6 +136,7 @@ func New(cfg *config.Config, binPath string, log *slog.Logger) (*Server, error)
136 136 s.ctx, s.cancel = context.WithCancel(context.Background())
137 137 go s.watchRoster(s.ctx)
138 138 go s.watchSpool(s.ctx)
139 go s.watchRefs(s.ctx)
139 140 go s.watchLeases(s.ctx)
140 141 return s, nil
141 142 }