polyphony 0.22 → 0.23

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.
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: