返回 DeepSeek-Reasonix
scheduler_priority_test.go
根目录 / internal / agent / scheduler_priority_test.go
1 package agent
2
3 import (
4 "context"
5 "errors"
6 "os"
7 "path/filepath"
8 "runtime"
9 "testing"
10 "time"
11 )
12
13 func TestSchedulerQueuedWholeWriterBlocksLaterDirectoryWriter(t *testing.T) {
14 s := NewSubagentScheduler(4, 3)
15 root := t.TempDir()
16 if err := os.MkdirAll(filepath.Join(root, "src"), 0o755); err != nil {
17 t.Fatal(err)
18 }
19 dir, err := NormalizeWritePaths(root, []string{"src/"})
20 if err != nil {
21 t.Fatal(err)
22 }
23 whole, err := WholeWorkspaceWriteClaim(root)
24 if err != nil {
25 t.Fatal(err)
26 }
27 releaseDir, err := s.Acquire(context.Background(), AcquireRequest{Writer: true, WritePaths: dir})
28 if err != nil {
29 t.Fatal(err)
30 }
31
32 type result struct {
33 release func()
34 err error
35 }
36 wholeResult := make(chan result, 1)
37 go func() {
38 release, acquireErr := s.Acquire(context.Background(), AcquireRequest{Writer: true, WritePaths: whole})
39 wholeResult <- result{release: release, err: acquireErr}
40 }()
41 waitForSchedulerWaiters(t, s, 1)
42
43 lateRelease, err := s.Acquire(context.Background(), AcquireRequest{
44 Writer: true, WritePaths: dir, Nested: true,
45 })
46 if err == nil {
47 lateRelease()
48 releaseDir()
49 t.Fatal("later directory writer bypassed the queued whole-workspace writer")
50 }
51 releaseDir()
52
53 acquired := <-wholeResult
54 if acquired.err != nil {
55 t.Fatal(acquired.err)
56 }
57 if lateRelease, err = s.Acquire(context.Background(), AcquireRequest{
58 Writer: true, WritePaths: dir, Nested: true,
59 }); err == nil {
60 lateRelease()
61 acquired.release()
62 t.Fatal("directory writer started while the queued whole-workspace writer was active")
63 }
64 acquired.release()
65
66 lateRelease, err = s.Acquire(context.Background(), AcquireRequest{
67 Writer: true, WritePaths: dir, Nested: true,
68 })
69 if err != nil {
70 t.Fatalf("directory writer after whole-workspace release: %v", err)
71 }
72 lateRelease()
73 }
74
75 func TestSchedulerQueuedWholeWriterStaysAheadWhenPumpRuns(t *testing.T) {
76 s := NewSubagentScheduler(5, 4)
77 root := t.TempDir()
78 if err := os.MkdirAll(filepath.Join(root, "src"), 0o755); err != nil {
79 t.Fatal(err)
80 }
81 dir, err := NormalizeWritePaths(root, []string{"src/"})
82 if err != nil {
83 t.Fatal(err)
84 }
85 file, err := NormalizeWritePaths(root, []string{"src/first.go"})
86 if err != nil {
87 t.Fatal(err)
88 }
89 whole, err := WholeWorkspaceWriteClaim(root)
90 if err != nil {
91 t.Fatal(err)
92 }
93 releaseDir, dirID, err := s.AcquireWithID(context.Background(), AcquireRequest{Writer: true, WritePaths: dir})
94 if err != nil {
95 t.Fatal(err)
96 }
97
98 type result struct {
99 release func()
100 err error
101 }
102 wholeResult := make(chan result, 1)
103 go func() {
104 release, acquireErr := s.Acquire(context.Background(), AcquireRequest{Writer: true, WritePaths: whole})
105 wholeResult <- result{release: release, err: acquireErr}
106 }()
107 waitForSchedulerWaiters(t, s, 1)
108
109 lateResult := make(chan result, 1)
110 go func() {
111 release, acquireErr := s.Acquire(context.Background(), AcquireRequest{Writer: true, WritePaths: dir})
112 lateResult <- result{release: release, err: acquireErr}
113 }()
114 waitForSchedulerWaiters(t, s, 2)
115
116 readRelease, err := s.Acquire(context.Background(), AcquireRequest{Nested: true})
117 if err != nil {
118 t.Fatalf("read-only work should remain concurrent with a queued workspace writer: %v", err)
119 }
120 readRelease()
121 if err := s.Realize(dirID, file); err != nil {
122 t.Fatal(err)
123 }
124 select {
125 case late := <-lateResult:
126 late.release()
127 releaseDir()
128 acquiredWhole := <-wholeResult
129 if acquiredWhole.err == nil {
130 acquiredWhole.release()
131 }
132 t.Fatal("queued directory writer bypassed an earlier whole-workspace writer during pump")
133 default:
134 }
135
136 releaseDir()
137 acquiredWhole := <-wholeResult
138 if acquiredWhole.err != nil {
139 t.Fatal(acquiredWhole.err)
140 }
141 select {
142 case late := <-lateResult:
143 late.release()
144 acquiredWhole.release()
145 t.Fatal("queued directory writer started while the whole-workspace writer was active")
146 default:
147 }
148 acquiredWhole.release()
149 late := <-lateResult
150 if late.err != nil {
151 t.Fatal(late.err)
152 }
153 late.release()
154 }
155
156 func TestSchedulerCancelQueuedWholeWriterAdmitsLaterWriter(t *testing.T) {
157 s := NewSubagentScheduler(4, 3)
158 root := t.TempDir()
159 if err := os.MkdirAll(filepath.Join(root, "src"), 0o755); err != nil {
160 t.Fatal(err)
161 }
162 dir, err := NormalizeWritePaths(root, []string{"src/"})
163 if err != nil {
164 t.Fatal(err)
165 }
166 whole, err := WholeWorkspaceWriteClaim(root)
167 if err != nil {
168 t.Fatal(err)
169 }
170 releaseDir, err := s.Acquire(context.Background(), AcquireRequest{Writer: true, WritePaths: dir})
171 if err != nil {
172 t.Fatal(err)
173 }
174 defer releaseDir()
175
176 wholeCtx, cancelWhole := context.WithCancel(context.Background())
177 wholeResult := make(chan error, 1)
178 go func() {
179 _, acquireErr := s.Acquire(wholeCtx, AcquireRequest{Writer: true, WritePaths: whole})
180 wholeResult <- acquireErr
181 }()
182 waitForSchedulerWaiters(t, s, 1)
183
184 type result struct {
185 release func()
186 err error
187 }
188 lateResult := make(chan result, 1)
189 go func() {
190 release, acquireErr := s.Acquire(context.Background(), AcquireRequest{Writer: true, WritePaths: dir})
191 lateResult <- result{release: release, err: acquireErr}
192 }()
193 waitForSchedulerWaiters(t, s, 2)
194
195 cancelWhole()
196 if err := <-wholeResult; !errors.Is(err, context.Canceled) {
197 t.Fatalf("cancelled whole-workspace waiter: %v", err)
198 }
199 select {
200 case late := <-lateResult:
201 if late.err != nil {
202 t.Fatal(late.err)
203 }
204 late.release()
205 case <-time.After(2 * time.Second):
206 t.Fatal("later writer stayed queued after the whole-workspace waiter was cancelled")
207 }
208 }
209
210 func waitForSchedulerWaiters(t *testing.T, s *SubagentScheduler, want int) {
211 t.Helper()
212 deadline := time.After(2 * time.Second)
213 for {
214 s.mu.Lock()
215 got := len(s.waiters)
216 s.mu.Unlock()
217 if got >= want {
218 return
219 }
220 select {
221 case <-deadline:
222 t.Fatalf("scheduler waiters = %d, want at least %d", got, want)
223 default:
224 runtime.Gosched()
225 }
226 }
227 }
228
228 lines GO