polyphony 0.13 → 0.14

Sign up to get free protection for your applications and to get access to all the features.
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