synctest 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +235 -0
- data/lib/synctest/bubble.rb +1225 -0
- data/lib/synctest/errors.rb +27 -0
- data/lib/synctest/patches.rb +608 -0
- data/lib/synctest/raw.rb +46 -0
- data/lib/synctest/version.rb +5 -0
- data/lib/synctest.rb +113 -0
- metadata +135 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Synctest
|
|
4
|
+
class Error < StandardError
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
class NotInBubbleError < Error
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class NestedBubbleError < Error
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class ConcurrentWaitError < Error
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class DeadlockError < Error
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class StalledError < Error
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class IsolationError < Error
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class UnsupportedOperationError < Error
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,608 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Synctest
|
|
4
|
+
# Process-wide shims which dispatch to a thread-local bubble. Outside a
|
|
5
|
+
# bubble, every method immediately delegates to Ruby's original behavior.
|
|
6
|
+
module Patches
|
|
7
|
+
module KernelPatch
|
|
8
|
+
def sleep(duration = nil)
|
|
9
|
+
bubble = Synctest.current_bubble
|
|
10
|
+
return super unless bubble
|
|
11
|
+
|
|
12
|
+
bubble.sleep(duration)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
module KernelSingletonPatch
|
|
17
|
+
def sleep(duration = nil)
|
|
18
|
+
bubble = Synctest.current_bubble
|
|
19
|
+
return super unless bubble
|
|
20
|
+
|
|
21
|
+
bubble.sleep(duration)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
module TimeSingletonPatch
|
|
26
|
+
def now(**keywords)
|
|
27
|
+
bubble = Synctest.current_bubble
|
|
28
|
+
return super unless bubble
|
|
29
|
+
return super unless keywords.empty? || keywords.keys == [:in]
|
|
30
|
+
|
|
31
|
+
value = bubble.wall_time
|
|
32
|
+
zone = keywords[:in]
|
|
33
|
+
zone ? value.getlocal(zone) : value
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def new(*arguments, **keywords)
|
|
37
|
+
bubble = Synctest.current_bubble
|
|
38
|
+
if bubble && arguments.empty? && (keywords.empty? || keywords.keys == [:in])
|
|
39
|
+
value = bubble.wall_time
|
|
40
|
+
zone = keywords[:in]
|
|
41
|
+
return zone ? value.getlocal(zone) : value
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
super
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
module ProcessSingletonPatch
|
|
49
|
+
def clock_gettime(clock_id, unit = :float_second)
|
|
50
|
+
bubble = Synctest.current_bubble
|
|
51
|
+
return super unless bubble
|
|
52
|
+
|
|
53
|
+
case clock_id
|
|
54
|
+
when Process::CLOCK_MONOTONIC
|
|
55
|
+
bubble.monotonic_time(unit)
|
|
56
|
+
when Process::CLOCK_REALTIME
|
|
57
|
+
bubble.realtime(unit)
|
|
58
|
+
else
|
|
59
|
+
super
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
module ThreadSingletonPatch
|
|
65
|
+
def new(*arguments, **keywords, &block)
|
|
66
|
+
bubble = Synctest.current_bubble
|
|
67
|
+
reserved = false
|
|
68
|
+
return super unless bubble && block
|
|
69
|
+
|
|
70
|
+
bubble.reserve_child
|
|
71
|
+
reserved = true
|
|
72
|
+
created = false
|
|
73
|
+
thread = super do |*block_arguments, **block_keywords|
|
|
74
|
+
Synctest.set_current_bubble(bubble)
|
|
75
|
+
bubble.attach_child(Thread.current)
|
|
76
|
+
Thread.current.report_on_exception = false
|
|
77
|
+
error = nil
|
|
78
|
+
|
|
79
|
+
begin
|
|
80
|
+
block.call(*block_arguments, **block_keywords)
|
|
81
|
+
rescue Exception => exception # rubocop:disable Lint/RescueException
|
|
82
|
+
error = exception
|
|
83
|
+
raise
|
|
84
|
+
ensure
|
|
85
|
+
bubble.child_finished(Thread.current, error)
|
|
86
|
+
Synctest.set_current_bubble(nil)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
created = true
|
|
90
|
+
thread.instance_variable_set(Synctest::OBJECT_BUBBLE_IVAR, bubble)
|
|
91
|
+
bubble.await_child_attachment(thread)
|
|
92
|
+
thread
|
|
93
|
+
ensure
|
|
94
|
+
bubble&.cancel_child_reservation if reserved && !created
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def start(*arguments, **keywords, &block)
|
|
98
|
+
return super unless Synctest.current_bubble
|
|
99
|
+
|
|
100
|
+
new(*arguments, **keywords, &block)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def fork(*arguments, **keywords, &block)
|
|
104
|
+
return super unless Synctest.current_bubble
|
|
105
|
+
|
|
106
|
+
new(*arguments, **keywords, &block)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def stop
|
|
110
|
+
bubble = Synctest.current_bubble
|
|
111
|
+
return super unless bubble
|
|
112
|
+
|
|
113
|
+
bubble.stop
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def kill(thread)
|
|
117
|
+
return super unless thread.is_a?(Thread)
|
|
118
|
+
|
|
119
|
+
owner = thread.thread_variable_get(Synctest::THREAD_BUBBLE_KEY) || Synctest.bubble_for(thread)
|
|
120
|
+
return super unless owner
|
|
121
|
+
|
|
122
|
+
thread.kill
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
module ThreadPatch
|
|
127
|
+
def join(timeout = nil)
|
|
128
|
+
owner = Synctest.bubble_for(self)
|
|
129
|
+
bubble = owner ? Synctest.bubble_for_operation(self) : root_bubble_for_join
|
|
130
|
+
return super unless bubble
|
|
131
|
+
|
|
132
|
+
bubble.join(self, timeout)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def value
|
|
136
|
+
owner = Synctest.bubble_for(self)
|
|
137
|
+
bubble = owner ? Synctest.bubble_for_operation(self) : root_bubble_for_join
|
|
138
|
+
return super unless bubble
|
|
139
|
+
unless owner
|
|
140
|
+
if equal?(Thread.current)
|
|
141
|
+
Kernel.raise ThreadError, "Target thread must not be current thread"
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
Kernel.raise UnsupportedOperationError, "Thread#value cannot target the root of its own Synctest bubble"
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
bubble.join(self, nil)
|
|
148
|
+
Synctest::Raw::THREAD_VALUE.bind_call(self)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def wakeup
|
|
152
|
+
bubble = Synctest.bubble_for_thread_operation(self)
|
|
153
|
+
return super unless bubble
|
|
154
|
+
return self if bubble.wake_thread(self)
|
|
155
|
+
|
|
156
|
+
super
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def run
|
|
160
|
+
bubble = Synctest.bubble_for_thread_operation(self)
|
|
161
|
+
return super unless bubble
|
|
162
|
+
return self if bubble.wake_thread(self)
|
|
163
|
+
|
|
164
|
+
super
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def kill
|
|
168
|
+
if thread_variable_get(Synctest::THREAD_BUBBLE_KEY) || Synctest.bubble_for(self)
|
|
169
|
+
bubble = Synctest.bubble_for_thread_operation(self)
|
|
170
|
+
bubble.interrupt_thread(self)
|
|
171
|
+
end
|
|
172
|
+
super
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def terminate
|
|
176
|
+
if thread_variable_get(Synctest::THREAD_BUBBLE_KEY) || Synctest.bubble_for(self)
|
|
177
|
+
bubble = Synctest.bubble_for_thread_operation(self)
|
|
178
|
+
bubble.interrupt_thread(self)
|
|
179
|
+
end
|
|
180
|
+
super
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def exit
|
|
184
|
+
if thread_variable_get(Synctest::THREAD_BUBBLE_KEY) || Synctest.bubble_for(self)
|
|
185
|
+
bubble = Synctest.bubble_for_thread_operation(self)
|
|
186
|
+
bubble.interrupt_thread(self)
|
|
187
|
+
end
|
|
188
|
+
super
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def raise(*arguments)
|
|
192
|
+
if thread_variable_get(Synctest::THREAD_BUBBLE_KEY) || Synctest.bubble_for(self)
|
|
193
|
+
bubble = Synctest.bubble_for_thread_operation(self)
|
|
194
|
+
bubble.interrupt_thread(self)
|
|
195
|
+
end
|
|
196
|
+
super
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
private
|
|
200
|
+
|
|
201
|
+
def root_bubble_for_join
|
|
202
|
+
current = Synctest.current_bubble
|
|
203
|
+
owner = thread_variable_get(Synctest::THREAD_BUBBLE_KEY)
|
|
204
|
+
return unless owner&.active?
|
|
205
|
+
return owner if owner.equal?(current)
|
|
206
|
+
|
|
207
|
+
Kernel.raise IsolationError, "Thread belongs to a different Synctest bubble" if current
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
module MutexPatch
|
|
212
|
+
def initialize(...)
|
|
213
|
+
super
|
|
214
|
+
Synctest.associate(self)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def lock
|
|
218
|
+
bubble = Synctest.bubble_for_operation(self)
|
|
219
|
+
return super unless bubble
|
|
220
|
+
|
|
221
|
+
bubble.mutex_lock(self)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def try_lock
|
|
225
|
+
bubble = Synctest.bubble_for_operation(self)
|
|
226
|
+
return super unless bubble
|
|
227
|
+
|
|
228
|
+
bubble.mutex_try_lock(self)
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def unlock
|
|
232
|
+
bubble = Synctest.bubble_for_operation(self)
|
|
233
|
+
return super unless bubble
|
|
234
|
+
|
|
235
|
+
bubble.mutex_unlock(self)
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def owned?
|
|
239
|
+
bubble = Synctest.bubble_for_operation(self)
|
|
240
|
+
return super unless bubble
|
|
241
|
+
|
|
242
|
+
bubble.mutex_owned?(self)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def synchronize
|
|
246
|
+
bubble = Synctest.bubble_for_operation(self)
|
|
247
|
+
return super unless bubble
|
|
248
|
+
|
|
249
|
+
locked = false
|
|
250
|
+
begin
|
|
251
|
+
bubble.mutex_lock(self)
|
|
252
|
+
locked = true
|
|
253
|
+
yield
|
|
254
|
+
ensure
|
|
255
|
+
bubble.mutex_unlock(self) if locked
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def sleep(timeout = nil)
|
|
260
|
+
bubble = Synctest.bubble_for_operation(self)
|
|
261
|
+
return super unless bubble
|
|
262
|
+
|
|
263
|
+
bubble.mutex_sleep(self, timeout)
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
module ConditionVariablePatch
|
|
268
|
+
def initialize(...)
|
|
269
|
+
super
|
|
270
|
+
Synctest.associate(self)
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def wait(mutex, timeout = nil)
|
|
274
|
+
bubble = Synctest.bubble_for_operation(self)
|
|
275
|
+
return super unless bubble
|
|
276
|
+
unless Synctest.bubble_for(mutex).equal?(bubble)
|
|
277
|
+
raise IsolationError, "a bubbled ConditionVariable requires a Mutex from the same bubble"
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
bubble.condition_wait(self, mutex, timeout)
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def signal
|
|
284
|
+
bubble = Synctest.bubble_for_operation(self)
|
|
285
|
+
return super unless bubble
|
|
286
|
+
|
|
287
|
+
bubble.condition_signal(self, broadcast: false)
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def broadcast
|
|
291
|
+
bubble = Synctest.bubble_for_operation(self)
|
|
292
|
+
return super unless bubble
|
|
293
|
+
|
|
294
|
+
bubble.condition_signal(self, broadcast: true)
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
module MonitorPatch
|
|
299
|
+
def initialize(...)
|
|
300
|
+
super
|
|
301
|
+
Synctest.associate(self)
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def enter
|
|
305
|
+
bubble = Synctest.bubble_for_operation(self)
|
|
306
|
+
return super unless bubble
|
|
307
|
+
|
|
308
|
+
bubble.monitor_enter(self)
|
|
309
|
+
nil
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def mon_enter
|
|
313
|
+
bubble = Synctest.bubble_for_operation(self)
|
|
314
|
+
return super unless bubble
|
|
315
|
+
|
|
316
|
+
bubble.monitor_enter(self)
|
|
317
|
+
nil
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def try_enter
|
|
321
|
+
bubble = Synctest.bubble_for_operation(self)
|
|
322
|
+
return super unless bubble
|
|
323
|
+
|
|
324
|
+
bubble.monitor_try_enter(self)
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
def mon_try_enter
|
|
328
|
+
bubble = Synctest.bubble_for_operation(self)
|
|
329
|
+
return super unless bubble
|
|
330
|
+
|
|
331
|
+
bubble.monitor_try_enter(self)
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def try_mon_enter
|
|
335
|
+
bubble = Synctest.bubble_for_operation(self)
|
|
336
|
+
return super unless bubble
|
|
337
|
+
|
|
338
|
+
bubble.monitor_try_enter(self)
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def exit
|
|
342
|
+
bubble = Synctest.bubble_for_operation(self)
|
|
343
|
+
return super unless bubble
|
|
344
|
+
|
|
345
|
+
bubble.monitor_exit(self)
|
|
346
|
+
nil
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def mon_exit
|
|
350
|
+
bubble = Synctest.bubble_for_operation(self)
|
|
351
|
+
return super unless bubble
|
|
352
|
+
|
|
353
|
+
bubble.monitor_exit(self)
|
|
354
|
+
nil
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
def mon_owned?
|
|
358
|
+
bubble = Synctest.bubble_for_operation(self)
|
|
359
|
+
return super unless bubble
|
|
360
|
+
|
|
361
|
+
bubble.monitor_owned?(self)
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
def synchronize
|
|
365
|
+
bubble = Synctest.bubble_for_operation(self)
|
|
366
|
+
return super unless bubble
|
|
367
|
+
|
|
368
|
+
entered = false
|
|
369
|
+
begin
|
|
370
|
+
bubble.monitor_enter(self)
|
|
371
|
+
entered = true
|
|
372
|
+
yield
|
|
373
|
+
ensure
|
|
374
|
+
bubble.monitor_exit(self) if entered
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
def mon_synchronize(&block)
|
|
379
|
+
bubble = Synctest.bubble_for_operation(self)
|
|
380
|
+
return super unless bubble
|
|
381
|
+
|
|
382
|
+
synchronize(&block)
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
def wait_for_cond(condition, timeout)
|
|
386
|
+
bubble = Synctest.bubble_for_operation(self)
|
|
387
|
+
return super unless bubble
|
|
388
|
+
|
|
389
|
+
bubble.monitor_wait(self, condition, timeout)
|
|
390
|
+
end
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
module QueuePatch
|
|
394
|
+
def initialize(...)
|
|
395
|
+
super
|
|
396
|
+
Synctest.associate(self)
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
def pop(non_block = false, timeout: nil)
|
|
400
|
+
bubble = Synctest.bubble_for_operation(self)
|
|
401
|
+
return super unless bubble
|
|
402
|
+
|
|
403
|
+
bubble.queue_pop(self, non_block, timeout)
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
def shift(non_block = false, timeout: nil)
|
|
407
|
+
bubble = Synctest.bubble_for_operation(self)
|
|
408
|
+
return super unless bubble
|
|
409
|
+
|
|
410
|
+
bubble.queue_pop(self, non_block, timeout)
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
def deq(non_block = false, timeout: nil)
|
|
414
|
+
bubble = Synctest.bubble_for_operation(self)
|
|
415
|
+
return super unless bubble
|
|
416
|
+
|
|
417
|
+
bubble.queue_pop(self, non_block, timeout)
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
def push(object)
|
|
421
|
+
bubble = Synctest.bubble_for_operation(self)
|
|
422
|
+
return super unless bubble
|
|
423
|
+
|
|
424
|
+
bubble.queue_push(self, object)
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
def enq(object)
|
|
428
|
+
bubble = Synctest.bubble_for_operation(self)
|
|
429
|
+
return super unless bubble
|
|
430
|
+
|
|
431
|
+
bubble.queue_push(self, object)
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
def <<(object)
|
|
435
|
+
bubble = Synctest.bubble_for_operation(self)
|
|
436
|
+
return super unless bubble
|
|
437
|
+
|
|
438
|
+
bubble.queue_push(self, object)
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
def close
|
|
442
|
+
bubble = Synctest.bubble_for_operation(self)
|
|
443
|
+
return super unless bubble
|
|
444
|
+
|
|
445
|
+
bubble.queue_close(self)
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
def clear
|
|
449
|
+
bubble = Synctest.bubble_for_operation(self)
|
|
450
|
+
return super unless bubble
|
|
451
|
+
|
|
452
|
+
bubble.queue_clear(self)
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
def num_waiting
|
|
456
|
+
bubble = Synctest.bubble_for_operation(self)
|
|
457
|
+
return super unless bubble
|
|
458
|
+
|
|
459
|
+
bubble.queue_num_waiting(self, super)
|
|
460
|
+
end
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
module SizedQueuePatch
|
|
464
|
+
def initialize(...)
|
|
465
|
+
super
|
|
466
|
+
Synctest.associate(self)
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
def pop(non_block = false, timeout: nil)
|
|
470
|
+
bubble = Synctest.bubble_for_operation(self)
|
|
471
|
+
return super unless bubble
|
|
472
|
+
|
|
473
|
+
bubble.queue_pop(self, non_block, timeout)
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
def shift(non_block = false, timeout: nil)
|
|
477
|
+
bubble = Synctest.bubble_for_operation(self)
|
|
478
|
+
return super unless bubble
|
|
479
|
+
|
|
480
|
+
bubble.queue_pop(self, non_block, timeout)
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
def deq(non_block = false, timeout: nil)
|
|
484
|
+
bubble = Synctest.bubble_for_operation(self)
|
|
485
|
+
return super unless bubble
|
|
486
|
+
|
|
487
|
+
bubble.queue_pop(self, non_block, timeout)
|
|
488
|
+
end
|
|
489
|
+
|
|
490
|
+
def push(object, non_block = false, timeout: nil)
|
|
491
|
+
bubble = Synctest.bubble_for_operation(self)
|
|
492
|
+
return super unless bubble
|
|
493
|
+
|
|
494
|
+
bubble.sized_queue_push(self, object, non_block, timeout)
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
def enq(object, non_block = false, timeout: nil)
|
|
498
|
+
bubble = Synctest.bubble_for_operation(self)
|
|
499
|
+
return super unless bubble
|
|
500
|
+
|
|
501
|
+
bubble.sized_queue_push(self, object, non_block, timeout)
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
def <<(object)
|
|
505
|
+
bubble = Synctest.bubble_for_operation(self)
|
|
506
|
+
return super unless bubble
|
|
507
|
+
|
|
508
|
+
bubble.sized_queue_push(self, object, false, nil)
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
def close
|
|
512
|
+
bubble = Synctest.bubble_for_operation(self)
|
|
513
|
+
return super unless bubble
|
|
514
|
+
|
|
515
|
+
bubble.queue_close(self)
|
|
516
|
+
end
|
|
517
|
+
|
|
518
|
+
def clear
|
|
519
|
+
bubble = Synctest.bubble_for_operation(self)
|
|
520
|
+
return super unless bubble
|
|
521
|
+
|
|
522
|
+
bubble.queue_clear(self)
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
def max=(maximum)
|
|
526
|
+
bubble = Synctest.bubble_for_operation(self)
|
|
527
|
+
if bubble
|
|
528
|
+
bubble.sized_queue_set_max(self, maximum)
|
|
529
|
+
else
|
|
530
|
+
super
|
|
531
|
+
end
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
def num_waiting
|
|
535
|
+
bubble = Synctest.bubble_for_operation(self)
|
|
536
|
+
return super unless bubble
|
|
537
|
+
|
|
538
|
+
bubble.queue_num_waiting(self, super)
|
|
539
|
+
end
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
module FiberSingletonPatch
|
|
543
|
+
def set_scheduler(scheduler)
|
|
544
|
+
if Synctest.current_bubble
|
|
545
|
+
raise UnsupportedOperationError, "Fiber schedulers are not supported inside a Synctest bubble"
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
super
|
|
549
|
+
end
|
|
550
|
+
end
|
|
551
|
+
|
|
552
|
+
module TimeoutPatch
|
|
553
|
+
def timeout(seconds, exception_class = nil, message = nil, &block)
|
|
554
|
+
bubble = Synctest.current_bubble
|
|
555
|
+
return super unless bubble
|
|
556
|
+
return block.call(seconds) if seconds.nil? || seconds.zero?
|
|
557
|
+
|
|
558
|
+
raise ArgumentError, "Timeout sec must be a non-negative number" if seconds.negative?
|
|
559
|
+
|
|
560
|
+
message ||= "execution expired"
|
|
561
|
+
if exception_class
|
|
562
|
+
Patches.run_timeout_timer(seconds, exception_class, message, &block)
|
|
563
|
+
else
|
|
564
|
+
Timeout::Error.handle_timeout(message) do |exception|
|
|
565
|
+
Patches.run_timeout_timer(seconds, exception, message, &block)
|
|
566
|
+
end
|
|
567
|
+
end
|
|
568
|
+
end
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
class << self
|
|
572
|
+
def install!
|
|
573
|
+
return if @installed
|
|
574
|
+
|
|
575
|
+
Kernel.prepend(KernelPatch)
|
|
576
|
+
Kernel.singleton_class.prepend(KernelSingletonPatch)
|
|
577
|
+
Time.singleton_class.prepend(TimeSingletonPatch)
|
|
578
|
+
Process.singleton_class.prepend(ProcessSingletonPatch)
|
|
579
|
+
Thread.singleton_class.prepend(ThreadSingletonPatch)
|
|
580
|
+
Thread.prepend(ThreadPatch)
|
|
581
|
+
Mutex.prepend(MutexPatch)
|
|
582
|
+
ConditionVariable.prepend(ConditionVariablePatch)
|
|
583
|
+
Monitor.prepend(MonitorPatch)
|
|
584
|
+
Queue.prepend(QueuePatch)
|
|
585
|
+
SizedQueue.prepend(SizedQueuePatch)
|
|
586
|
+
Fiber.singleton_class.prepend(FiberSingletonPatch)
|
|
587
|
+
Timeout.singleton_class.prepend(TimeoutPatch)
|
|
588
|
+
Timeout.prepend(TimeoutPatch)
|
|
589
|
+
|
|
590
|
+
@installed = true
|
|
591
|
+
end
|
|
592
|
+
|
|
593
|
+
def run_timeout_timer(seconds, exception, message)
|
|
594
|
+
target = Thread.current
|
|
595
|
+
timer = Thread.new do
|
|
596
|
+
sleep(seconds)
|
|
597
|
+
target.raise(exception, message)
|
|
598
|
+
end
|
|
599
|
+
yield(seconds)
|
|
600
|
+
ensure
|
|
601
|
+
if timer
|
|
602
|
+
timer.kill if timer.alive?
|
|
603
|
+
timer.join
|
|
604
|
+
end
|
|
605
|
+
end
|
|
606
|
+
end
|
|
607
|
+
end
|
|
608
|
+
end
|
data/lib/synctest/raw.rb
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Synctest
|
|
4
|
+
# Original methods captured before process-wide instrumentation is installed.
|
|
5
|
+
# Bubble internals must use these methods to avoid observing themselves.
|
|
6
|
+
module Raw
|
|
7
|
+
MUTEX_LOCK = Mutex.instance_method(:lock)
|
|
8
|
+
MUTEX_TRY_LOCK = Mutex.instance_method(:try_lock)
|
|
9
|
+
MUTEX_UNLOCK = Mutex.instance_method(:unlock)
|
|
10
|
+
MUTEX_LOCKED = Mutex.instance_method(:locked?)
|
|
11
|
+
MUTEX_OWNED = Mutex.instance_method(:owned?)
|
|
12
|
+
|
|
13
|
+
CONDITION_WAIT = ConditionVariable.instance_method(:wait)
|
|
14
|
+
CONDITION_SIGNAL = ConditionVariable.instance_method(:signal)
|
|
15
|
+
CONDITION_BROADCAST = ConditionVariable.instance_method(:broadcast)
|
|
16
|
+
|
|
17
|
+
MONITOR_ENTER = Monitor.instance_method(:enter)
|
|
18
|
+
MONITOR_TRY_ENTER = Monitor.instance_method(:try_enter)
|
|
19
|
+
MONITOR_EXIT = Monitor.instance_method(:exit)
|
|
20
|
+
MONITOR_OWNED = Monitor.instance_method(:mon_owned?)
|
|
21
|
+
|
|
22
|
+
QUEUE_POP = Queue.instance_method(:pop)
|
|
23
|
+
QUEUE_PUSH = Queue.instance_method(:push)
|
|
24
|
+
QUEUE_CLOSE = Queue.instance_method(:close)
|
|
25
|
+
QUEUE_CLOSED = Queue.instance_method(:closed?)
|
|
26
|
+
QUEUE_CLEAR = Queue.instance_method(:clear)
|
|
27
|
+
QUEUE_LENGTH = Queue.instance_method(:length)
|
|
28
|
+
|
|
29
|
+
SIZED_QUEUE_PUSH = SizedQueue.instance_method(:push)
|
|
30
|
+
SIZED_QUEUE_POP = SizedQueue.instance_method(:pop)
|
|
31
|
+
SIZED_QUEUE_CLOSE = SizedQueue.instance_method(:close)
|
|
32
|
+
SIZED_QUEUE_CLEAR = SizedQueue.instance_method(:clear)
|
|
33
|
+
SIZED_QUEUE_LENGTH = SizedQueue.instance_method(:length)
|
|
34
|
+
SIZED_QUEUE_MAX = SizedQueue.instance_method(:max)
|
|
35
|
+
SIZED_QUEUE_SET_MAX = SizedQueue.instance_method(:max=)
|
|
36
|
+
|
|
37
|
+
THREAD_JOIN = Thread.instance_method(:join)
|
|
38
|
+
THREAD_VALUE = Thread.instance_method(:value)
|
|
39
|
+
THREAD_KILL = Thread.instance_method(:kill)
|
|
40
|
+
THREAD_RAISE = Thread.instance_method(:raise)
|
|
41
|
+
THREAD_WAKEUP = Thread.instance_method(:wakeup)
|
|
42
|
+
THREAD_RUN = Thread.instance_method(:run)
|
|
43
|
+
|
|
44
|
+
CLOCK_GETTIME = Process.method(:clock_gettime)
|
|
45
|
+
end
|
|
46
|
+
end
|