polyphony 0.13 → 0.14

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 (135) hide show
  1. checksums.yaml +4 -4
  2. data/.gitbook.yaml +5 -0
  3. data/.gitignore +55 -0
  4. data/.rubocop.yml +49 -0
  5. data/CHANGELOG.md +13 -2
  6. data/Gemfile +3 -0
  7. data/Gemfile.lock +31 -0
  8. data/LICENSE +21 -0
  9. data/README.md +35 -18
  10. data/Rakefile +20 -0
  11. data/TODO.md +49 -0
  12. data/docs/getting-started/getting-started.md +10 -0
  13. data/docs/getting-started/tutorial.md +2 -0
  14. data/docs/summary.md +9 -0
  15. data/examples/core/cancel.rb +10 -0
  16. data/examples/core/channel_echo.rb +43 -0
  17. data/examples/core/enumerator.rb +14 -0
  18. data/examples/core/fork.rb +22 -0
  19. data/examples/core/genserver.rb +74 -0
  20. data/examples/core/lock.rb +20 -0
  21. data/examples/core/move_on.rb +11 -0
  22. data/examples/core/move_on_twice.rb +17 -0
  23. data/examples/core/move_on_with_ensure.rb +17 -0
  24. data/examples/core/multiple_async.rb +17 -0
  25. data/examples/core/nested_async.rb +18 -0
  26. data/examples/core/nested_cancel.rb +41 -0
  27. data/examples/core/nested_multiple_async.rb +19 -0
  28. data/examples/core/next_tick.rb +13 -0
  29. data/examples/core/pulse.rb +13 -0
  30. data/examples/core/resource.rb +29 -0
  31. data/examples/core/resource_cancel.rb +34 -0
  32. data/examples/core/resource_delegate.rb +32 -0
  33. data/examples/core/sleep.rb +9 -0
  34. data/examples/core/sleep2.rb +13 -0
  35. data/examples/core/spawn.rb +15 -0
  36. data/examples/core/spawn_cancel.rb +19 -0
  37. data/examples/core/spawn_error.rb +28 -0
  38. data/examples/core/supervisor.rb +22 -0
  39. data/examples/core/supervisor_with_cancel_scope.rb +24 -0
  40. data/examples/core/supervisor_with_error.rb +23 -0
  41. data/examples/core/supervisor_with_manual_move_on.rb +25 -0
  42. data/examples/core/thread.rb +30 -0
  43. data/examples/core/thread_cancel.rb +30 -0
  44. data/examples/core/thread_pool.rb +60 -0
  45. data/examples/core/throttle.rb +17 -0
  46. data/examples/fs/read.rb +37 -0
  47. data/examples/interfaces/pg_client.rb +38 -0
  48. data/examples/interfaces/pg_pool.rb +37 -0
  49. data/examples/interfaces/pg_query.rb +32 -0
  50. data/examples/interfaces/redis_channels.rb +119 -0
  51. data/examples/interfaces/redis_client.rb +21 -0
  52. data/examples/interfaces/redis_pubsub.rb +26 -0
  53. data/examples/interfaces/redis_pubsub_perf.rb +65 -0
  54. data/examples/io/config.ru +3 -0
  55. data/examples/io/echo_client.rb +22 -0
  56. data/examples/io/echo_server.rb +14 -0
  57. data/examples/io/echo_server_with_timeout.rb +33 -0
  58. data/examples/io/echo_stdin.rb +15 -0
  59. data/examples/io/happy_eyeballs.rb +32 -0
  60. data/examples/io/http_client.rb +19 -0
  61. data/examples/io/http_server.js +24 -0
  62. data/examples/io/http_server.rb +16 -0
  63. data/examples/io/http_server_forked.rb +27 -0
  64. data/examples/io/http_server_throttled.rb +16 -0
  65. data/examples/io/http_ws_server.rb +42 -0
  66. data/examples/io/https_client.rb +17 -0
  67. data/examples/io/https_server.rb +23 -0
  68. data/examples/io/https_wss_server.rb +46 -0
  69. data/examples/io/rack_server.rb +19 -0
  70. data/examples/io/rack_server_https.rb +24 -0
  71. data/examples/io/rack_server_https_forked.rb +32 -0
  72. data/examples/io/websocket_server.rb +33 -0
  73. data/examples/io/ws_page.html +34 -0
  74. data/examples/io/wss_page.html +34 -0
  75. data/examples/performance/perf_multi_snooze.rb +21 -0
  76. data/examples/performance/perf_snooze.rb +30 -0
  77. data/examples/performance/thread-vs-fiber/polyphony_server.rb +63 -0
  78. data/examples/performance/thread-vs-fiber/threaded_server.rb +27 -0
  79. data/examples/streams/lines.rb +27 -0
  80. data/examples/streams/stdio.rb +18 -0
  81. data/ext/ev/async.c +168 -0
  82. data/ext/ev/child.c +169 -0
  83. data/ext/ev/ev.h +32 -0
  84. data/ext/ev/ev_ext.c +20 -0
  85. data/ext/ev/ev_module.c +222 -0
  86. data/ext/ev/io.c +405 -0
  87. data/ext/ev/libev.h +9 -0
  88. data/ext/ev/signal.c +119 -0
  89. data/ext/ev/timer.c +197 -0
  90. data/ext/libev/Changes +513 -0
  91. data/ext/libev/LICENSE +37 -0
  92. data/ext/libev/README +58 -0
  93. data/ext/libev/README.embed +3 -0
  94. data/ext/libev/ev.c +5214 -0
  95. data/ext/libev/ev.h +849 -0
  96. data/ext/libev/ev_epoll.c +285 -0
  97. data/ext/libev/ev_kqueue.c +218 -0
  98. data/ext/libev/ev_poll.c +151 -0
  99. data/ext/libev/ev_port.c +189 -0
  100. data/ext/libev/ev_select.c +316 -0
  101. data/ext/libev/ev_vars.h +204 -0
  102. data/ext/libev/ev_win32.c +162 -0
  103. data/ext/libev/ev_wrap.h +200 -0
  104. data/ext/libev/test_libev_win32.c +123 -0
  105. data/lib/polyphony.rb +7 -2
  106. data/lib/polyphony/core.rb +1 -1
  107. data/lib/polyphony/core/{coroutine.rb → coprocess.rb} +10 -10
  108. data/lib/polyphony/core/exceptions.rb +5 -5
  109. data/lib/polyphony/core/supervisor.rb +16 -16
  110. data/lib/polyphony/core/thread.rb +1 -1
  111. data/lib/polyphony/extensions/io.rb +43 -42
  112. data/lib/polyphony/extensions/kernel.rb +10 -34
  113. data/lib/polyphony/extensions/postgres.rb +3 -2
  114. data/lib/polyphony/extensions/redis.rb +1 -1
  115. data/lib/polyphony/extensions/socket.rb +8 -4
  116. data/lib/polyphony/extensions/ssl.rb +0 -54
  117. data/lib/polyphony/http/agent.rb +4 -10
  118. data/lib/polyphony/http/http1.rb +25 -25
  119. data/lib/polyphony/http/http1_request.rb +38 -26
  120. data/lib/polyphony/http/http2.rb +4 -5
  121. data/lib/polyphony/http/http2_request.rb +12 -18
  122. data/lib/polyphony/http/rack.rb +1 -3
  123. data/lib/polyphony/http/server.rb +9 -9
  124. data/lib/polyphony/net.rb +2 -2
  125. data/lib/polyphony/resource_pool.rb +5 -1
  126. data/lib/polyphony/version.rb +1 -1
  127. data/lib/polyphony/websocket.rb +52 -0
  128. data/polyphony.gemspec +31 -0
  129. data/test/test_coprocess.rb +131 -0
  130. data/test/test_core.rb +274 -0
  131. data/test/test_ev.rb +117 -0
  132. data/test/test_io.rb +38 -0
  133. metadata +113 -7
  134. data/lib/polyphony/core/async.rb +0 -36
  135. data/lib/polyphony/net_old.rb +0 -299
@@ -18,10 +18,8 @@ def env(request)
18
18
  { }
19
19
  end
20
20
 
21
- S_STATUS = ':status'
22
-
23
21
  def respond(request, (status_code, headers, body))
24
- headers[S_STATUS] = status_code.to_s
22
+ headers[':status'] = status_code.to_s
25
23
  body = body.first
26
24
  request.respond(body, headers)
27
25
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- export :serve, :listen
3
+ export :serve, :listener
4
4
 
5
5
  Net = import('../net')
6
6
  HTTP1 = import('./http1')
@@ -13,27 +13,27 @@ async def serve(host, port, opts = {}, &handler)
13
13
  opts[:alpn_protocols] = ALPN_PROTOCOLS
14
14
  server = Net.tcp_listen(host, port, opts)
15
15
 
16
- accept_loop(server, handler)
16
+ accept_loop(server, opts, handler)
17
17
  end
18
18
 
19
- def listen(host, port, opts = {}, &handler)
19
+ def listener(host, port, opts = {}, &handler)
20
20
  opts[:alpn_protocols] = ALPN_PROTOCOLS
21
21
  server = Net.tcp_listen(host, port, opts)
22
- proc { accept_loop(server, handler) }
22
+ proc { accept_loop(server, opts, handler) }
23
23
  end
24
24
 
25
- def accept_loop(server, handler)
25
+ def accept_loop(server, opts, handler)
26
26
  while true
27
27
  client = server.accept
28
- spawn client_task(client, handler)
28
+ spawn client_task(client, opts, handler)
29
29
  end
30
30
  rescue OpenSSL::SSL::SSLError
31
31
  retry # disregard
32
32
  end
33
33
 
34
- async def client_task(client, handler)
35
- client.no_delay
36
- protocol_module(client).run(client, handler)
34
+ async def client_task(client, opts, handler)
35
+ client.no_delay rescue nil
36
+ protocol_module(client).run(client, opts, handler)
37
37
  end
38
38
 
39
39
  def protocol_module(socket)
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  export :tcp_connect,
4
- :tcp_listen,
5
- :getaddrinfo
4
+ :tcp_listen#,
5
+ # :getaddrinfo
6
6
 
7
7
  import('./extensions/socket')
8
8
  import('./extensions/ssl')
@@ -22,7 +22,7 @@ class ResourcePool
22
22
  yield resource
23
23
  ensure
24
24
  @available << resource if resource
25
- dequeue
25
+ dequeue unless @waiting.empty?
26
26
  end
27
27
 
28
28
  def wait
@@ -43,6 +43,10 @@ class ResourcePool
43
43
  @available.shift || (@count < @limit && allocate)
44
44
  end
45
45
 
46
+ def method_missing(sym, *args, &block)
47
+ acquire { |r| r.send(sym, *args, &block) }
48
+ end
49
+
46
50
  # Allocates a resource
47
51
  # @return [any] allocated resource
48
52
  def allocate
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Polyphony
4
- VERSION = '0.13'
4
+ VERSION = '0.14'
5
5
  end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ export :handler
4
+
5
+ require 'digest/sha1'
6
+ require 'websocket'
7
+
8
+ class WebsocketConnection
9
+ def initialize(client, headers)
10
+ @client = client
11
+ @headers = headers
12
+ setup(headers)
13
+ end
14
+
15
+ S_WS_GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
16
+ UPGRADE_RESPONSE = "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: %s\r\n\r\n"
17
+
18
+ def setup(headers)
19
+ key = headers['Sec-WebSocket-Key']
20
+ @version = headers['Sec-WebSocket-Version'].to_i
21
+ accept = Digest::SHA1.base64digest([key, S_WS_GUID].join)
22
+ @client << UPGRADE_RESPONSE % accept
23
+
24
+ @reader = ::WebSocket::Frame::Incoming::Server.new(version: @version)
25
+ end
26
+
27
+ def recv
28
+ while true
29
+ data = @client.readpartial(8192)
30
+ break nil unless data
31
+
32
+ @reader << data
33
+ if msg = @reader.next
34
+ break msg.to_s
35
+ end
36
+ end
37
+ end
38
+
39
+ def send(data)
40
+ frame = ::WebSocket::Frame::Outgoing::Server.new(
41
+ version: @version, data: data, type: :text
42
+ )
43
+ @client << frame.to_s
44
+ end
45
+ alias_method :<<, :send
46
+ end
47
+
48
+ def handler(&block)
49
+ proc { |client, header|
50
+ block.(WebsocketConnection.new(client, header))
51
+ }
52
+ end
@@ -0,0 +1,31 @@
1
+ require_relative './lib/polyphony/version'
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'polyphony'
5
+ s.version = Polyphony::VERSION
6
+ s.licenses = ['MIT']
7
+ s.summary = 'Polyphony: Fiber-based Concurrency for Ruby'
8
+ s.author = 'Sharon Rosner'
9
+ s.email = 'ciconia@gmail.com'
10
+ s.files = `git ls-files`.split
11
+ s.homepage = 'http://github.com/digital-fabric/polyphony'
12
+ s.metadata = {
13
+ "source_code_uri" => "https://github.com/digital-fabric/polyphony"
14
+ }
15
+ s.rdoc_options = ["--title", "polyphony", "--main", "README.md"]
16
+ s.extra_rdoc_files = ["README.md"]
17
+ s.extensions = ["ext/ev/extconf.rb"]
18
+ s.require_paths = ["lib"]
19
+
20
+ s.add_runtime_dependency 'modulation', '0.23'
21
+
22
+ s.add_runtime_dependency 'http_parser.rb', '0.6.0'
23
+ s.add_runtime_dependency 'http-2', '0.10.0'
24
+
25
+ # s.add_runtime_dependency 'hiredis', '0.6.1'
26
+ # s.add_runtime_dependency 'pg', '1.0.0'
27
+
28
+ s.add_development_dependency 'rake-compiler', '1.0.5'
29
+ s.add_development_dependency 'minitest', '5.11.3'
30
+ s.add_development_dependency 'localhost', '1.1.4'
31
+ end
@@ -0,0 +1,131 @@
1
+ require 'minitest/autorun'
2
+ require 'modulation'
3
+
4
+ class CoprocessTest < MiniTest::Test
5
+ Core = import('../lib/polyphony/core')
6
+ Coprocess = import('../lib/polyphony/core/coprocess')
7
+ Exceptions = import('../lib/polyphony/core/exceptions')
8
+
9
+ def setup
10
+ EV.rerun
11
+ end
12
+
13
+ def test_that_main_fiber_has_associated_coprocess
14
+ assert_equal(Fiber.current, Coprocess.current.fiber)
15
+ assert_equal(Coprocess.current, Fiber.current.coprocess)
16
+ end
17
+
18
+ def test_that_new_coprocess_starts_in_suspended_state
19
+ result = nil
20
+ coproc = Coprocess.new { result = 42 }
21
+ assert_nil(result)
22
+ coproc.await
23
+ assert_equal(42, result)
24
+ end
25
+
26
+ def test_that_new_coprocess_runs_on_different_fiber
27
+ coproc = Coprocess.new { Fiber.current }
28
+ fiber = coproc.await
29
+ assert(fiber != Fiber.current)
30
+ end
31
+
32
+ def test_that_await_blocks_until_coprocess_is_done
33
+ result = nil
34
+ coproc = Coprocess.new { sleep 0.001; result = 42 }
35
+ coproc.await
36
+ assert_equal(42, result)
37
+ end
38
+
39
+ def test_that_await_returns_the_coprocess_return_value
40
+ coproc = Coprocess.new { [:foo, :bar] }
41
+ assert_equal([:foo, :bar], coproc.await)
42
+ end
43
+
44
+ def test_that_await_raises_error_raised_by_coprocess
45
+ result = nil
46
+ coproc = Coprocess.new { raise 'foo' }
47
+ begin
48
+ result = coproc.await
49
+ rescue => e
50
+ result = { error: e }
51
+ end
52
+ assert_kind_of(Hash, result)
53
+ assert_kind_of(RuntimeError, result[:error])
54
+ end
55
+
56
+ def test_that_running_coprocess_can_be_cancelled
57
+ result = []
58
+ coproc = Coprocess.new {
59
+ result << 1
60
+ sleep 0.002
61
+ result << 2
62
+ }
63
+ EV::Timer.new(0.001, 0).start { coproc.cancel! }
64
+ begin
65
+ coproc.await
66
+ rescue Exception => e
67
+ result << e
68
+ end
69
+ assert_equal(2, result.size)
70
+ assert_equal(1, result[0])
71
+ assert_kind_of(Exceptions::Cancel, result[1])
72
+ end
73
+
74
+ def test_that_running_coprocess_can_be_interrupted
75
+ # that is, stopped without exception
76
+ result = []
77
+ coproc = Coprocess.new {
78
+ result << 1
79
+ sleep 0.002
80
+ result << 2
81
+ 3
82
+ }
83
+ EV::Timer.new(0.001, 0).start { coproc.stop(42) }
84
+
85
+ await_result = coproc.await
86
+ assert_equal(1, result.size)
87
+ assert_equal(42, await_result)
88
+ end
89
+ end
90
+
91
+ class MailboxTest < MiniTest::Test
92
+ def setup
93
+ EV.rerun
94
+ end
95
+
96
+ def test_that_coprocess_can_receive_messages
97
+ msgs = []
98
+ coproc = spawn {
99
+ loop {
100
+ msgs << receive
101
+ }
102
+ }
103
+
104
+ EV.snooze # allow coproc to start
105
+
106
+ 3.times { |i| coproc << i; EV.snooze }
107
+
108
+ assert_equal([0, 1, 2], msgs)
109
+ ensure
110
+ coproc.stop
111
+ end
112
+
113
+ def test_that_multiple_messages_sent_at_once_arrive
114
+ msgs = []
115
+ coproc = spawn {
116
+ loop {
117
+ msgs << receive
118
+ }
119
+ }
120
+
121
+ EV.snooze # allow coproc to start
122
+
123
+ 3.times { |i| coproc << i }
124
+
125
+ EV.snooze
126
+
127
+ assert_equal([0, 1, 2], msgs)
128
+ ensure
129
+ coproc.stop
130
+ end
131
+ end
@@ -0,0 +1,274 @@
1
+ require 'minitest/autorun'
2
+ require 'modulation'
3
+
4
+ module CoreTests
5
+ CancelScope = import('../lib/polyphony/core/cancel_scope')
6
+ Core = import('../lib/polyphony/core')
7
+ Coprocess = import('../lib/polyphony/core/coprocess')
8
+ Exceptions = import('../lib/polyphony/core/exceptions')
9
+ Supervisor = import('../lib/polyphony/core/supervisor')
10
+
11
+ class SpawnTest < MiniTest::Test
12
+ def setup
13
+ EV.rerun
14
+ end
15
+
16
+ def test_that_spawn_returns_a_coprocess
17
+ result = nil
18
+ coprocess = spawn { result = 42 }
19
+
20
+ assert_kind_of(Coprocess, coprocess)
21
+ assert_nil(result)
22
+ suspend
23
+ assert_equal(42, result)
24
+ end
25
+
26
+ def test_that_spawn_accepts_coprocess_argument
27
+ result = nil
28
+ coprocess = Coprocess.new { result = 42 }
29
+ spawn coprocess
30
+
31
+ assert_nil(result)
32
+ suspend
33
+ assert_equal(42, result)
34
+ end
35
+
36
+ def test_that_spawned_coprocess_saves_result
37
+ coprocess = spawn { 42 }
38
+
39
+ assert_kind_of(Coprocess, coprocess)
40
+ assert_nil(coprocess.result)
41
+ suspend
42
+ assert_equal(42, coprocess.result)
43
+ end
44
+
45
+ def test_that_spawned_coprocess_can_be_interrupted
46
+ result = nil
47
+ coprocess = spawn { sleep(1); 42 }
48
+ EV.next_tick { coprocess.interrupt }
49
+ suspend
50
+ assert_nil(coprocess.result)
51
+ end
52
+ end
53
+
54
+ class CoprocessTest < MiniTest::Test
55
+ def setup
56
+ EV.rerun
57
+ end
58
+
59
+ def test_that_coprocess_can_be_awaited
60
+ result = nil
61
+ spawn do
62
+ coprocess = Coprocess.new { sleep(0.001); 42 }
63
+ result = coprocess.await
64
+ end
65
+ suspend
66
+ assert_equal(42, result)
67
+ end
68
+
69
+ def test_that_coprocess_can_be_stopped
70
+ result = nil
71
+ coprocess = spawn do
72
+ sleep(0.001)
73
+ result = 42
74
+ end
75
+ EV.next_tick { coprocess.interrupt }
76
+ suspend
77
+ assert_nil(result)
78
+ end
79
+
80
+ def test_that_coprocess_can_be_cancelled
81
+ result = nil
82
+ coprocess = spawn do
83
+ sleep(0.001)
84
+ result = 42
85
+ rescue Exceptions::Cancel => e
86
+ result = e
87
+ end
88
+ EV.next_tick { coprocess.cancel! }
89
+
90
+ suspend
91
+
92
+ assert_kind_of(Exceptions::Cancel, result)
93
+ assert_kind_of(Exceptions::Cancel, coprocess.result)
94
+ assert_nil(coprocess.running?)
95
+ end
96
+
97
+ def test_that_inner_coprocess_can_be_interrupted
98
+ result = nil
99
+ coprocess2 = nil
100
+ coprocess = spawn do
101
+ coprocess2 = spawn do
102
+ sleep(0.001)
103
+ result = 42
104
+ end
105
+ coprocess2.await
106
+ result && result += 1
107
+ end
108
+ EV.next_tick { coprocess.interrupt }
109
+ suspend
110
+ assert_nil(result)
111
+ assert_nil(coprocess.running?)
112
+ assert_nil(coprocess2.running?)
113
+ end
114
+
115
+ def test_that_inner_coprocess_can_interrupt_outer_coprocess
116
+ result, coprocess2 = nil
117
+
118
+ coprocess = spawn do
119
+ coprocess2 = spawn do
120
+ EV.next_tick { coprocess.interrupt }
121
+ sleep(0.001)
122
+ result = 42
123
+ end
124
+ coprocess2.await
125
+ result && result += 1
126
+ end
127
+
128
+ suspend
129
+
130
+ assert_nil(result)
131
+ assert_nil(coprocess.running?)
132
+ assert_nil(coprocess2.running?)
133
+ end
134
+ end
135
+
136
+ class CancelScopeTest < Minitest::Test
137
+ def setup
138
+ EV.rerun
139
+ end
140
+
141
+ def sleep_with_cancel(ctx, mode = nil)
142
+ CancelScope.new(mode: mode).call do |c|
143
+ ctx[:cancel_scope] = c
144
+ ctx[:result] = sleep(0.01)
145
+ end
146
+ end
147
+
148
+ def test_that_cancel_scope_cancels_coprocess
149
+ ctx = {}
150
+ spawn do
151
+ EV::Timer.new(0.005, 0).start { ctx[:cancel_scope]&.cancel! }
152
+ sleep_with_cancel(ctx, :cancel)
153
+ rescue Exception => e
154
+ ctx[:result] = e
155
+ end
156
+ assert_nil(ctx[:result])
157
+ # async operation will only begin on next iteration of event loop
158
+ assert_nil(ctx[:cancel_scope])
159
+
160
+ suspend
161
+ assert_kind_of(CancelScope, ctx[:cancel_scope])
162
+ assert_kind_of(Exceptions::Cancel, ctx[:result])
163
+ end
164
+
165
+ # def test_that_cancel_scope_cancels_async_op_with_stop
166
+ # ctx = {}
167
+ # spawn do
168
+ # EV::Timer.new(0, 0).start { ctx[:cancel_scope].cancel! }
169
+ # sleep_with_cancel(ctx, :stop)
170
+ # end
171
+
172
+ # suspend
173
+ # assert(ctx[:cancel_scope])
174
+ # assert_nil(ctx[:result])
175
+ # end
176
+
177
+ def test_that_cancel_after_raises_cancelled_exception
178
+ result = nil
179
+ spawn do
180
+ cancel_after(0.01) do
181
+ sleep(1000)
182
+ end
183
+ result = 42
184
+ rescue Exceptions::Cancel
185
+ result = :cancelled
186
+ end
187
+ suspend
188
+ assert_equal(:cancelled, result)
189
+ end
190
+
191
+ def test_that_cancel_scopes_can_be_nested
192
+ inner_result = nil
193
+ outer_result = nil
194
+ spawn do
195
+ move_on_after(0.01) do
196
+ move_on_after(0.02) do
197
+ sleep(1000)
198
+ end
199
+ inner_result = 42
200
+ end
201
+ outer_result = 42
202
+ end
203
+ suspend
204
+ assert_nil(inner_result)
205
+ assert_equal(42, outer_result)
206
+
207
+ EV.rerun
208
+
209
+ outer_result = nil
210
+ spawn do
211
+ move_on_after(0.02) do
212
+ move_on_after(0.01) do
213
+ sleep(1000)
214
+ end
215
+ inner_result = 42
216
+ end
217
+ outer_result = 42
218
+ end
219
+ suspend
220
+ assert_equal(42, inner_result)
221
+ assert_equal(42, outer_result)
222
+ end
223
+ end
224
+
225
+ class SupervisorTest < MiniTest::Test
226
+ def setup
227
+ EV.rerun
228
+ end
229
+
230
+ def sleep_and_set(ctx, idx)
231
+ proc do
232
+ sleep(0.001 * idx)
233
+ ctx[idx] = true
234
+ end
235
+ end
236
+
237
+ def parallel_sleep(ctx)
238
+ supervise do |s|
239
+ (1..3).each { |idx| s.spawn sleep_and_set(ctx, idx) }
240
+ end
241
+ end
242
+
243
+ def test_that_supervisor_waits_for_all_nested_coprocesss_to_complete
244
+ ctx = {}
245
+ spawn do
246
+ parallel_sleep(ctx)
247
+ end
248
+ suspend
249
+ assert(ctx[1])
250
+ assert(ctx[2])
251
+ assert(ctx[3])
252
+ end
253
+
254
+ def test_that_supervisor_can_add_coprocesss_after_having_started
255
+ result = []
256
+ spawn do
257
+ supervisor = Supervisor.new
258
+ 3.times do |i|
259
+ spawn do
260
+ sleep(0.001)
261
+ supervisor.spawn do
262
+ sleep(0.001)
263
+ result << i
264
+ end
265
+ end
266
+ end
267
+ supervisor.await
268
+ end
269
+
270
+ suspend
271
+ assert_equal([0, 1, 2], result)
272
+ end
273
+ end
274
+ end