polyphony 0.22 → 0.23

Sign up to get free protection for your applications and to get access to all the features.
Files changed (114) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +25 -0
  3. data/Gemfile.lock +9 -1
  4. data/TODO.md +13 -38
  5. data/docs/summary.md +19 -5
  6. data/docs/technical-overview/faq.md +12 -0
  7. data/examples/core/01-spinning-up-coprocesses.rb +2 -6
  8. data/examples/core/02-awaiting-coprocesses.rb +3 -1
  9. data/examples/core/03-interrupting.rb +3 -1
  10. data/examples/core/04-no-auto-run.rb +1 -3
  11. data/examples/core/cancel.rb +1 -1
  12. data/examples/core/channel_echo.rb +3 -1
  13. data/examples/core/defer.rb +3 -1
  14. data/examples/core/enumerator.rb +3 -1
  15. data/examples/core/error_bubbling.rb +35 -0
  16. data/examples/core/fork.rb +1 -1
  17. data/examples/core/genserver.rb +1 -1
  18. data/examples/core/lock.rb +3 -1
  19. data/examples/core/move_on.rb +1 -1
  20. data/examples/core/move_on_twice.rb +1 -1
  21. data/examples/core/move_on_with_ensure.rb +1 -1
  22. data/examples/core/move_on_with_value.rb +1 -1
  23. data/examples/core/multiple_spin.rb +3 -1
  24. data/examples/core/nested_cancel.rb +1 -1
  25. data/examples/core/nested_multiple_spin.rb +3 -1
  26. data/examples/core/nested_spin.rb +3 -1
  27. data/examples/core/pulse.rb +1 -1
  28. data/examples/core/resource.rb +1 -1
  29. data/examples/core/resource_cancel.rb +2 -2
  30. data/examples/core/resource_delegate.rb +1 -1
  31. data/examples/core/sleep.rb +1 -1
  32. data/examples/core/sleep_spin.rb +3 -1
  33. data/examples/core/snooze.rb +1 -1
  34. data/examples/core/spin_error.rb +2 -1
  35. data/examples/core/spin_error_backtrace.rb +1 -1
  36. data/examples/core/spin_uncaught_error.rb +3 -1
  37. data/examples/core/supervisor.rb +1 -1
  38. data/examples/core/supervisor_with_cancel_scope.rb +1 -1
  39. data/examples/core/supervisor_with_error.rb +3 -1
  40. data/examples/core/supervisor_with_manual_move_on.rb +1 -1
  41. data/examples/core/suspend.rb +1 -1
  42. data/examples/core/thread.rb +3 -3
  43. data/examples/core/thread_cancel.rb +6 -3
  44. data/examples/core/thread_pool.rb +8 -52
  45. data/examples/core/thread_pool_perf.rb +63 -0
  46. data/examples/core/throttle.rb +3 -1
  47. data/examples/core/timeout.rb +1 -1
  48. data/examples/core/wait_for_signal.rb +4 -2
  49. data/examples/fs/read.rb +1 -1
  50. data/examples/http/http2_raw.rb +1 -1
  51. data/examples/http/http_get.rb +1 -1
  52. data/examples/http/http_server.rb +2 -1
  53. data/examples/http/http_server_graceful.rb +3 -1
  54. data/examples/http/http_ws_server.rb +0 -2
  55. data/examples/http/https_wss_server.rb +0 -2
  56. data/examples/http/websocket_secure_server.rb +0 -2
  57. data/examples/http/websocket_server.rb +0 -2
  58. data/examples/interfaces/redis_channels.rb +3 -1
  59. data/examples/interfaces/redis_pubsub.rb +3 -1
  60. data/examples/interfaces/redis_pubsub_perf.rb +3 -1
  61. data/examples/io/backticks.rb +1 -1
  62. data/examples/io/cat.rb +1 -1
  63. data/examples/io/echo_client.rb +1 -1
  64. data/examples/io/echo_client_from_stdin.rb +3 -1
  65. data/examples/io/echo_pipe.rb +1 -1
  66. data/examples/io/echo_server.rb +1 -1
  67. data/examples/io/echo_server_with_timeout.rb +1 -1
  68. data/examples/io/echo_stdin.rb +1 -1
  69. data/examples/io/httparty_multi.rb +1 -1
  70. data/examples/io/io_read.rb +1 -1
  71. data/examples/io/irb.rb +1 -1
  72. data/examples/io/net-http.rb +1 -1
  73. data/examples/io/open.rb +1 -1
  74. data/examples/io/system.rb +1 -1
  75. data/examples/io/tcpserver.rb +1 -1
  76. data/examples/io/tcpsocket.rb +1 -1
  77. data/examples/performance/multi_snooze.rb +1 -1
  78. data/examples/performance/snooze.rb +18 -10
  79. data/ext/gyro/async.c +16 -9
  80. data/ext/gyro/child.c +2 -2
  81. data/ext/gyro/gyro.c +17 -10
  82. data/ext/gyro/gyro.h +2 -2
  83. data/ext/gyro/io.c +2 -2
  84. data/ext/gyro/signal.c +33 -35
  85. data/ext/gyro/timer.c +6 -73
  86. data/lib/polyphony.rb +6 -8
  87. data/lib/polyphony/core/cancel_scope.rb +32 -21
  88. data/lib/polyphony/core/coprocess.rb +26 -23
  89. data/lib/polyphony/core/global_api.rb +86 -0
  90. data/lib/polyphony/core/resource_pool.rb +1 -1
  91. data/lib/polyphony/core/supervisor.rb +47 -13
  92. data/lib/polyphony/core/thread.rb +10 -36
  93. data/lib/polyphony/core/thread_pool.rb +6 -26
  94. data/lib/polyphony/extensions/core.rb +30 -100
  95. data/lib/polyphony/extensions/io.rb +10 -7
  96. data/lib/polyphony/extensions/openssl.rb +18 -28
  97. data/lib/polyphony/http/client/agent.rb +15 -11
  98. data/lib/polyphony/http/client/http2.rb +1 -1
  99. data/lib/polyphony/version.rb +1 -1
  100. data/polyphony.gemspec +1 -0
  101. data/test/coverage.rb +45 -0
  102. data/test/helper.rb +15 -5
  103. data/test/test_async.rb +4 -4
  104. data/test/test_cancel_scope.rb +109 -0
  105. data/test/test_coprocess.rb +80 -36
  106. data/test/{test_core.rb → test_global_api.rb} +67 -13
  107. data/test/test_gyro.rb +1 -5
  108. data/test/test_io.rb +2 -2
  109. data/test/test_resource_pool.rb +19 -0
  110. data/test/test_signal.rb +10 -5
  111. data/test/test_supervisor.rb +168 -0
  112. data/test/test_timer.rb +31 -5
  113. metadata +23 -4
  114. data/lib/polyphony/auto_run.rb +0 -19
@@ -54,31 +54,32 @@ class CancelScopeTest < Minitest::Test
54
54
  def test_that_cancel_scope_cancels_coprocess
55
55
  ctx = {}
56
56
  spin do
57
- Gyro::Timer.new(0.005, 0).start { ctx[:cancel_scope]&.cancel! }
57
+ after(0.005) { ctx[:cancel_scope].cancel! }
58
58
  sleep_with_cancel(ctx, :cancel)
59
59
  rescue Exception => e
60
60
  ctx[:result] = e
61
+ nil
61
62
  end
62
63
  assert_nil(ctx[:result])
63
64
  # async operation will only begin on next iteration of event loop
64
65
  assert_nil(ctx[:cancel_scope])
65
66
 
66
- suspend
67
+ Gyro.run
67
68
  assert_kind_of(Polyphony::CancelScope, ctx[:cancel_scope])
68
69
  assert_kind_of(Polyphony::Cancel, ctx[:result])
69
70
  end
70
71
 
71
- # def test_that_cancel_scope_cancels_async_op_with_stop
72
- # ctx = {}
73
- # spin do
74
- # Gyro::Timer.new(0, 0).start { ctx[:cancel_scope].cancel! }
75
- # sleep_with_cancel(ctx, :stop)
76
- # end
72
+ def test_that_cancel_scope_cancels_async_op_with_stop
73
+ ctx = {}
74
+ spin do
75
+ after(0) { ctx[:cancel_scope].cancel! }
76
+ sleep_with_cancel(ctx, :stop)
77
+ end
77
78
 
78
- # suspend
79
- # assert(ctx[:cancel_scope])
80
- # assert_nil(ctx[:result])
81
- # end
79
+ Gyro.run
80
+ assert(ctx[:cancel_scope])
81
+ assert_nil(ctx[:result])
82
+ end
82
83
 
83
84
  def test_that_cancel_after_raises_cancelled_exception
84
85
  result = nil
@@ -169,7 +170,7 @@ class SupervisorTest < MiniTest::Test
169
170
  supervisor.await
170
171
  end.await
171
172
 
172
- assert_equal([0, 1, 2], result)
173
+ assert_equal([0, 1, 2], result.sort)
173
174
  end
174
175
  end
175
176
 
@@ -241,4 +242,57 @@ class MoveOnAfterTest < MiniTest::Test
241
242
  assert t1 - t0 < 0.02
242
243
  assert_equal :bar, v
243
244
  end
245
+
246
+ def test_spin_loop
247
+ buffer = []
248
+ counter = 0
249
+ cp = spin_loop do
250
+ buffer << (counter += 1)
251
+ snooze
252
+ end
253
+
254
+ assert_kind_of Polyphony::Coprocess, cp
255
+ assert_equal [], buffer
256
+ snooze
257
+ assert_equal [1], buffer
258
+ snooze
259
+ assert_equal [1, 2], buffer
260
+ snooze
261
+ assert_equal [1, 2, 3], buffer
262
+ cp.stop
263
+ snooze
264
+ assert !cp.alive?
265
+ assert_equal [1, 2, 3], buffer
266
+ end
267
+
268
+ def test_throttled_loop
269
+ buffer = []
270
+ counter = 0
271
+ cp = spin do
272
+ throttled_loop(50) { buffer << (counter += 1) }
273
+ end
274
+ sleep 0.1
275
+ cp.stop
276
+ assert_equal [1, 2, 3, 4, 5], buffer
277
+ end
278
+
279
+ def test_throttled_loop_with_count
280
+ buffer = []
281
+ counter = 0
282
+ cp = spin do
283
+ throttled_loop(50, count: 5) { buffer << (counter += 1) }
284
+ end
285
+ cp.await
286
+ assert_equal [1, 2, 3, 4, 5], buffer
287
+ end
288
+
289
+ def test_every
290
+ buffer = []
291
+ cp = spin do
292
+ every(0.01) { buffer << 1 }
293
+ end
294
+ sleep 0.05
295
+ cp.stop
296
+ assert_equal 5, buffer.size
297
+ end
244
298
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  require_relative 'helper'
4
4
 
5
- class SchedulingTest < MiniTest::Test
5
+ class GyroTest < MiniTest::Test
6
6
  def test_fiber_state
7
7
  assert_equal :running, Fiber.current.state
8
8
 
@@ -35,18 +35,14 @@ class SchedulingTest < MiniTest::Test
35
35
  snooze
36
36
  assert_equal [0, 1, 2], values
37
37
  end
38
- end
39
38
 
40
- class RunTest < MiniTest::Test
41
39
  def test_that_run_loop_returns_immediately_if_no_watchers
42
40
  t0 = Time.now
43
41
  suspend
44
42
  t1 = Time.now
45
43
  assert((t1 - t0) < 0.01)
46
44
  end
47
- end
48
45
 
49
- class IdleTest < MiniTest::Test
50
46
  def test_defer
51
47
  values = []
52
48
  defer { values << 1 }
@@ -134,8 +134,8 @@ class IOClassMethodsTest < MiniTest::Test
134
134
  counter = 0
135
135
  timer = spin { throttled_loop(200) { counter += 1 } }
136
136
 
137
- IO.popen('sleep 0.1') { |io| io.read(8192) }
138
- assert(counter >= 10)
137
+ IO.popen('sleep 0.05') { |io| io.read(8192) }
138
+ assert(counter >= 5)
139
139
 
140
140
  result = nil
141
141
  IO.popen('echo "foo"') { |io| result = io.read(8192) }
@@ -104,4 +104,23 @@ class ResourcePoolTest < MiniTest::Test
104
104
 
105
105
  assert_raises { pool.acquire { } }
106
106
  end
107
+
108
+ def test_method_delegation
109
+ resources = [+'a', +'b']
110
+ pool = Polyphony::ResourcePool.new(limit: 2) { resources.shift }
111
+
112
+ assert_respond_to pool, :upcase
113
+ assert_equal 'A', pool.upcase
114
+ end
115
+
116
+ def test_preheat
117
+ resources = [+'a', +'b']
118
+ pool = Polyphony::ResourcePool.new(limit: 2) { resources.shift }
119
+
120
+ assert_equal 2, pool.limit
121
+ assert_equal 0, pool.size
122
+
123
+ pool.preheat!
124
+ assert_equal 2, pool.size
125
+ end
107
126
  end
@@ -6,12 +6,17 @@ class SignalTest < MiniTest::Test
6
6
  def test_Gyro_Signal_constructor
7
7
  sig = Signal.list['USR1']
8
8
  count = 0
9
- w = Gyro::Signal.new(sig) do
10
- count += 1
11
- w.stop
12
- end
9
+ w = Gyro::Signal.new(sig)
10
+
11
+ spin {
12
+ loop {
13
+ w.await
14
+ count += 1
15
+ break
16
+ }
17
+ }
13
18
  Thread.new do
14
- sync_sleep 0.001
19
+ orig_sleep 0.001
15
20
  Process.kill(:USR1, Process.pid)
16
21
  end
17
22
  suspend
@@ -0,0 +1,168 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'helper'
4
+
5
+ class SupervisorTest < MiniTest::Test
6
+ def test_await
7
+ result = Polyphony::Supervisor.new.await { |s|
8
+ s.spin {
9
+ snooze
10
+ :foo
11
+ }
12
+ }
13
+ assert_equal [:foo], result
14
+ end
15
+
16
+ def test_await_multiple_coprocs
17
+ result = Polyphony::Supervisor.new.await { |s|
18
+ (1..3).each { |i|
19
+ s.spin {
20
+ snooze
21
+ i * 10
22
+ }
23
+ }
24
+ }
25
+ assert_equal [10, 20, 30], result
26
+ end
27
+
28
+ def test_join_multiple_coprocs
29
+ result = Polyphony::Supervisor.new.join { |s|
30
+ (1..3).each { |i|
31
+ s.spin {
32
+ snooze
33
+ i * 10
34
+ }
35
+ }
36
+ }
37
+ assert_equal [10, 20, 30], result
38
+ end
39
+
40
+ def test_spin_method
41
+ buffer = []
42
+ Polyphony::Supervisor.new.join { |s|
43
+ (1..3).each { |i|
44
+ buffer << s.spin {
45
+ snooze
46
+ i * 10
47
+ }
48
+ }
49
+ }
50
+
51
+ assert_equal [Polyphony::Coprocess], buffer.map { |v| v.class }.uniq
52
+ end
53
+
54
+ def test_supervisor_select
55
+ buffer = []
56
+ foo_cp = bar_cp = baz_cp = nil
57
+ result, cp = Polyphony::Supervisor.new.select { |s|
58
+ foo_cp = s.spin { sleep 0.01; buffer << :foo; :foo }
59
+ bar_cp = s.spin { sleep 0.02; buffer << :bar; :bar }
60
+ baz_cp = s.spin { sleep 0.03; buffer << :baz; :baz }
61
+ }
62
+
63
+ assert_equal :foo, result
64
+ assert_equal foo_cp, cp
65
+
66
+ sleep 0.03
67
+ assert_nil bar_cp.alive?
68
+ assert_nil baz_cp.alive?
69
+ assert_equal [:foo], buffer
70
+ end
71
+
72
+ def test_await_with_exception
73
+ buffer = []
74
+ result = capture_exception do
75
+ Polyphony::Supervisor.new.await { |s|
76
+ (1..3).each { |i|
77
+ s.spin {
78
+ raise 'foo' if i == 1
79
+ snooze
80
+ buffer << i * 10
81
+ i * 10
82
+ }
83
+ }
84
+ }
85
+ end
86
+
87
+ assert_kind_of RuntimeError, result
88
+ assert_equal 'foo', result.message
89
+ snooze
90
+ assert_equal [], buffer
91
+ end
92
+
93
+ def test_await_interruption
94
+ buffer = []
95
+ supervisor = nil
96
+ supervisor = Polyphony::Supervisor.new
97
+ defer { supervisor.interrupt(42) }
98
+ buffer << supervisor.await { |s|
99
+ (1..3).each { |i|
100
+ s.spin {
101
+ buffer << i
102
+ sleep i
103
+ buffer << i * 10
104
+ }
105
+ }
106
+ }
107
+
108
+ snooze
109
+ assert_equal [1, 2, 3, 42], buffer
110
+ end
111
+
112
+ def test_select_interruption
113
+ buffer = []
114
+ supervisor = nil
115
+ supervisor = Polyphony::Supervisor.new
116
+ defer { supervisor.interrupt(42) }
117
+ buffer << supervisor.select { |s|
118
+ (1..3).each { |i|
119
+ s.spin {
120
+ buffer << i
121
+ sleep i
122
+ buffer << i * 10
123
+ }
124
+ }
125
+ }
126
+
127
+ snooze
128
+ assert_equal [1, 2, 3, 42], buffer
129
+ end
130
+
131
+ def test_add
132
+ supervisor = Polyphony::Supervisor.new
133
+ supervisor << spin { :foo }
134
+ supervisor << spin { :bar }
135
+
136
+ assert_equal [:foo, :bar], supervisor.await
137
+ end
138
+ end
139
+
140
+ class CoprocessExtensionsTest < MiniTest::Test
141
+ def test_join
142
+ cp1 = spin { :foo }
143
+ cp2 = spin { :bar }
144
+ assert_equal [:foo, :bar], Polyphony::Coprocess.join(cp1, cp2)
145
+
146
+ cp1 = spin { :foo }
147
+ cp2 = spin { raise 'bar' }
148
+ result = capture_exception { Polyphony::Coprocess.join(cp1, cp2) }
149
+ assert_kind_of RuntimeError, result
150
+ assert_equal 'bar', result.message
151
+ end
152
+
153
+ def test_select
154
+ cp1 = spin { sleep 1; :foo }
155
+ cp2 = spin { :bar }
156
+ assert_equal [:bar, cp2], Polyphony::Coprocess.select(cp1, cp2)
157
+
158
+ cp1 = spin { :foo }
159
+ cp2 = spin { sleep 0.01; raise 'bar' }
160
+ assert_equal [:foo, cp1], Polyphony::Coprocess.select(cp1, cp2)
161
+
162
+ cp1 = spin { sleep 1; :foo }
163
+ cp2 = spin { raise 'bar' }
164
+ result = capture_exception { Polyphony::Coprocess.select(cp1, cp2) }
165
+ assert_kind_of RuntimeError, result
166
+ assert_equal 'bar', result.message
167
+ end
168
+ end
@@ -6,7 +6,10 @@ class TimerTest < MiniTest::Test
6
6
  def test_that_one_shot_timer_works
7
7
  count = 0
8
8
  t = Gyro::Timer.new(0.01, 0)
9
- t.start { count += 1 }
9
+ spin {
10
+ t.await
11
+ count += 1
12
+ }
10
13
  suspend
11
14
  assert_equal(1, count)
12
15
  end
@@ -14,11 +17,34 @@ class TimerTest < MiniTest::Test
14
17
  def test_that_repeating_timer_works
15
18
  count = 0
16
19
  t = Gyro::Timer.new(0.001, 0.001)
17
- t.start do
18
- count += 1
19
- t.stop if count >= 3
20
- end
20
+ spin {
21
+ loop {
22
+ t.await
23
+ count += 1
24
+ break if count >= 3
25
+ }
26
+ }
21
27
  suspend
22
28
  assert_equal(3, count)
23
29
  end
30
+
31
+ def test_that_repeating_timer_compensates_for_drift
32
+ count = 0
33
+ t = Gyro::Timer.new(0.01, 0.01)
34
+ times = []
35
+ last = nil
36
+ spin {
37
+ last = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
38
+ loop {
39
+ t.await
40
+ times << ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
41
+ count += 1
42
+ sleep 0.005
43
+ break if count >= 10
44
+ }
45
+ }
46
+ suspend
47
+ deltas = times.each_with_object([]) { |t, a| a << t - last; last = t }
48
+ assert_equal 0, deltas.filter { |d| (d - 0.01).abs >= 0.005 }.size
49
+ end
24
50
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: polyphony
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.22'
4
+ version: '0.23'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-01-02 00:00:00.000000000 Z
11
+ date: 2020-01-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: modulation
@@ -136,6 +136,20 @@ dependencies:
136
136
  - - '='
137
137
  - !ruby/object:Gem::Version
138
138
  version: 1.4.2
139
+ - !ruby/object:Gem::Dependency
140
+ name: simplecov
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - '='
144
+ - !ruby/object:Gem::Version
145
+ version: 0.17.1
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - '='
151
+ - !ruby/object:Gem::Version
152
+ version: 0.17.1
139
153
  - !ruby/object:Gem::Dependency
140
154
  name: pg
141
155
  requirement: !ruby/object:Gem::Requirement
@@ -232,6 +246,7 @@ files:
232
246
  - examples/core/channel_echo.rb
233
247
  - examples/core/defer.rb
234
248
  - examples/core/enumerator.rb
249
+ - examples/core/error_bubbling.rb
235
250
  - examples/core/fiber_error.rb
236
251
  - examples/core/fiber_error_with_backtrace.rb
237
252
  - examples/core/fork.rb
@@ -265,6 +280,7 @@ files:
265
280
  - examples/core/thread.rb
266
281
  - examples/core/thread_cancel.rb
267
282
  - examples/core/thread_pool.rb
283
+ - examples/core/thread_pool_perf.rb
268
284
  - examples/core/throttle.rb
269
285
  - examples/core/timeout.rb
270
286
  - examples/core/wait_for_signal.rb
@@ -353,11 +369,11 @@ files:
353
369
  - ext/libev/test_libev_win32.c
354
370
  - lib/ev_ext.bundle
355
371
  - lib/polyphony.rb
356
- - lib/polyphony/auto_run.rb
357
372
  - lib/polyphony/core/cancel_scope.rb
358
373
  - lib/polyphony/core/channel.rb
359
374
  - lib/polyphony/core/coprocess.rb
360
375
  - lib/polyphony/core/exceptions.rb
376
+ - lib/polyphony/core/global_api.rb
361
377
  - lib/polyphony/core/resource_pool.rb
362
378
  - lib/polyphony/core/supervisor.rb
363
379
  - lib/polyphony/core/sync.rb
@@ -388,18 +404,21 @@ files:
388
404
  - lib/polyphony/version.rb
389
405
  - lib/polyphony/websocket.rb
390
406
  - polyphony.gemspec
407
+ - test/coverage.rb
391
408
  - test/eg.rb
392
409
  - test/helper.rb
393
410
  - test/run.rb
394
411
  - test/test_async.rb
412
+ - test/test_cancel_scope.rb
395
413
  - test/test_coprocess.rb
396
- - test/test_core.rb
414
+ - test/test_global_api.rb
397
415
  - test/test_gyro.rb
398
416
  - test/test_http_server.rb
399
417
  - test/test_io.rb
400
418
  - test/test_kernel.rb
401
419
  - test/test_resource_pool.rb
402
420
  - test/test_signal.rb
421
+ - test/test_supervisor.rb
403
422
  - test/test_timer.rb
404
423
  homepage: http://github.com/digital-fabric/polyphony
405
424
  licenses: