vxx2 / vlib / x / executor / admission_test.v
388 lines · 346 sloc · 9.91 KB · e80386b6af8a071a2e13fd654ebcdb2105fb9202
Raw
1module executor
2
3import context
4import time
5
6fn test_try_post_returns_backpressure_when_queue_is_full() {
7 mut ex := new(queue_size: 1)!
8 ex.try_post(fn () ! {})!
9 ex.try_post(fn () ! {}) or {
10 assert err.msg() == 'executor: queue is full'
11 ex.stop()
12 return
13 }
14 assert false, 'try_post accepted a job beyond queue capacity'
15}
16
17fn test_post_with_timeout_returns_timeout_when_queue_stays_full() {
18 mut ex := new(queue_size: 1)!
19 ex.try_post(fn () ! {})!
20 ex.post_with_timeout(20 * time.millisecond, fn () ! {}) or {
21 assert err.msg() == 'executor: timeout'
22 ex.stop()
23 return
24 }
25 assert false, 'post_with_timeout accepted a job while the queue stayed full'
26}
27
28fn test_post_with_timeout_zero_timeout_rejects_available_capacity() {
29 mut ex := new(queue_size: 1)!
30 ran := chan bool{cap: 1}
31 ex.post_with_timeout(time.Duration(0), fn [ran] () ! {
32 ran <- true
33 return
34 }) or {
35 assert err.msg() == 'executor: timeout'
36 assert ex.drain_pending(1)! == 0
37 assert_no_bool(ran, 'zero-timeout admission job ran')
38 ex.stop()
39 ex.wait()!
40 return
41 }
42 assert false, 'post_with_timeout accepted a job with zero timeout'
43}
44
45fn test_post_with_timeout_negative_timeout_rejects_available_capacity() {
46 mut ex := new(queue_size: 1)!
47 ran := chan bool{cap: 1}
48 ex.post_with_timeout(-1 * time.nanosecond, fn [ran] () ! {
49 ran <- true
50 return
51 }) or {
52 assert err.msg() == 'executor: timeout'
53 assert ex.drain_pending(1)! == 0
54 assert_no_bool(ran, 'negative-timeout admission job ran')
55 ex.stop()
56 ex.wait()!
57 return
58 }
59 assert false, 'post_with_timeout accepted a job with negative timeout'
60}
61
62fn test_post_with_timeout_long_timeout_accepts_available_capacity() {
63 jobs := 4
64 mut ex := new(queue_size: jobs)!
65 ran := chan int{cap: jobs}
66
67 for i in 0 .. jobs {
68 ex.post_with_timeout(1 * time.hour, fn [ran, i] () ! {
69 value := i + 1
70 ran <- value
71 return
72 })!
73 }
74
75 assert ex.drain_pending(jobs)! == jobs
76 mut total := 0
77 for _ in 0 .. jobs {
78 total += <-ran
79 }
80 assert total == 10
81
82 ex.stop()
83 assert !ex.run_one()!
84 ex.wait()!
85}
86
87fn test_post_with_context_returns_parent_cancellation_without_accepting_job() {
88 mut ex := new(queue_size: 1)!
89 ex.try_post(fn () ! {})!
90 mut background := context.background()
91 mut parent, cancel := context.with_cancel(mut background)
92 cancel()
93 ran := chan bool{cap: 1}
94
95 ex.post_with_context(parent, fn [ran] () ! {
96 ran <- true
97 }) or {
98 assert err.msg() == 'context canceled'
99 ex.stop()
100 assert_no_bool(ran, 'canceled admission job ran')
101 return
102 }
103 assert false, 'post_with_context accepted after parent cancellation'
104}
105
106fn test_post_with_context_rejects_nil_job() {
107 mut ex := new(queue_size: 1)!
108 nil_job := unsafe { JobFn(nil) }
109 ex.post_with_context(context.background(), nil_job) or {
110 assert err.msg() == 'executor: job function is nil'
111 ex.stop()
112 return
113 }
114 assert false, 'post_with_context accepted a nil job'
115}
116
117fn test_post_with_timeout_rejects_nil_job() {
118 mut ex := new(queue_size: 1)!
119 nil_job := unsafe { JobFn(nil) }
120 ex.post_with_timeout(1 * time.second, nil_job) or {
121 assert err.msg() == 'executor: job function is nil'
122 ex.stop()
123 return
124 }
125 assert false, 'post_with_timeout accepted a nil job'
126}
127
128fn test_owner_post_with_context_full_queue_returns_owner_capacity_error() {
129 mut ex := new(queue_size: 1)!
130 owner_started := chan bool{cap: 1}
131 filler_accepted := chan bool{cap: 1}
132 owner_result := chan string{cap: 1}
133 filler_ran := chan bool{cap: 1}
134 nested_ran := chan bool{cap: 1}
135
136 ex.try_post(fn [mut ex, owner_started, filler_accepted, owner_result, nested_ran] () ! {
137 owner_started <- true
138 assert wait_for_bool(filler_accepted, 'filler job was not accepted')
139 ex.post_with_context(context.background(), fn [nested_ran] () ! {
140 nested_ran <- true
141 }) or {
142 owner_result <- err.msg()
143 return
144 }
145 owner_result <- 'accepted'
146 })!
147
148 filler := spawn fn [mut ex, owner_started, filler_accepted, filler_ran] () {
149 assert wait_for_bool(owner_started, 'owner callback did not start')
150 ex.try_post(fn [filler_ran] () ! {
151 filler_ran <- true
152 }) or {
153 filler_accepted <- false
154 return
155 }
156 filler_accepted <- true
157 }()
158
159 runner := spawn fn [mut ex, owner_result] () {
160 ex.run_one() or {
161 owner_result <- err.msg()
162 return
163 }
164 }()
165
166 msg := wait_for_string(owner_result, 'owner post_with_context did not return')
167 assert msg == 'executor: owner thread cannot wait for queue capacity'
168
169 ex.stop()
170 runner.wait()
171 filler.wait()
172 assert ex.run_one()!
173 assert wait_for_bool(filler_ran, 'filler job did not run after owner rejection')
174 assert_no_bool(nested_ran, 'owner post_with_context job ran after rejection')
175 ex.wait()!
176}
177
178fn test_owner_post_with_timeout_full_queue_returns_owner_capacity_error() {
179 mut ex := new(queue_size: 1)!
180 owner_started := chan bool{cap: 1}
181 filler_accepted := chan bool{cap: 1}
182 owner_result := chan string{cap: 1}
183 filler_ran := chan bool{cap: 1}
184 nested_ran := chan bool{cap: 1}
185
186 ex.try_post(fn [mut ex, owner_started, filler_accepted, owner_result, nested_ran] () ! {
187 owner_started <- true
188 assert wait_for_bool(filler_accepted, 'filler job was not accepted')
189 ex.post_with_timeout(1 * time.hour, fn [nested_ran] () ! {
190 nested_ran <- true
191 }) or {
192 owner_result <- err.msg()
193 return
194 }
195 owner_result <- 'accepted'
196 })!
197
198 filler := spawn fn [mut ex, owner_started, filler_accepted, filler_ran] () {
199 assert wait_for_bool(owner_started, 'owner callback did not start')
200 ex.try_post(fn [filler_ran] () ! {
201 filler_ran <- true
202 }) or {
203 filler_accepted <- false
204 return
205 }
206 filler_accepted <- true
207 }()
208
209 runner := spawn fn [mut ex, owner_result] () {
210 ex.run_one() or {
211 owner_result <- err.msg()
212 return
213 }
214 }()
215
216 msg := wait_for_string(owner_result, 'owner post_with_timeout did not return')
217 assert msg == 'executor: owner thread cannot wait for queue capacity'
218
219 ex.stop()
220 runner.wait()
221 filler.wait()
222 assert ex.run_one()!
223 assert wait_for_bool(filler_ran, 'filler job did not run after owner rejection')
224 assert_no_bool(nested_ran, 'owner post_with_timeout job ran after rejection')
225 ex.wait()!
226}
227
228fn test_owner_try_post_full_queue_returns_queue_full_and_pump_continues() {
229 mut ex := new(queue_size: 1)!
230 owner_started := chan bool{cap: 1}
231 filler_accepted := chan bool{cap: 1}
232 owner_result := chan string{cap: 1}
233 filler_ran := chan bool{cap: 1}
234 nested_ran := chan bool{cap: 1}
235
236 ex.try_post(fn [mut ex, owner_started, filler_accepted, owner_result, nested_ran] () ! {
237 owner_started <- true
238 assert wait_for_bool(filler_accepted, 'filler job was not accepted')
239 ex.try_post(fn [nested_ran] () ! {
240 nested_ran <- true
241 }) or {
242 owner_result <- err.msg()
243 return
244 }
245 owner_result <- 'accepted'
246 })!
247
248 filler := spawn fn [mut ex, owner_started, filler_accepted, filler_ran] () {
249 assert wait_for_bool(owner_started, 'owner callback did not start')
250 ex.try_post(fn [filler_ran] () ! {
251 filler_ran <- true
252 }) or {
253 filler_accepted <- false
254 return
255 }
256 filler_accepted <- true
257 }()
258
259 assert ex.run_one()!
260 assert wait_for_string(owner_result, 'owner try_post did not return') == 'executor: queue is full'
261 filler.wait()
262 assert ex.run_one()!
263 assert wait_for_bool(filler_ran, 'pump did not continue to filler job')
264 assert_no_bool(nested_ran, 'owner try_post job ran after queue-full rejection')
265
266 ex.stop()
267 assert !ex.run_one()!
268 ex.wait()!
269}
270
271fn test_owner_bounded_posts_accept_when_capacity_is_available() {
272 mut ex := new(queue_size: 2)!
273 owner_result := chan string{cap: 1}
274 context_ran := chan bool{cap: 1}
275 timeout_ran := chan bool{cap: 1}
276
277 ex.try_post(fn [mut ex, owner_result, context_ran, timeout_ran] () ! {
278 ex.post_with_context(context.background(), fn [context_ran] () ! {
279 context_ran <- true
280 }) or {
281 owner_result <- err.msg()
282 return
283 }
284 ex.post_with_timeout(1 * time.hour, fn [timeout_ran] () ! {
285 timeout_ran <- true
286 }) or {
287 owner_result <- err.msg()
288 return
289 }
290 owner_result <- 'accepted'
291 })!
292
293 assert ex.run_one()!
294 assert wait_for_string(owner_result, 'owner bounded posts did not return') == 'accepted'
295 assert ex.run_one()!
296 assert ex.run_one()!
297 assert wait_for_bool(context_ran, 'owner post_with_context job did not run')
298 assert wait_for_bool(timeout_ran, 'owner post_with_timeout job did not run')
299
300 ex.stop()
301 assert !ex.run_one()!
302 ex.wait()!
303}
304
305fn test_post_with_context_waits_until_capacity_opens() {
306 mut ex := new(queue_size: 1)!
307 release := chan bool{cap: 1}
308 started := chan bool{cap: 1}
309 attempting := chan bool{cap: 1}
310 accepted := chan bool{cap: 1}
311 ran := chan bool{cap: 1}
312 owner_result := chan string{cap: 1}
313
314 ex.try_post(fn [started, release] () ! {
315 started <- true
316 _ := <-release
317 })!
318
319 submitter := spawn fn [mut ex, attempting, accepted, ran] () {
320 attempting <- true
321 ex.post_with_context(context.background(), fn [ran] () ! {
322 ran <- true
323 }) or {
324 accepted <- false
325 return
326 }
327 accepted <- true
328 }()
329 assert wait_for_bool(attempting, 'bounded submitter did not start')
330 assert_no_bool(accepted, 'post_with_context returned before capacity opened')
331
332 owner := spawn fn [mut ex, owner_result] () {
333 ran_one := ex.run_one() or {
334 owner_result <- err.msg()
335 return
336 }
337 if ran_one {
338 owner_result <- 'ran'
339 } else {
340 owner_result <- 'empty'
341 }
342 }()
343 assert wait_for_bool(started, 'blocking job did not start')
344 assert wait_for_bool(accepted, 'post_with_context did not accept after capacity opened')
345 release <- true
346 assert wait_for_string(owner_result, 'owner run_one did not return') == 'ran'
347 assert ex.run_one()!
348 assert wait_for_bool(ran, 'bounded admission job did not run')
349
350 ex.stop()
351 assert !ex.run_one()!
352 ex.wait()!
353 owner.wait()
354 submitter.wait()
355}
356
357fn wait_for_bool(signal chan bool, message string) bool {
358 select {
359 value := <-signal {
360 return value
361 }
362 1 * time.second {
363 assert false, message
364 }
365 }
366 return false
367}
368
369fn wait_for_string(signal chan string, message string) string {
370 select {
371 value := <-signal {
372 return value
373 }
374 1 * time.second {
375 assert false, message
376 }
377 }
378 return ''
379}
380
381fn assert_no_bool(signal chan bool, message string) {
382 select {
383 _ := <-signal {
384 assert false, message
385 }
386 50 * time.millisecond {}
387 }
388}
389