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/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

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

Phase 4: record the queue's new shape, and let the pins be tested while running

d798eaacf9b210fcdc2b51372d64a0e21e239bab · Keenan Nemetz · 16d ago · parent ac433531

Phase 4: record the queue's new shape, and let the pins be tested while running

Docs, plus the two tests whose setup the new cancellation invalidates.

docs/roadmap.md gains slices 7 and 8, rewrites the Phase 4 remaining list (the
push-event table is gone by decision; what is left is the toolchain in the record, a
narrowing/targeting filter for a mixed fleet, and a per-repository cap somebody would
have to tune), adds an exit criterion for "a push whose spool file was never written
still runs", and appends four decision-log rows: reconcile from the refs rather than
persist events, park an idle lease on a wake-up channel, cancel queued superseded
runs and order the lease by in-flight count, and keep runner presence in the queue
filtered by the clone rule. README describes the new queue behaviour and /runners,
and its stale "instance ed25519 key generated at first boot" line is replaced: the
instance holds no signing key.

Two tests built their scenario on a superseded run being started — queue v1, push v2,
then run both. That is exactly what the queue no longer does, so both now hold the
first build inside its step with a sentinel file, land the second push while it is
running, and release it. The properties are unchanged and now tested where they
actually apply: a build that is running when the branch moves must not publish a site
(TestWorkflowRefusesAStaleSiteDeploy), and a run executes the steps it was queued
with, not the workflow as the branch has it now
(TestQueuedRunUsesTheWorkflowItWasQueuedFrom). A run that is still waiting when the
new tip arrives is canceled, which TestANewerTipCancelsTheQueuedBuildOfTheOlderOne
covers.

scripts/ci.sh green before these comment-and-doc edits (internal/server 132.9s,
everything else included); gofmt, vet, build and the markdown link check green after.

internal/server/integration_test.go +25−14

@@ -2854,23 +2854,40 @@ jobs:
2854 2854 // TestQueuedRunUsesTheWorkflowItWasQueuedFrom: editing a workflow after a push
2855 2855 // must not change what the already-queued run executes. The step list is pinned
2856 2856 // when the run is created, from the blob that was pushed.
2857 //
2858 // The first run is held inside its step, so the second push lands while it is running.
2859 // A run still waiting when a newer tip arrives is canceled rather than started — the
2860 // pinning question only exists for a run that got as far as executing.
2857 2861 func TestQueuedRunUsesTheWorkflowItWasQueuedFrom(t *testing.T) {
2858 2862 f := newFixtureWith(t, func(c *config.Config) { c.LeaseSeconds = 5 })
2859 2863 f.publish(f.repo)
2860 2864
2865 sentinel := filepath.Join(t.TempDir(), "release")
2861 2866 work := workRepo(t, workflowFile(`version: 1
2862 2867 jobs:
2863 2868 test:
2864 2869 steps:
2865 - run: echo first-version
2870 - run: while [ ! -f `+sentinel+` ]; do sleep 0.1; done; echo first-version
2866 2871 `))
2867 2872 if _, errs, err := f.git(work, "push", f.sshURL(f.repo), "main:refs/heads/main"); err != nil {
2868 2873 t.Fatalf("push v1: %s", errs)
2869 2874 }
2870 2875 firstSHA := mustSHA(t, work)
2871 f.waitForRun(t, 5*time.Second, func(r *runs.Run) bool { return r.SHA == firstSHA })
2876 first := f.waitForRun(t, 5*time.Second, func(r *runs.Run) bool { return r.SHA == firstSHA })
2877
2878 // Lease the v1 run and hold it in its step.
2879 held := exec.Command(binPath, "runner", "--remote", f.sshBase(), "--once", "--dir", t.TempDir())
2880 held.Env = f.ciEnv()
2881 held.Stderr = io.Discard
2882 if err := held.Start(); err != nil {
2883 t.Fatal(err)
2884 }
2885 t.Cleanup(func() { _ = held.Process.Kill(); _ = held.Wait() })
2886 f.waitForRun(t, 15*time.Second, func(r *runs.Run) bool {
2887 return r.ID == first.ID && r.Status == runs.StatusRunning
2888 })
2872 2889
2873 // Second push rewrites the workflow.
2890 // Second push rewrites the workflow, while the first run is executing.
2874 2891 if err := os.WriteFile(filepath.Join(work, ".vole/workflows/ci.yaml"),
2875 2892 []byte("version: 1\njobs:\n test:\n steps:\n - run: echo second-version\n"), 0o644); err != nil {
2876 2893 t.Fatal(err)
@@ -2887,20 +2904,14 @@ jobs:
2887 2904 secondSHA := mustSHA(t, work)
2888 2905 f.waitForRun(t, 5*time.Second, func(r *runs.Run) bool { return r.SHA == secondSHA })
2889 2906
2890 // Run both jobs.
2891 for i := 0; i < 2; i++ {
2892 cmd := exec.Command(binPath, "runner", "--remote", f.sshBase(), "--once", "--dir", t.TempDir())
2893 cmd.Env = f.ciEnv()
2894 cmd.Stderr = io.Discard
2895 if err := cmd.Start(); err != nil {
2896 t.Fatal(err)
2897 }
2898 _ = cmd.Wait()
2907 // Release the held build, then run the job queued from v2.
2908 if err := os.WriteFile(sentinel, []byte("go\n"), 0o644); err != nil {
2909 t.Fatal(err)
2899 2910 }
2900
2901 first := f.waitForRun(t, 30*time.Second, func(r *runs.Run) bool {
2911 first = f.waitForRun(t, 30*time.Second, func(r *runs.Run) bool {
2902 2912 return r.SHA == firstSHA && r.Status != runs.StatusQueued && r.Status != runs.StatusRunning
2903 2913 })
2914 f.runOnce(t)
2904 2915 second := f.waitForRun(t, 30*time.Second, func(r *runs.Run) bool {
2905 2916 return r.SHA == secondSHA && r.Status != runs.StatusQueued && r.Status != runs.StatusRunning
2906 2917 })