返回 DeepSeek-Reasonix
startup_lease_contention_test.go
根目录 / desktop / startup_lease_contention_test.go
1 package main
2
3 import (
4 "os"
5 "path/filepath"
6 "testing"
7 "time"
8
9 "reasonix/internal/agent"
10 "reasonix/internal/config"
11 )
12
13 // TestEnsureTabSessionLeaseForRebuildSurvivesTransientHolder reproduces the
14 // startup "this session is already open in another Reasonix window" false
15 // positive: a transient lease holder — CleanupStaleRunning probing a running
16 // subagent's parent session during a concurrent controller build — holds the
17 // session lease for a few milliseconds while the tab's own startup bind runs.
18 // The bind must retry against the genuinely-free lease instead of surfacing a
19 // spurious ErrSessionLeaseHeld.
20 func TestEnsureTabSessionLeaseForRebuildSurvivesTransientHolder(t *testing.T) {
21 isolateDesktopUserDirs(t)
22 dir := config.SessionDir()
23 if err := os.MkdirAll(dir, 0o755); err != nil {
24 t.Fatalf("mkdir sessions: %v", err)
25 }
26 path := filepath.Join(dir, "contended-session.jsonl")
27
28 tab := &WorkspaceTab{ID: "tab", Scope: "global", Ready: true, SessionPath: path}
29 app := &App{
30 tabs: map[string]*WorkspaceTab{tab.ID: tab},
31 tabOrder: []string{tab.ID},
32 }
33 t.Cleanup(tab.releaseSessionLease)
34
35 // Simulate CleanupStaleRunning's transient parent-session lease probe:
36 // acquire, hold briefly, then release. The probe targets the same runtime
37 // key the tab's bind uses (case-folded on Windows), so the contention is
38 // real on every platform.
39 key := sessionRuntimeKey(path)
40 acquired := make(chan struct{})
41 releaseProbe := make(chan struct{})
42 probeDone := make(chan struct{})
43 go func() {
44 defer close(probeDone)
45 lease, err := agent.TryAcquireSessionLease(key)
46 if err != nil {
47 t.Errorf("probe lease acquire: %v", err)
48 close(acquired)
49 return
50 }
51 close(acquired)
52 <-releaseProbe
53 lease.Release()
54 }()
55
56 <-acquired // the probe now holds the lease
57
58 bindErr := make(chan error, 1)
59 go func() {
60 bindErr <- app.ensureTabSessionLeaseForRebuild(tab, path, "")
61 }()
62
63 // Give the first bind attempt time to fail against the held lease, then
64 // release the probe: the bind must succeed on a later attempt.
65 time.Sleep(50 * time.Millisecond)
66 close(releaseProbe)
67
68 select {
69 case err := <-bindErr:
70 if err != nil {
71 t.Fatalf("startup bind failed against a transient holder: %v", err)
72 }
73 case <-time.After(5 * time.Second):
74 t.Fatal("startup bind did not complete after the transient holder released")
75 }
76 <-probeDone
77
78 if key := tab.sessionLeaseRuntimeKey(); key != sessionRuntimeKey(path) {
79 t.Fatalf("tab lease key = %q, want %q", key, sessionRuntimeKey(path))
80 }
81 }
82
82 lines GO