polyphony 0.24 → 0.25
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 +4 -4
- data/CHANGELOG.md +6 -0
- data/Gemfile.lock +1 -1
- data/TODO.md +12 -8
- data/docs/README.md +2 -2
- data/docs/summary.md +3 -3
- data/docs/technical-overview/concurrency.md +4 -6
- data/docs/technical-overview/design-principles.md +8 -8
- data/docs/technical-overview/exception-handling.md +1 -1
- data/examples/core/{01-spinning-up-coprocesses.rb → 01-spinning-up-fibers.rb} +1 -1
- data/examples/core/{02-awaiting-coprocesses.rb → 02-awaiting-fibers.rb} +3 -3
- data/examples/core/xx-erlang-style-genserver.rb +10 -10
- data/examples/core/xx-extended_fibers.rb +150 -0
- data/examples/core/xx-sleeping.rb +9 -0
- data/examples/core/xx-supervisors.rb +1 -1
- data/examples/interfaces/pg_pool.rb +3 -3
- data/examples/performance/mem-usage.rb +19 -4
- data/examples/performance/thread-vs-fiber/polyphony_server.rb +1 -5
- data/ext/gyro/gyro.c +9 -15
- data/lib/polyphony/core/cancel_scope.rb +0 -2
- data/lib/polyphony/core/exceptions.rb +2 -2
- data/lib/polyphony/core/global_api.rb +7 -8
- data/lib/polyphony/core/supervisor.rb +25 -31
- data/lib/polyphony/extensions/core.rb +4 -78
- data/lib/polyphony/extensions/fiber.rb +166 -0
- data/lib/polyphony/extensions/io.rb +2 -1
- data/lib/polyphony/version.rb +1 -1
- data/lib/polyphony.rb +6 -8
- data/test/test_async.rb +2 -2
- data/test/test_cancel_scope.rb +6 -6
- data/test/test_fiber.rb +382 -0
- data/test/test_global_api.rb +49 -50
- data/test/test_gyro.rb +1 -1
- data/test/test_io.rb +30 -29
- data/test/test_kernel.rb +2 -2
- data/test/test_signal.rb +1 -1
- data/test/test_supervisor.rb +27 -27
- data/test/test_timer.rb +2 -2
- metadata +7 -7
- data/examples/core/04-no-auto-run.rb +0 -16
- data/lib/polyphony/core/coprocess.rb +0 -168
- data/test/test_coprocess.rb +0 -440
data/test/test_coprocess.rb
DELETED
@@ -1,440 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'helper'
|
4
|
-
|
5
|
-
class CoprocessTest < MiniTest::Test
|
6
|
-
def test_that_root_fiber_has_associated_coprocess
|
7
|
-
assert_equal(Fiber.current, Polyphony::Coprocess.current.fiber)
|
8
|
-
assert_equal(Polyphony::Coprocess.current, Fiber.current.coprocess)
|
9
|
-
end
|
10
|
-
|
11
|
-
def test_that_new_coprocess_starts_in_suspended_state
|
12
|
-
result = nil
|
13
|
-
coproc = Polyphony::Coprocess.new { result = 42 }
|
14
|
-
assert_nil(result)
|
15
|
-
coproc.await
|
16
|
-
assert_equal(42, result)
|
17
|
-
ensure
|
18
|
-
coproc&.stop
|
19
|
-
end
|
20
|
-
|
21
|
-
def test_that_new_coprocess_runs_on_different_fiber
|
22
|
-
coproc = Polyphony::Coprocess.new { Fiber.current }
|
23
|
-
fiber = coproc.await
|
24
|
-
assert(fiber != Fiber.current)
|
25
|
-
ensure
|
26
|
-
coproc&.stop
|
27
|
-
end
|
28
|
-
|
29
|
-
def test_that_await_blocks_until_coprocess_is_done
|
30
|
-
result = nil
|
31
|
-
coproc = Polyphony::Coprocess.new do
|
32
|
-
snooze
|
33
|
-
result = 42
|
34
|
-
end
|
35
|
-
coproc.await
|
36
|
-
assert_equal(42, result)
|
37
|
-
ensure
|
38
|
-
coproc&.stop
|
39
|
-
end
|
40
|
-
|
41
|
-
def test_that_await_returns_the_coprocess_return_value
|
42
|
-
coproc = Polyphony::Coprocess.new { %i[foo bar] }
|
43
|
-
assert_equal(%i[foo bar], coproc.await)
|
44
|
-
ensure
|
45
|
-
coproc&.stop
|
46
|
-
end
|
47
|
-
|
48
|
-
def test_that_await_raises_error_raised_by_coprocess
|
49
|
-
result = nil
|
50
|
-
coproc = Polyphony::Coprocess.new { raise 'foo' }
|
51
|
-
begin
|
52
|
-
result = coproc.await
|
53
|
-
rescue Exception => e
|
54
|
-
result = { error: e }
|
55
|
-
end
|
56
|
-
assert_kind_of(Hash, result)
|
57
|
-
assert_kind_of(RuntimeError, result[:error])
|
58
|
-
ensure
|
59
|
-
coproc&.stop
|
60
|
-
end
|
61
|
-
|
62
|
-
def test_that_running_coprocess_can_be_cancelled
|
63
|
-
result = []
|
64
|
-
error = nil
|
65
|
-
coproc = Polyphony::Coprocess.new do
|
66
|
-
result << 1
|
67
|
-
2.times { snooze }
|
68
|
-
result << 2
|
69
|
-
end.run
|
70
|
-
defer { coproc.cancel! }
|
71
|
-
assert_equal(0, result.size)
|
72
|
-
begin
|
73
|
-
coproc.await
|
74
|
-
rescue Polyphony::Cancel => e
|
75
|
-
error = e
|
76
|
-
end
|
77
|
-
assert_equal(1, result.size)
|
78
|
-
assert_equal(1, result[0])
|
79
|
-
assert_kind_of(Polyphony::Cancel, error)
|
80
|
-
ensure
|
81
|
-
coproc&.stop
|
82
|
-
end
|
83
|
-
|
84
|
-
def test_that_running_coprocess_can_be_interrupted
|
85
|
-
# that is, stopped without exception
|
86
|
-
result = []
|
87
|
-
coproc = Polyphony::Coprocess.new do
|
88
|
-
result << 1
|
89
|
-
2.times { snooze }
|
90
|
-
result << 2
|
91
|
-
3
|
92
|
-
end.run
|
93
|
-
defer { coproc.stop(42) }
|
94
|
-
|
95
|
-
await_result = coproc.await
|
96
|
-
assert_equal(1, result.size)
|
97
|
-
assert_equal(42, await_result)
|
98
|
-
ensure
|
99
|
-
coproc&.stop
|
100
|
-
end
|
101
|
-
|
102
|
-
def test_that_coprocess_can_be_awaited
|
103
|
-
result = nil
|
104
|
-
cp2 = nil
|
105
|
-
cp1 = spin do
|
106
|
-
cp2 = Polyphony::Coprocess.new do
|
107
|
-
snooze
|
108
|
-
42
|
109
|
-
end
|
110
|
-
result = cp2.await
|
111
|
-
end
|
112
|
-
suspend
|
113
|
-
assert_equal(42, result)
|
114
|
-
ensure
|
115
|
-
cp1&.stop
|
116
|
-
cp2&.stop
|
117
|
-
end
|
118
|
-
|
119
|
-
def test_that_coprocess_can_be_stopped
|
120
|
-
result = nil
|
121
|
-
coproc = spin do
|
122
|
-
snooze
|
123
|
-
result = 42
|
124
|
-
end
|
125
|
-
defer { coproc.interrupt }
|
126
|
-
suspend
|
127
|
-
assert_nil(result)
|
128
|
-
ensure
|
129
|
-
coproc&.stop
|
130
|
-
end
|
131
|
-
|
132
|
-
def test_that_coprocess_can_be_cancelled
|
133
|
-
result = nil
|
134
|
-
coproc = spin do
|
135
|
-
snooze
|
136
|
-
result = 42
|
137
|
-
rescue Polyphony::Cancel => e
|
138
|
-
result = e
|
139
|
-
end
|
140
|
-
defer { coproc.cancel! }
|
141
|
-
|
142
|
-
suspend
|
143
|
-
|
144
|
-
assert_kind_of(Polyphony::Cancel, result)
|
145
|
-
assert_kind_of(Polyphony::Cancel, coproc.result)
|
146
|
-
assert_nil(coproc.alive?)
|
147
|
-
ensure
|
148
|
-
coproc&.stop
|
149
|
-
end
|
150
|
-
|
151
|
-
def test_that_inner_coprocess_can_be_interrupted
|
152
|
-
result = nil
|
153
|
-
cp2 = nil
|
154
|
-
cp1 = spin do
|
155
|
-
cp2 = spin do
|
156
|
-
snooze
|
157
|
-
result = 42
|
158
|
-
end
|
159
|
-
cp2.await
|
160
|
-
result && result += 1
|
161
|
-
end
|
162
|
-
defer { cp1.interrupt }
|
163
|
-
suspend
|
164
|
-
assert_nil(result)
|
165
|
-
assert_nil(cp1.alive?)
|
166
|
-
assert_nil(cp2.alive?)
|
167
|
-
ensure
|
168
|
-
cp1&.stop
|
169
|
-
cp2&.stop
|
170
|
-
end
|
171
|
-
|
172
|
-
def test_that_inner_coprocess_can_interrupt_outer_coprocess
|
173
|
-
result, cp2 = nil
|
174
|
-
|
175
|
-
cp1 = spin do
|
176
|
-
cp2 = spin do
|
177
|
-
defer { cp1.interrupt }
|
178
|
-
snooze
|
179
|
-
snooze
|
180
|
-
result = 42
|
181
|
-
end
|
182
|
-
cp2.await
|
183
|
-
result && result += 1
|
184
|
-
end
|
185
|
-
|
186
|
-
suspend
|
187
|
-
|
188
|
-
assert_nil(result)
|
189
|
-
assert_nil(cp1.alive?)
|
190
|
-
assert_nil(cp2.alive?)
|
191
|
-
ensure
|
192
|
-
cp1&.stop
|
193
|
-
cp2&.stop
|
194
|
-
end
|
195
|
-
|
196
|
-
def test_alive?
|
197
|
-
counter = 0
|
198
|
-
coproc = spin do
|
199
|
-
3.times do
|
200
|
-
snooze
|
201
|
-
counter += 1
|
202
|
-
end
|
203
|
-
end
|
204
|
-
|
205
|
-
assert(coproc.alive?)
|
206
|
-
snooze
|
207
|
-
assert(coproc.alive?)
|
208
|
-
snooze while counter < 3
|
209
|
-
assert(!coproc.alive?)
|
210
|
-
ensure
|
211
|
-
coproc&.stop
|
212
|
-
end
|
213
|
-
|
214
|
-
def test_coprocess_exception_propagation
|
215
|
-
# error is propagated to calling coprocess
|
216
|
-
raised_error = nil
|
217
|
-
spin do
|
218
|
-
spin do
|
219
|
-
raise 'foo'
|
220
|
-
end
|
221
|
-
snooze # allow nested coprocess to run before finishing
|
222
|
-
end
|
223
|
-
suspend
|
224
|
-
rescue Exception => e
|
225
|
-
raised_error = e
|
226
|
-
ensure
|
227
|
-
assert(raised_error)
|
228
|
-
assert_equal('foo', raised_error.message)
|
229
|
-
end
|
230
|
-
|
231
|
-
def test_that_coprocess_can_be_cancelled_before_first_scheduling
|
232
|
-
buffer = []
|
233
|
-
coproc = spin { buffer << 1 }
|
234
|
-
coproc.stop
|
235
|
-
|
236
|
-
snooze
|
237
|
-
assert_nil coproc.alive?
|
238
|
-
assert_equal [], buffer
|
239
|
-
end
|
240
|
-
|
241
|
-
def test_exception_propagation_for_orphan_fiber
|
242
|
-
raised_error = nil
|
243
|
-
spin do
|
244
|
-
spin do
|
245
|
-
snooze
|
246
|
-
raise 'bar'
|
247
|
-
end
|
248
|
-
end
|
249
|
-
suspend
|
250
|
-
rescue Exception => e
|
251
|
-
raised_error = e
|
252
|
-
ensure
|
253
|
-
assert(raised_error)
|
254
|
-
assert_equal('bar', raised_error.message)
|
255
|
-
end
|
256
|
-
|
257
|
-
def test_await_multiple_coprocesses
|
258
|
-
cp1 = spin { sleep 0.01; :foo }
|
259
|
-
cp2 = spin { sleep 0.01; :bar }
|
260
|
-
cp3 = spin { sleep 0.01; :baz }
|
261
|
-
|
262
|
-
result = Polyphony::Coprocess.await(cp1, cp2, cp3)
|
263
|
-
assert_equal %i{foo bar baz}, result
|
264
|
-
end
|
265
|
-
|
266
|
-
def test_join_multiple_coprocesses
|
267
|
-
cp1 = spin { sleep 0.01; :foo }
|
268
|
-
cp2 = spin { sleep 0.01; :bar }
|
269
|
-
cp3 = spin { sleep 0.01; :baz }
|
270
|
-
|
271
|
-
result = Polyphony::Coprocess.join(cp1, cp2, cp3)
|
272
|
-
assert_equal %i{foo bar baz}, result
|
273
|
-
end
|
274
|
-
|
275
|
-
def test_caller
|
276
|
-
location = /^#{__FILE__}:#{__LINE__ + 1}/
|
277
|
-
cp = spin do
|
278
|
-
sleep 0.01
|
279
|
-
end
|
280
|
-
snooze
|
281
|
-
|
282
|
-
caller = cp.caller
|
283
|
-
assert_match location, caller[0]
|
284
|
-
end
|
285
|
-
|
286
|
-
def test_location
|
287
|
-
location = /^#{__FILE__}:#{__LINE__ + 1}/
|
288
|
-
cp = spin do
|
289
|
-
sleep 0.01
|
290
|
-
end
|
291
|
-
snooze
|
292
|
-
|
293
|
-
assert cp.location =~ location
|
294
|
-
end
|
295
|
-
|
296
|
-
def test_when_done
|
297
|
-
flag = nil
|
298
|
-
values = []
|
299
|
-
coproc = spin do
|
300
|
-
snooze until flag
|
301
|
-
end
|
302
|
-
coproc.when_done { values << 42 }
|
303
|
-
|
304
|
-
snooze
|
305
|
-
assert values.empty?
|
306
|
-
snooze
|
307
|
-
flag = true
|
308
|
-
assert values.empty?
|
309
|
-
assert coproc.alive?
|
310
|
-
|
311
|
-
snooze
|
312
|
-
assert_equal [42], values
|
313
|
-
assert !coproc.alive?
|
314
|
-
end
|
315
|
-
|
316
|
-
def test_resume
|
317
|
-
values = []
|
318
|
-
coproc = spin do
|
319
|
-
values << 1
|
320
|
-
x = suspend
|
321
|
-
values << x
|
322
|
-
suspend
|
323
|
-
values << 3
|
324
|
-
end
|
325
|
-
snooze
|
326
|
-
assert_equal [1], values
|
327
|
-
|
328
|
-
coproc.resume 2
|
329
|
-
assert_equal [1, 2], values
|
330
|
-
|
331
|
-
coproc.resume
|
332
|
-
assert_equal [1, 2, 3], values
|
333
|
-
|
334
|
-
assert !coproc.alive?
|
335
|
-
end
|
336
|
-
|
337
|
-
def test_interrupt
|
338
|
-
coproc = spin do
|
339
|
-
sleep 1
|
340
|
-
:foo
|
341
|
-
end
|
342
|
-
|
343
|
-
snooze
|
344
|
-
assert coproc.alive?
|
345
|
-
|
346
|
-
coproc.interrupt :bar
|
347
|
-
assert !coproc.alive?
|
348
|
-
|
349
|
-
assert_equal :bar, coproc.result
|
350
|
-
end
|
351
|
-
|
352
|
-
def test_cancel
|
353
|
-
error = nil
|
354
|
-
coproc = spin do
|
355
|
-
sleep 1
|
356
|
-
:foo
|
357
|
-
end
|
358
|
-
|
359
|
-
snooze
|
360
|
-
coproc.cancel!
|
361
|
-
rescue Polyphony::Cancel => e
|
362
|
-
# cancel error should bubble up
|
363
|
-
error = e
|
364
|
-
ensure
|
365
|
-
assert error
|
366
|
-
assert !coproc.alive?
|
367
|
-
end
|
368
|
-
|
369
|
-
def test_current
|
370
|
-
assert_equal Fiber.root.coprocess, Polyphony::Coprocess.current
|
371
|
-
|
372
|
-
value = nil
|
373
|
-
coproc = spin do
|
374
|
-
value = :ok if Polyphony::Coprocess.current == coproc
|
375
|
-
end
|
376
|
-
|
377
|
-
snooze
|
378
|
-
assert_equal :ok, value
|
379
|
-
end
|
380
|
-
end
|
381
|
-
|
382
|
-
class MailboxTest < MiniTest::Test
|
383
|
-
def test_that_coprocess_can_receive_messages
|
384
|
-
msgs = []
|
385
|
-
coproc = spin { loop { msgs << receive } }
|
386
|
-
|
387
|
-
snooze # allow coproc to start
|
388
|
-
|
389
|
-
3.times do |i|
|
390
|
-
coproc << i
|
391
|
-
snooze
|
392
|
-
end
|
393
|
-
|
394
|
-
assert_equal([0, 1, 2], msgs)
|
395
|
-
ensure
|
396
|
-
coproc&.stop
|
397
|
-
end
|
398
|
-
|
399
|
-
def test_that_multiple_messages_sent_at_once_arrive_in_order
|
400
|
-
msgs = []
|
401
|
-
coproc = spin { loop { msgs << receive } }
|
402
|
-
|
403
|
-
snooze # allow coproc to start
|
404
|
-
|
405
|
-
3.times { |i| coproc << i }
|
406
|
-
|
407
|
-
snooze
|
408
|
-
|
409
|
-
assert_equal([0, 1, 2], msgs)
|
410
|
-
ensure
|
411
|
-
coproc&.stop
|
412
|
-
end
|
413
|
-
|
414
|
-
def test_that_sent_message_are_queued_before_calling_receive
|
415
|
-
buffer = []
|
416
|
-
receiver = spin { suspend; 3.times { buffer << receive } }
|
417
|
-
sender = spin { 3.times { |i| receiver << (i * 10) } }
|
418
|
-
|
419
|
-
sender.await
|
420
|
-
receiver.schedule
|
421
|
-
receiver.await
|
422
|
-
|
423
|
-
assert_equal [0, 10, 20], buffer
|
424
|
-
end
|
425
|
-
|
426
|
-
def test_map_and_count
|
427
|
-
assert_equal 1, Polyphony::Coprocess.count
|
428
|
-
map = { Fiber.current => Polyphony::Coprocess.current }
|
429
|
-
assert_equal map, Polyphony::Coprocess.map
|
430
|
-
|
431
|
-
cp = spin { sleep 1 }
|
432
|
-
snooze
|
433
|
-
assert_equal 2, Polyphony::Coprocess.count
|
434
|
-
assert_equal cp, Polyphony::Coprocess.map[cp.fiber]
|
435
|
-
|
436
|
-
cp.stop
|
437
|
-
snooze
|
438
|
-
assert_equal 1, Polyphony::Coprocess.count
|
439
|
-
end
|
440
|
-
end
|