//go:build linux package host import ( "errors" "os" "path/filepath" "testing" "time" "github.com/carlosframework/platform/internal/release" ) // Final review finding 1: the held-stage lock is a per-host flock that a // second holder (another open, as another process makes) waits for and // then gives up on; other hosts do not contend; a planted symlink is // refused. func TestHeldStageLockSerialisesItsHolders(t *testing.T) { prev := heldLockWait t.Cleanup(func() { heldLockWait = prev }) heldLockWait = 50 * time.Millisecond data := t.TempDir() in, out, done := make(chan struct{}), make(chan struct{}), make(chan error, 1) go func() { done <- withHeldStageLock(data, hsHost, func() error { close(in); <-out; return nil }) }() <-in ran := false if err := withHeldStageLock(data, hsHost, func() error { ran = true; return nil }); !errors.Is(err, errHeldLockBusy) || ran { t.Fatalf("a second holder: %v (ran %v), want errHeldLockBusy", err, ran) } if err := withHeldStageLock(data, "other.example.com", func() error { ran = true; return nil }); err != nil || !ran { t.Fatalf("another host's lock: %v (ran %v)", err, ran) } close(out) if err := <-done; err != nil { t.Fatal(err) } ran = false if err := withHeldStageLock(data, hsHost, func() error { ran = true; return nil }); err != nil || !ran { t.Fatalf("after release: %v (ran %v)", err, ran) } if err := withHeldStageLock(data, "../x", func() error { return nil }); err == nil { t.Fatal("a malformed host was locked") } if err := os.Symlink(filepath.Join(t.TempDir(), "elsewhere"), filepath.Join(data, ".moves", heldStageLockFile("planted.example.com"))); err != nil { t.Fatal(err) } if err := withHeldStageLock(data, "planted.example.com", func() error { return nil }); err == nil { t.Fatal("a planted symlink was followed") } } // The publication runs under the lock: heldStageAuthority's then (the // publish closure: link read, bind, swap, re-hash, rollback) holds host's // held-stage lock, inside the registry's freeze. The pre-stage check // (then nil) takes no lock. func TestHeldPublicationHoldsTheHeldStageLock(t *testing.T) { prev := heldLockWait t.Cleanup(func() { heldLockWait = prev }) heldLockWait = 50 * time.Millisecond s := release.NewDirStore(t.TempDir()) hsClaim(t, s, movingOp()) hsReport(t, s, hsOp, "v7") data := t.TempDir() auth := heldStageAuthority(testRegistry(t), s, hsBox, data) if err := auth(hsRequest(), nil); err != nil { t.Fatal(err) } if _, err := os.Stat(filepath.Join(data, ".moves", heldStageLockFile(hsHost))); !errors.Is(err, os.ErrNotExist) { t.Fatalf("the pre-stage check took the lock: %v", err) } published := false err := auth(hsRequest(), func() error { published = true return withHeldStageLock(data, hsHost, func() error { return errors.New("the lock was free during the publication") }) }) if !published || !errors.Is(err, errHeldLockBusy) { t.Fatalf("publication: %v (ran %v), want the lock held around it", err, published) } }