package rootstate import ( "context" "errors" "os" "path/filepath" "sync/atomic" "testing" "time" ) func TestPlan14ConcurrentAdoptionPublicationOrder(t *testing.T) { path, release := fixture(t) _, host := fixture(t) ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() entered, finish := make(chan struct{}), make(chan struct{}) done := make(chan error, 2) var effects atomic.Int32 go func() { done <- WithRelease(ctx, release, func() error { unlock, err := host.Lock(ctx, "host.lock") if err != nil { return err } defer unlock() close(entered) select { case <-finish: case <-ctx.Done(): return ctx.Err() } effects.Add(1) return nil }) }() select { case <-entered: case <-ctx.Done(): t.Fatal(ctx.Err()) } // A separate open is a distinct flock description, as in two root processes. parent, err := os.Open(filepath.Dir(path)) if err != nil { t.Fatal(err) } defer parent.Close() other, err := descend(parent, []string{filepath.Base(path)}, false, false, uint32(os.Geteuid())) if err != nil { t.Fatal(err) } defer other.Close() short, stop := context.WithCancel(ctx) stop() if err := WithRelease(short, other, func() error { effects.Add(100); return nil }); !errors.Is(err, context.Canceled) { t.Fatalf("canceled contender: %v", err) } go func() { done <- WithRelease(ctx, other, func() error { unlock, err := host.Lock(ctx, "host.lock") if err != nil { return err } defer unlock() effects.Add(1) return nil }) }() close(finish) for i := 0; i < 2; i++ { select { case err := <-done: if err != nil { t.Fatal(err) } case <-ctx.Done(): t.Fatal("release/host lock deadlock") } } if effects.Load() != 2 { t.Fatalf("effects = %d", effects.Load()) } // The stable lock survives completion; neither caller unlinks it. if _, err := os.Stat(filepath.Join(path, "release.lock")); err != nil { t.Fatal(err) } }