polyphony 0.28 → 0.29

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +0 -4
  3. data/CHANGELOG.md +12 -0
  4. data/Gemfile.lock +1 -1
  5. data/LICENSE +1 -1
  6. data/README.md +23 -21
  7. data/Rakefile +2 -0
  8. data/TODO.md +0 -3
  9. data/docs/_includes/prevnext.html +17 -0
  10. data/docs/_layouts/default.html +106 -0
  11. data/docs/_sass/custom/custom.scss +21 -0
  12. data/docs/faq.md +13 -10
  13. data/docs/getting-started/installing.md +2 -0
  14. data/docs/getting-started/tutorial.md +5 -3
  15. data/docs/index.md +4 -5
  16. data/docs/technical-overview/concurrency.md +21 -19
  17. data/docs/technical-overview/design-principles.md +12 -20
  18. data/docs/technical-overview/exception-handling.md +70 -1
  19. data/docs/technical-overview/extending.md +1 -0
  20. data/docs/technical-overview/fiber-scheduling.md +109 -88
  21. data/docs/user-guide/all-about-timers.md +126 -0
  22. data/docs/user-guide/web-server.md +2 -2
  23. data/docs/user-guide.md +1 -1
  24. data/examples/core/xx-deferring-an-operation.rb +2 -2
  25. data/examples/core/xx-sleep-forever.rb +9 -0
  26. data/examples/core/xx-snooze-starve.rb +16 -0
  27. data/examples/core/xx-spin_error_backtrace.rb +1 -1
  28. data/examples/core/xx-trace.rb +1 -2
  29. data/examples/core/xx-worker-thread.rb +30 -0
  30. data/examples/io/xx-happy-eyeballs.rb +37 -0
  31. data/ext/gyro/gyro.c +8 -3
  32. data/ext/gyro/gyro.h +7 -1
  33. data/ext/gyro/queue.c +35 -3
  34. data/ext/gyro/selector.c +31 -2
  35. data/ext/gyro/thread.c +18 -16
  36. data/lib/polyphony/core/global_api.rb +0 -1
  37. data/lib/polyphony/core/thread_pool.rb +5 -0
  38. data/lib/polyphony/core/throttler.rb +0 -1
  39. data/lib/polyphony/extensions/fiber.rb +14 -3
  40. data/lib/polyphony/extensions/thread.rb +16 -4
  41. data/lib/polyphony/irb.rb +7 -1
  42. data/lib/polyphony/trace.rb +44 -11
  43. data/lib/polyphony/version.rb +1 -1
  44. data/lib/polyphony.rb +1 -0
  45. data/test/helper.rb +1 -3
  46. data/test/test_async.rb +1 -1
  47. data/test/test_cancel_scope.rb +3 -3
  48. data/test/test_fiber.rb +157 -54
  49. data/test/test_global_api.rb +51 -1
  50. data/test/test_gyro.rb +4 -156
  51. data/test/test_io.rb +1 -1
  52. data/test/test_supervisor.rb +2 -2
  53. data/test/test_thread.rb +72 -1
  54. data/test/test_thread_pool.rb +6 -2
  55. data/test/test_throttler.rb +7 -5
  56. data/test/test_trace.rb +6 -6
  57. metadata +10 -5
  58. data/examples/core/xx-extended_fibers.rb +0 -150
  59. data/examples/core/xx-mt-scheduler.rb +0 -349
data/test/test_fiber.rb CHANGED
@@ -25,6 +25,62 @@ class FiberTest < MiniTest::Test
25
25
  f&.stop
26
26
  end
27
27
 
28
+ def test_schedule
29
+ values = []
30
+ fibers = (0..2).map { |i| spin { suspend; values << i } }
31
+ snooze
32
+
33
+ fibers[0].schedule
34
+ assert_equal [], values
35
+
36
+ snooze
37
+
38
+ assert_equal [0], values
39
+ assert_equal :dead, fibers[0].state
40
+
41
+ fibers[1].schedule
42
+ fibers[2].schedule
43
+
44
+ assert_equal [0], values
45
+ snooze
46
+ assert_equal [0, 1, 2], values
47
+ end
48
+
49
+ def test_cross_thread_schedule
50
+ buffer = []
51
+ worker_fiber = nil
52
+ async = Gyro::Async.new
53
+ worker = Thread.new do
54
+ worker_fiber = Fiber.current
55
+ async.signal!
56
+ suspend
57
+ buffer << :foo
58
+ end
59
+
60
+ async.await
61
+ assert worker_fiber
62
+ worker_fiber.schedule
63
+ worker.join
64
+ assert_equal [:foo], buffer
65
+ ensure
66
+ worker&.kill
67
+ end
68
+
69
+ def test_ev_loop_anti_starve_mechanism
70
+ async = Gyro::Async.new
71
+ t = Thread.new do
72
+ f = spin_loop { snooze }
73
+ sleep 0.001
74
+ async.signal!(:foo)
75
+ end
76
+
77
+ result = move_on_after(0.05) { async.await }
78
+
79
+ assert_equal :foo, result
80
+ ensure
81
+ t.kill if t.alive?
82
+ end
83
+
28
84
  def test_tag
29
85
  assert_equal :main, Fiber.current.tag
30
86
  Fiber.current.tag = :foo
@@ -63,7 +119,7 @@ class FiberTest < MiniTest::Test
63
119
  2.times { snooze }
64
120
  result << 2
65
121
  end
66
- defer { f.raise }
122
+ spin { f.raise }
67
123
  assert_equal 0, result.size
68
124
  begin
69
125
  f.await
@@ -88,7 +144,7 @@ class FiberTest < MiniTest::Test
88
144
  2.times { snooze }
89
145
  result << 2
90
146
  end
91
- defer { f.raise MyError }
147
+ spin { f.raise MyError }
92
148
  assert_equal 0, result.size
93
149
  begin
94
150
  f.await
@@ -110,7 +166,7 @@ class FiberTest < MiniTest::Test
110
166
  2.times { snooze }
111
167
  result << 2
112
168
  end
113
- defer { f.raise(MyError, 'foo') }
169
+ spin { f.raise(MyError, 'foo') }
114
170
  assert_equal 0, result.size
115
171
  begin
116
172
  f.await
@@ -133,7 +189,7 @@ class FiberTest < MiniTest::Test
133
189
  2.times { snooze }
134
190
  result << 2
135
191
  end
136
- defer { f.raise 'foo' }
192
+ spin { f.raise 'foo' }
137
193
  assert_equal 0, result.size
138
194
  begin
139
195
  f.await
@@ -156,7 +212,7 @@ class FiberTest < MiniTest::Test
156
212
  2.times { snooze }
157
213
  result << 2
158
214
  end
159
- defer { f.raise MyError.new('bar') }
215
+ spin { f.raise MyError.new('bar') }
160
216
  assert_equal 0, result.size
161
217
  begin
162
218
  f.await
@@ -179,7 +235,7 @@ class FiberTest < MiniTest::Test
179
235
  2.times { snooze }
180
236
  result << 2
181
237
  end
182
- defer { f.cancel! }
238
+ spin { f.cancel! }
183
239
  assert_equal 0, result.size
184
240
  begin
185
241
  f.await
@@ -202,7 +258,7 @@ class FiberTest < MiniTest::Test
202
258
  result << 2
203
259
  3
204
260
  end
205
- defer { f.interrupt(42) }
261
+ spin { f.interrupt(42) }
206
262
 
207
263
  await_result = f.await
208
264
  assert_equal 1, result.size
@@ -220,7 +276,7 @@ class FiberTest < MiniTest::Test
220
276
  result << 2
221
277
  3
222
278
  end
223
- defer { f.stop(42) }
279
+ spin { f.stop(42) }
224
280
 
225
281
  await_result = f.await
226
282
  assert_equal 1, result.size
@@ -253,7 +309,7 @@ class FiberTest < MiniTest::Test
253
309
  f2.await
254
310
  result && result += 1
255
311
  end
256
- defer { f2.interrupt }
312
+ spin { f2.interrupt }
257
313
  suspend
258
314
  assert_nil result
259
315
  assert_equal :dead, f1.state
@@ -337,6 +393,7 @@ class FiberTest < MiniTest::Test
337
393
  end
338
394
 
339
395
  def test_select_from_multiple_fibers
396
+ sleep 0
340
397
  buffer = []
341
398
  f1 = spin { sleep 0.01; buffer << :foo; :foo }
342
399
  f2 = spin { sleep 0.03; buffer << :bar; :bar }
@@ -388,51 +445,6 @@ class FiberTest < MiniTest::Test
388
445
  assert_equal [42], values
389
446
  assert !f.running?
390
447
  end
391
- end
392
-
393
- class MailboxTest < MiniTest::Test
394
- def test_that_fiber_can_receive_messages
395
- msgs = []
396
- f = spin { loop { msgs << receive } }
397
-
398
- snooze # allow fiber to start
399
-
400
- 3.times do |i|
401
- f << i
402
- snooze
403
- end
404
-
405
- assert_equal [0, 1, 2], msgs
406
- ensure
407
- f&.stop
408
- end
409
-
410
- def test_that_multiple_messages_sent_at_once_arrive_in_order
411
- msgs = []
412
- f = spin { loop { msgs << receive } }
413
-
414
- snooze # allow coproc to start
415
-
416
- 3.times { |i| f << i }
417
-
418
- snooze
419
-
420
- assert_equal [0, 1, 2], msgs
421
- ensure
422
- f&.stop
423
- end
424
-
425
- def test_that_sent_message_are_queued_before_calling_receive
426
- buffer = []
427
- receiver = spin { suspend; 3.times { buffer << receive } }
428
- sender = spin { 3.times { |i| receiver << (i * 10) } }
429
-
430
- sender.await
431
- receiver.schedule
432
- receiver.await
433
-
434
- assert_equal [0, 10, 20], buffer
435
- end
436
448
 
437
449
  def test_list_and_count
438
450
  assert_equal 1, Fiber.count
@@ -510,4 +522,95 @@ class MailboxTest < MiniTest::Test
510
522
  assert_nil parent_error
511
523
  assert_kind_of Interrupt, main_fiber_error
512
524
  end
525
+
526
+ def test_signal_exception_in_fiber
527
+ parent_error = nil
528
+ main_fiber_error = nil
529
+ f2 = nil
530
+ f1 = spin do
531
+ f2 = spin { raise SignalException.new('HUP') }
532
+ suspend
533
+ rescue Exception => parent_error
534
+ end
535
+
536
+ begin
537
+ suspend
538
+ rescue Exception => main_fiber_error
539
+ end
540
+
541
+ assert_nil parent_error
542
+ assert_kind_of SignalException, main_fiber_error
543
+ end
544
+ end
545
+
546
+ class MailboxTest < MiniTest::Test
547
+ def test_that_fiber_can_receive_messages
548
+ msgs = []
549
+ f = spin { loop { msgs << receive } }
550
+
551
+ snooze # allow fiber to start
552
+
553
+ 3.times do |i|
554
+ f << i
555
+ snooze
556
+ end
557
+
558
+ assert_equal [0, 1, 2], msgs
559
+ ensure
560
+ f&.stop
561
+ end
562
+
563
+ def test_that_multiple_messages_sent_at_once_arrive_in_order
564
+ msgs = []
565
+ f = spin { loop { msgs << receive } }
566
+
567
+ snooze # allow coproc to start
568
+
569
+ 3.times { |i| f << i }
570
+
571
+ snooze
572
+
573
+ assert_equal [0, 1, 2], msgs
574
+ ensure
575
+ f&.stop
576
+ end
577
+
578
+ def test_that_sent_message_are_queued_before_calling_receive
579
+ buffer = []
580
+ receiver = spin { suspend; 3.times { buffer << receive } }
581
+ sender = spin { 3.times { |i| receiver << (i * 10) } }
582
+
583
+ sender.await
584
+ receiver.schedule
585
+ receiver.await
586
+
587
+ assert_equal [0, 10, 20], buffer
588
+ end
589
+
590
+ def test_cross_thread_send_receive
591
+ skip "There's currently a race condition in cross-thread send/receive. We're going to rewrite it in C"
592
+ ping_receive_buffer = []
593
+ pong_receive_buffer = []
594
+ pong = Thread.new do
595
+ loop do
596
+ peer, data = receive
597
+ pong_receive_buffer << data
598
+ peer << 'pong'
599
+ end
600
+ end
601
+
602
+ ping = Thread.new do
603
+ 3.times do
604
+ pong << [Fiber.current, 'ping']
605
+ data = receive
606
+ ping_receive_buffer << data
607
+ end
608
+ end
609
+
610
+ ping.join
611
+ pong.kill
612
+
613
+ assert_equal %w{pong pong pong}, ping_receive_buffer
614
+ assert_equal %w{ping ping ping}, pong_receive_buffer
615
+ end
513
616
  end
@@ -36,7 +36,7 @@ class SpinTest < MiniTest::Test
36
36
  sleep(1)
37
37
  42
38
38
  end
39
- defer { fiber.interrupt }
39
+ spin { fiber.interrupt }
40
40
  suspend
41
41
  assert_nil fiber.result
42
42
  end
@@ -321,4 +321,54 @@ class MoveOnAfterTest < MiniTest::Test
321
321
  f.stop
322
322
  assert !f.running?
323
323
  end
324
+
325
+ def test_snooze
326
+ values = []
327
+ 3.times.map do |i|
328
+ spin do
329
+ 3.times do
330
+ snooze
331
+ values << i
332
+ end
333
+ suspend
334
+ end
335
+ end
336
+ suspend
337
+
338
+ assert_equal [0, 1, 2, 0, 1, 2, 0, 1, 2], values
339
+ end
340
+
341
+ def test_defer
342
+ values = []
343
+ spin { values << 1 }
344
+ spin { values << 2 }
345
+ spin { values << 3 }
346
+ suspend
347
+
348
+ assert_equal [1, 2, 3], values
349
+ end
350
+
351
+ def test_suspend
352
+ values = []
353
+ spin do
354
+ values << :foo
355
+ suspend
356
+ end
357
+ suspend
358
+
359
+ assert_equal [:foo], values
360
+ end
361
+
362
+ def test_schedule_and_suspend
363
+ values = []
364
+ 3.times.map do |i|
365
+ spin do
366
+ values << i
367
+ suspend
368
+ end
369
+ end
370
+ suspend
371
+
372
+ assert_equal [0, 1, 2], values
373
+ end
324
374
  end
data/test/test_gyro.rb CHANGED
@@ -3,175 +3,23 @@
3
3
  require_relative 'helper'
4
4
 
5
5
  class GyroTest < MiniTest::Test
6
- def test_fiber_state
7
- assert_equal :running, Fiber.current.state
8
-
9
- f = Fiber.new {}
10
-
11
- assert_equal :waiting, f.state
12
- f.resume
13
- assert_equal :dead, f.state
14
-
15
- f = Fiber.new { }
16
- f.schedule
17
- assert_equal :runnable, f.state
18
- snooze
19
- assert_equal :dead, f.state
20
- end
21
-
22
- def test_schedule
23
- values = []
24
- fibers = 3.times.map { |i| Fiber.new { values << i } }
25
- fibers[0].schedule
26
-
27
- assert_equal [], values
28
- snooze
29
- assert_equal [0], values
30
-
31
- fibers[1].schedule
32
- fibers[2].schedule
33
-
34
- assert_equal [0], values
35
- snooze
36
- assert_equal [0, 1, 2], values
37
- end
38
-
39
- def test_that_run_loop_returns_immediately_if_no_watchers
40
- t0 = Time.now
41
- suspend
42
- t1 = Time.now
43
- assert((t1 - t0) < 0.01)
44
- end
45
-
46
- def test_defer
47
- values = []
48
- defer { values << 1 }
49
- defer { values << 2 }
50
- defer { values << 3 }
51
- suspend
52
-
53
- assert_equal [1, 2, 3], values
54
- end
55
-
56
- def test_schedule
57
- values = []
58
- f = Fiber.new do
59
- values << :foo
60
- # We *have* to suspend the fiber in order to yield to the reactor,
61
- # otherwise control will transfer back to root fiber.
62
- suspend
63
- end
64
- assert_equal [], values
65
- f.schedule
66
- suspend
67
-
68
- assert_equal [:foo], values
69
- end
70
-
71
- def test_suspend
72
- values = []
73
- Fiber.new do
74
- values << :foo
75
- suspend
76
- end.schedule
77
- suspend
78
-
79
- assert_equal [:foo], values
80
- end
81
-
82
- def test_schedule_and_suspend
83
- values = []
84
- 3.times.map do |i|
85
- Fiber.new do
86
- values << i
87
- suspend
88
- end.schedule
89
- end
90
- suspend
91
-
92
- assert_equal [0, 1, 2], values
93
- end
94
-
95
- def test_snooze
96
- values = []
97
- 3.times.map do |i|
98
- Fiber.new do
99
- 3.times do
100
- snooze
101
- values << i
102
- end
103
- suspend
104
- end.schedule
105
- end
106
- suspend
107
-
108
- assert_equal [0, 1, 2, 0, 1, 2, 0, 1, 2], values
109
- end
110
-
111
6
  def test_break
112
7
  skip "break is still not implemented for new scheduler"
113
8
  values = []
114
- Fiber.new do
9
+ Fiber.spin do
115
10
  values << :foo
116
11
  snooze
117
12
  # here will never be reached
118
13
  values << :bar
119
14
  suspend
120
- end.schedule
15
+ end
121
16
 
122
- Fiber.new do
17
+ Fiber.spin do
123
18
  Gyro.break!
124
- end.schedule
19
+ end
125
20
 
126
21
  suspend
127
22
 
128
23
  assert_equal [:foo], values
129
24
  end
130
-
131
- def test_reset
132
- values = []
133
- f1 = Fiber.new do
134
- values << :foo
135
- snooze
136
- values << :bar
137
- suspend
138
- end.schedule
139
-
140
- f2 = Fiber.new do
141
- Thread.current.reset_fiber_scheduling
142
- values << :restarted
143
- snooze
144
- values << :baz
145
- end.schedule
146
-
147
- suspend
148
-
149
- f1.schedule
150
- suspend
151
- assert_equal %i[foo restarted baz], values
152
- end
153
-
154
- def test_restart
155
- values = []
156
- Fiber.new do
157
- values << :foo
158
- snooze
159
- # this part will not be reached, as Gyro state is reset
160
- values << :bar
161
- suspend
162
- end.schedule
163
-
164
- Fiber.new do
165
- Thread.current.reset_fiber_scheduling
166
-
167
- # control is transfer to the fiber that called Gyro.restart
168
- values << :restarted
169
- snooze
170
- values << :baz
171
- end.schedule
172
-
173
- suspend
174
-
175
- assert_equal %i[foo restarted baz], values
176
- end
177
25
  end
data/test/test_io.rb CHANGED
@@ -16,7 +16,7 @@ class GyroIOTest < MiniTest::Test
16
16
  }
17
17
  snooze
18
18
  sequence << 3
19
- defer do
19
+ spin do
20
20
  o << 'hello'
21
21
  sequence << 4
22
22
  end
@@ -106,7 +106,7 @@ class SupervisorTest < MiniTest::Test
106
106
  buffer = []
107
107
  supervisor = nil
108
108
  supervisor = Polyphony::Supervisor.new
109
- defer { supervisor.interrupt(42) }
109
+ spin { supervisor.interrupt(42) }
110
110
  buffer << supervisor.await { |s|
111
111
  (1..3).each { |i|
112
112
  s.spin {
@@ -125,7 +125,7 @@ class SupervisorTest < MiniTest::Test
125
125
  buffer = []
126
126
  supervisor = nil
127
127
  supervisor = Polyphony::Supervisor.new
128
- defer { supervisor.interrupt(42) }
128
+ spin { supervisor.interrupt(42) }
129
129
  buffer << supervisor.select { |s|
130
130
  (1..3).each { |i|
131
131
  s.spin {
data/test/test_thread.rb CHANGED
@@ -5,25 +5,35 @@ require_relative 'helper'
5
5
  class ThreadTest < MiniTest::Test
6
6
  def test_thread_spin
7
7
  buffer = []
8
- spin { (1..3).each { |i| snooze; buffer << i } }
8
+ f = spin { (1..3).each { |i| snooze; buffer << i } }
9
9
  t = Thread.new do
10
10
  s1 = spin { (11..13).each { |i| snooze; buffer << i } }
11
11
  s2 = spin { (21..23).each { |i| snooze; buffer << i } }
12
12
  Fiber.join(s1, s2)
13
13
  end
14
+ f.join
14
15
  t.join
15
16
 
16
17
  assert_equal [1, 2, 3, 11, 12, 13, 21, 22, 23], buffer.sort
17
18
  end
18
19
 
19
20
  def test_thread_join
21
+ tr = nil
22
+ # tr = Polyphony::Trace.new(:fiber_all) { |r| p r[:event] }
23
+ # Gyro.trace(true)
24
+ # tr.enable
25
+
20
26
  buffer = []
21
27
  spin { (1..3).each { |i| snooze; buffer << i } }
22
28
  t = Thread.new { sleep 0.01; buffer << 4 }
29
+
23
30
  r = t.join
24
31
 
25
32
  assert_equal [1, 2, 3, 4], buffer
26
33
  assert_equal t, r
34
+ ensure
35
+ tr&.disable
36
+ Gyro.trace(nil)
27
37
  end
28
38
 
29
39
  def test_thread_join_with_timeout
@@ -53,4 +63,65 @@ class ThreadTest < MiniTest::Test
53
63
  )
54
64
  assert_equal str, t.inspect
55
65
  end
66
+
67
+ def test_that_suspend_returns_immediately_if_no_watchers
68
+ records = []
69
+ t = Polyphony::Trace.new(:fiber_all) { |r| records << r if r[:event] =~ /^fiber_/ }
70
+ t.enable
71
+ Gyro.trace(true)
72
+
73
+ suspend
74
+ t.disable
75
+ assert_equal [:fiber_switchpoint], records.map { |r| r[:event] }
76
+ ensure
77
+ t&.disable
78
+ Gyro.trace(false)
79
+ end
80
+
81
+ def test_reset
82
+ values = []
83
+ f1 = spin do
84
+ values << :foo
85
+ snooze
86
+ values << :bar
87
+ suspend
88
+ end
89
+
90
+ f2 = spin do
91
+ Thread.current.reset_fiber_scheduling
92
+ values << :restarted
93
+ snooze
94
+ values << :baz
95
+ end
96
+
97
+ suspend
98
+
99
+ f1.schedule
100
+ suspend
101
+ assert_equal %i[foo restarted baz], values
102
+ end
103
+
104
+ def test_restart
105
+ values = []
106
+ spin do
107
+ values << :foo
108
+ snooze
109
+ # this part will not be reached, as Gyro state is reset
110
+ values << :bar
111
+ suspend
112
+ end
113
+
114
+ spin do
115
+ Thread.current.reset_fiber_scheduling
116
+
117
+ # control is transfer to the fiber that called Gyro.restart
118
+ values << :restarted
119
+ snooze
120
+ values << :baz
121
+ end
122
+
123
+ suspend
124
+
125
+ assert_equal %i[foo restarted baz], values
126
+ end
56
127
  end
@@ -5,10 +5,11 @@ require_relative 'helper'
5
5
  class ThreadPoolTest < MiniTest::Test
6
6
  def setup
7
7
  super
8
- @pool = Polyphony::ThreadPool.new
8
+ # @pool = Polyphony::ThreadPool.new
9
9
  end
10
10
 
11
11
  def test_process
12
+ skip
12
13
  current_thread = Thread.current
13
14
 
14
15
  processing_thread = nil
@@ -21,6 +22,7 @@ class ThreadPoolTest < MiniTest::Test
21
22
  end
22
23
 
23
24
  def test_multi_process
25
+ skip
24
26
  current_thread = Thread.current
25
27
  threads = []
26
28
  results = []
@@ -42,6 +44,7 @@ class ThreadPoolTest < MiniTest::Test
42
44
  end
43
45
 
44
46
  def test_process_with_exception
47
+ skip
45
48
  result = nil
46
49
  begin
47
50
  result = @pool.process { raise 'foo' }
@@ -53,6 +56,7 @@ class ThreadPoolTest < MiniTest::Test
53
56
  end
54
57
 
55
58
  def test_cast
59
+ skip
56
60
  t0 = Time.now
57
61
  threads = []
58
62
  buffer = []
@@ -68,7 +72,7 @@ class ThreadPoolTest < MiniTest::Test
68
72
  assert elapsed < 0.005
69
73
  assert buffer.size < 2
70
74
 
71
- sleep 0.04
75
+ sleep 0.05
72
76
  assert_equal @pool.size, threads.uniq.size
73
77
  assert_equal (0..9).to_a, buffer.sort
74
78
  end