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
@@ -25,13 +25,16 @@ class ::IO
25
25
 
26
26
  alias_method :orig_foreach, :foreach
27
27
  def foreach(name, sep = $/, limit = nil, getline_args = EMPTY_HASH, &block)
28
- if sep.is_a?(Integer)
29
- sep = $/
30
- limit = sep
31
- end
32
- File.open(name, 'r') do |f|
33
- f.each_line(sep, limit, getline_args, &block)
34
- end
28
+ # IO.orig_read(name).each_line(&block)
29
+ raise NotImplementedError
30
+
31
+ # if sep.is_a?(Integer)
32
+ # sep = $/
33
+ # limit = sep
34
+ # end
35
+ # File.open(name, 'r') do |f|
36
+ # f.each_line(sep, limit, getline_args, &block)
37
+ # end
35
38
  end
36
39
 
37
40
  alias_method :orig_read, :read
@@ -19,29 +19,24 @@ class ::OpenSSL::SSL::SSLSocket
19
19
  end
20
20
 
21
21
  def sysread(maxlen, buf)
22
+ read_watcher = nil
23
+ write_watcher = nil
22
24
  loop do
23
- read_watcher = nil
24
- write_watcher = nil
25
- result = read_nonblock(maxlen, buf, exception: false)
26
- if result == :wait_readable
27
- read_watcher ||= Gyro::IO.new(io, :r)
28
- read_watcher.await
29
- elsif result == :wait_writable
30
- write_watcher ||= Gyro::IO.new(io, :w)
31
- write_watcher.await
32
- else
33
- return result
25
+ case (result = read_nonblock(maxlen, buf, exception: false))
26
+ when :wait_readable then (read_watcher ||= Gyro::IO.new(io, :r)).await
27
+ when :wait_writable then (write_watcher ||= Gyro::IO.new(io, :w)).await
28
+ else result
34
29
  end
35
30
  end
36
31
  end
37
32
 
38
33
  def flush
39
- # osync = @sync
40
- # @sync = true
41
- # do_write ""
42
- # return self
43
- # ensure
44
- # @sync = osync
34
+ # osync = @sync
35
+ # @sync = true
36
+ # do_write ""
37
+ # return self
38
+ # ensure
39
+ # @sync = osync
45
40
  end
46
41
 
47
42
  # def do_write(s)
@@ -62,18 +57,13 @@ class ::OpenSSL::SSL::SSLSocket
62
57
  # end
63
58
 
64
59
  def syswrite(buf)
60
+ read_watcher = nil
61
+ write_watcher = nil
65
62
  loop do
66
- read_watcher = nil
67
- write_watcher = nil
68
- result = write_nonblock(buf, exception: false)
69
- if result == :wait_readable
70
- read_watcher ||= Gyro::IO.new(io, :r)
71
- read_watcher.await
72
- elsif result == :wait_writable
73
- write_watcher ||= Gyro::IO.new(io, :w)
74
- write_watcher.await
75
- else
76
- return result
63
+ case (result = write_nonblock(buf, exception: false))
64
+ when :wait_readable then (read_watcher ||= Gyro::IO.new(io, :r)).await
65
+ when :wait_writable then (write_watcher ||= Gyro::IO.new(io, :w)).await
66
+ else result
77
67
  end
78
68
  end
79
69
  end
@@ -104,23 +104,27 @@ class Agent
104
104
  key = uri_key(ctx[:uri])
105
105
 
106
106
  @pools[key].acquire do |adapter|
107
- response = adapter.request(ctx)
108
- case response.status_code
109
- when 200, 204
110
- if block
111
- block.(response)
112
- else
113
- # read body
114
- response.body
115
- end
116
- end
117
- response
107
+ send_request_and_check_response(adapter, ctx, &block)
118
108
  end
119
109
  rescue Exception => e
120
110
  p e
121
111
  puts e.backtrace.join("\n")
122
112
  end
123
113
 
114
+ def send_request_and_check_response(adapter, ctx, &block)
115
+ response = adapter.request(ctx)
116
+ case response.status_code
117
+ when 200, 204
118
+ if block
119
+ block.(response)
120
+ else
121
+ # read body
122
+ response.body
123
+ end
124
+ end
125
+ response
126
+ end
127
+
124
128
  def uri_key(uri)
125
129
  { scheme: uri.scheme, host: uri.host, port: uri.port }
126
130
  end
@@ -60,7 +60,7 @@ class HTTP2Adapter
60
60
  rescue Exception => e
61
61
  p e
62
62
  puts e.backtrace.join("\n")
63
- # ensure
63
+ # ensure
64
64
  # stream.close
65
65
  end
66
66
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Polyphony
4
- VERSION = '0.22'
4
+ VERSION = '0.23'
5
5
  end
@@ -31,6 +31,7 @@ Gem::Specification.new do |s|
31
31
  s.add_development_dependency 'localhost', '1.1.4'
32
32
  s.add_development_dependency 'minitest', '5.11.3'
33
33
  s.add_development_dependency 'minitest-reporters', '1.4.2'
34
+ s.add_development_dependency 'simplecov', '0.17.1'
34
35
  s.add_development_dependency 'pg', '1.1.3'
35
36
  s.add_development_dependency 'rake-compiler', '1.0.5'
36
37
  s.add_development_dependency 'redis', '4.1.0'
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'coverage'
4
+ require 'simplecov'
5
+
6
+ class << SimpleCov::LinesClassifier
7
+ alias_method :orig_whitespace_line?, :whitespace_line?
8
+ def whitespace_line?(line)
9
+ line.strip =~ /^(begin|end|ensure|else|\})|(\s*rescue\s.+)$/ || orig_whitespace_line?(line)
10
+ end
11
+ end
12
+
13
+ module Coverage
14
+ EXCLUDE = %w{coverage eg helper run
15
+ }.map { |n| File.expand_path("test/#{n}.rb") }
16
+
17
+ LIB_FILES = Dir["#{File.join(FileUtils.pwd, 'lib')}/polyphony/**/*.rb"]
18
+
19
+ class << self
20
+ def relevant_lines_for_filename(filename)
21
+ @classifier ||= SimpleCov::LinesClassifier.new
22
+ @classifier.classify(IO.read(filename).lines)
23
+ end
24
+
25
+ def start
26
+ @result = {}
27
+ trace = TracePoint.new(:line) do |tp|
28
+ next if tp.path =~ /\(/
29
+
30
+ absolute = File.expand_path(tp.path)
31
+ next unless LIB_FILES.include?(absolute)# =~ /^#{LIB_DIR}/
32
+
33
+ @result[absolute] ||= relevant_lines_for_filename(absolute)
34
+ @result[absolute][tp.lineno - 1] = 1
35
+ end
36
+ trace.enable
37
+ end
38
+
39
+ def result
40
+ @result
41
+ end
42
+ end
43
+ end
44
+
45
+ SimpleCov.start
@@ -2,13 +2,15 @@
2
2
 
3
3
  require 'bundler/setup'
4
4
 
5
+ require 'fileutils'
6
+ require_relative './eg'
7
+
8
+ require_relative './coverage' if ENV['COVERAGE']
9
+
5
10
  require 'minitest/autorun'
6
11
  require 'minitest/reporters'
7
12
 
8
13
  require 'polyphony'
9
- require 'fileutils'
10
-
11
- require_relative './eg'
12
14
 
13
15
  ::Exception.__disable_sanitized_backtrace__ = true
14
16
 
@@ -18,8 +20,16 @@ Minitest::Reporters.use! [
18
20
 
19
21
  class MiniTest::Test
20
22
  def teardown
21
- # wait for reactor loop to finish running
22
- suspend
23
+ # wait for any remaining scheduled work
24
+ Gyro.run
23
25
  Polyphony.reset!
24
26
  end
25
27
  end
28
+
29
+ module Kernel
30
+ def capture_exception
31
+ yield
32
+ rescue Exception => e
33
+ e
34
+ end
35
+ end
@@ -12,7 +12,7 @@ class AsyncTest < MiniTest::Test
12
12
  }
13
13
  snooze
14
14
  Thread.new do
15
- sync_sleep 0.001
15
+ orig_sleep 0.001
16
16
  a.signal!
17
17
  end
18
18
  suspend
@@ -26,15 +26,15 @@ class AsyncTest < MiniTest::Test
26
26
  loop {
27
27
  a.await
28
28
  count += 1
29
- after(0.01) { coproc.stop }
29
+ defer { coproc.stop }
30
30
  }
31
31
  }
32
32
  snooze
33
33
  Thread.new do
34
- sync_sleep 0.001
34
+ orig_sleep 0.001
35
35
  3.times { a.signal! }
36
36
  end
37
- suspend
37
+ coproc.await
38
38
  assert_equal(1, count)
39
39
  end
40
40
  end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'helper'
4
+
5
+ class CancelScopeTest < MiniTest::Test
6
+ def test_that_cancel_scope_can_cancel_provided_block
7
+ buffer = []
8
+ Polyphony::CancelScope.new { |scope|
9
+ defer { scope.cancel! }
10
+ buffer << 1
11
+ snooze
12
+ buffer << 2
13
+ }
14
+ assert_equal [1], buffer
15
+ end
16
+
17
+ def test_that_cancel_scope_can_cancel_multiple_coprocesses
18
+ buffer = []
19
+ scope = Polyphony::CancelScope.new
20
+ coprocs = (1..3).map { |i|
21
+ spin {
22
+ scope.call do
23
+ buffer << i
24
+ snooze
25
+ buffer << i * 10
26
+ end
27
+ }
28
+ }
29
+ snooze
30
+ scope.cancel!
31
+ assert_equal [1, 2, 3], buffer
32
+ end
33
+
34
+ def test_that_cancel_scope_takes_timeout_option
35
+ buffer = []
36
+ Polyphony::CancelScope.new(timeout: 0.01) { |scope|
37
+ buffer << 1
38
+ sleep 0.02
39
+ buffer << 2
40
+ }
41
+ assert_equal [1], buffer
42
+ end
43
+
44
+ def test_that_cancel_scope_cancels_timeout_waiter_if_block_provided
45
+ buffer = []
46
+ t0 = Time.now
47
+ scope = Polyphony::CancelScope.new(timeout: 1) { |scope|
48
+ buffer << 1
49
+ }
50
+ assert_equal [1], buffer
51
+ assert Time.now - t0 < 1
52
+ assert_nil scope.instance_variable_get(:@timeout_waiter)
53
+ end
54
+
55
+ def test_that_cancel_scope_can_cancel_multiple_coprocs_with_timeout
56
+ buffer = []
57
+ t0 = Time.now
58
+ scope = Polyphony::CancelScope.new(timeout: 0.02)
59
+ coprocs = (1..3).map { |i|
60
+ spin {
61
+ scope.call do
62
+ buffer << i
63
+ sleep i
64
+ buffer << i * 10
65
+ end
66
+ }
67
+ }
68
+ Polyphony::Coprocess.await(*coprocs)
69
+ assert Time.now - t0 < 0.05
70
+ assert_equal [1, 2, 3], buffer
71
+ end
72
+
73
+ def test_reset_timeout
74
+ buffer = []
75
+ scope = Polyphony::CancelScope.new(timeout: 0.01)
76
+ t0 = Time.now
77
+ scope.call {
78
+ sleep 0.005
79
+ scope.reset_timeout
80
+ sleep 0.010
81
+ }
82
+
83
+ assert !scope.cancelled?
84
+ end
85
+
86
+ def test_on_cancel
87
+ buffer = []
88
+ Polyphony::CancelScope.new { |scope|
89
+ defer { scope.cancel! }
90
+ scope.on_cancel { buffer << :cancelled }
91
+ buffer << 1
92
+ snooze
93
+ buffer << 2
94
+ }
95
+ assert_equal [1, :cancelled], buffer
96
+ end
97
+
98
+ def test_cancelled?
99
+ scope = Polyphony::CancelScope.new
100
+ spin {
101
+ scope.call { sleep 1 }
102
+ }
103
+
104
+ snooze
105
+ assert !scope.cancelled?
106
+ scope.cancel!
107
+ assert scope.cancelled?
108
+ end
109
+ end
@@ -2,8 +2,6 @@
2
2
 
3
3
  require_relative 'helper'
4
4
 
5
- STDOUT.sync = true
6
-
7
5
  class CoprocessTest < MiniTest::Test
8
6
  def test_that_root_fiber_has_associated_coprocess
9
7
  assert_equal(Fiber.current, Polyphony::Coprocess.current.fiber)
@@ -230,6 +228,16 @@ class CoprocessTest < MiniTest::Test
230
228
  assert_equal('foo', raised_error.message)
231
229
  end
232
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
+
233
241
  def test_exception_propagation_for_orphan_fiber
234
242
  raised_error = nil
235
243
  spin do
@@ -255,6 +263,15 @@ class CoprocessTest < MiniTest::Test
255
263
  assert_equal %i{foo bar baz}, result
256
264
  end
257
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
+
258
275
  def test_caller
259
276
  location = /^#{__FILE__}:#{__LINE__ + 1}/
260
277
  cp = spin do
@@ -263,7 +280,7 @@ class CoprocessTest < MiniTest::Test
263
280
  snooze
264
281
 
265
282
  caller = cp.caller
266
- assert caller[0] =~ location
283
+ assert_match location, caller[0]
267
284
  end
268
285
 
269
286
  def test_location
@@ -275,39 +292,6 @@ class CoprocessTest < MiniTest::Test
275
292
 
276
293
  assert cp.location =~ location
277
294
  end
278
- end
279
-
280
- class MailboxTest < MiniTest::Test
281
- def test_that_coprocess_can_receive_messages
282
- msgs = []
283
- coproc = spin { loop { msgs << receive } }
284
-
285
- snooze # allow coproc to start
286
-
287
- 3.times do |i|
288
- coproc << i
289
- snooze
290
- end
291
-
292
- assert_equal([0, 1, 2], msgs)
293
- ensure
294
- coproc&.stop
295
- end
296
-
297
- def test_that_multiple_messages_sent_at_once_arrive_in_order
298
- msgs = []
299
- coproc = spin { loop { msgs << receive } }
300
-
301
- snooze # allow coproc to start
302
-
303
- 3.times { |i| coproc << i }
304
-
305
- snooze
306
-
307
- assert_equal([0, 1, 2], msgs)
308
- ensure
309
- coproc&.stop
310
- end
311
295
 
312
296
  def test_when_done
313
297
  flag = nil
@@ -394,3 +378,63 @@ class MailboxTest < MiniTest::Test
394
378
  assert_equal :ok, value
395
379
  end
396
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