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.
- checksums.yaml +4 -4
- data/.gitbook.yaml +5 -0
- data/.gitignore +55 -0
- data/.rubocop.yml +49 -0
- data/CHANGELOG.md +13 -2
- data/Gemfile +3 -0
- data/Gemfile.lock +31 -0
- data/LICENSE +21 -0
- data/README.md +35 -18
- data/Rakefile +20 -0
- data/TODO.md +49 -0
- data/docs/getting-started/getting-started.md +10 -0
- data/docs/getting-started/tutorial.md +2 -0
- data/docs/summary.md +9 -0
- data/examples/core/cancel.rb +10 -0
- data/examples/core/channel_echo.rb +43 -0
- data/examples/core/enumerator.rb +14 -0
- data/examples/core/fork.rb +22 -0
- data/examples/core/genserver.rb +74 -0
- data/examples/core/lock.rb +20 -0
- data/examples/core/move_on.rb +11 -0
- data/examples/core/move_on_twice.rb +17 -0
- data/examples/core/move_on_with_ensure.rb +17 -0
- data/examples/core/multiple_async.rb +17 -0
- data/examples/core/nested_async.rb +18 -0
- data/examples/core/nested_cancel.rb +41 -0
- data/examples/core/nested_multiple_async.rb +19 -0
- data/examples/core/next_tick.rb +13 -0
- data/examples/core/pulse.rb +13 -0
- data/examples/core/resource.rb +29 -0
- data/examples/core/resource_cancel.rb +34 -0
- data/examples/core/resource_delegate.rb +32 -0
- data/examples/core/sleep.rb +9 -0
- data/examples/core/sleep2.rb +13 -0
- data/examples/core/spawn.rb +15 -0
- data/examples/core/spawn_cancel.rb +19 -0
- data/examples/core/spawn_error.rb +28 -0
- data/examples/core/supervisor.rb +22 -0
- data/examples/core/supervisor_with_cancel_scope.rb +24 -0
- data/examples/core/supervisor_with_error.rb +23 -0
- data/examples/core/supervisor_with_manual_move_on.rb +25 -0
- data/examples/core/thread.rb +30 -0
- data/examples/core/thread_cancel.rb +30 -0
- data/examples/core/thread_pool.rb +60 -0
- data/examples/core/throttle.rb +17 -0
- data/examples/fs/read.rb +37 -0
- data/examples/interfaces/pg_client.rb +38 -0
- data/examples/interfaces/pg_pool.rb +37 -0
- data/examples/interfaces/pg_query.rb +32 -0
- data/examples/interfaces/redis_channels.rb +119 -0
- data/examples/interfaces/redis_client.rb +21 -0
- data/examples/interfaces/redis_pubsub.rb +26 -0
- data/examples/interfaces/redis_pubsub_perf.rb +65 -0
- data/examples/io/config.ru +3 -0
- data/examples/io/echo_client.rb +22 -0
- data/examples/io/echo_server.rb +14 -0
- data/examples/io/echo_server_with_timeout.rb +33 -0
- data/examples/io/echo_stdin.rb +15 -0
- data/examples/io/happy_eyeballs.rb +32 -0
- data/examples/io/http_client.rb +19 -0
- data/examples/io/http_server.js +24 -0
- data/examples/io/http_server.rb +16 -0
- data/examples/io/http_server_forked.rb +27 -0
- data/examples/io/http_server_throttled.rb +16 -0
- data/examples/io/http_ws_server.rb +42 -0
- data/examples/io/https_client.rb +17 -0
- data/examples/io/https_server.rb +23 -0
- data/examples/io/https_wss_server.rb +46 -0
- data/examples/io/rack_server.rb +19 -0
- data/examples/io/rack_server_https.rb +24 -0
- data/examples/io/rack_server_https_forked.rb +32 -0
- data/examples/io/websocket_server.rb +33 -0
- data/examples/io/ws_page.html +34 -0
- data/examples/io/wss_page.html +34 -0
- data/examples/performance/perf_multi_snooze.rb +21 -0
- data/examples/performance/perf_snooze.rb +30 -0
- data/examples/performance/thread-vs-fiber/polyphony_server.rb +63 -0
- data/examples/performance/thread-vs-fiber/threaded_server.rb +27 -0
- data/examples/streams/lines.rb +27 -0
- data/examples/streams/stdio.rb +18 -0
- data/ext/ev/async.c +168 -0
- data/ext/ev/child.c +169 -0
- data/ext/ev/ev.h +32 -0
- data/ext/ev/ev_ext.c +20 -0
- data/ext/ev/ev_module.c +222 -0
- data/ext/ev/io.c +405 -0
- data/ext/ev/libev.h +9 -0
- data/ext/ev/signal.c +119 -0
- data/ext/ev/timer.c +197 -0
- data/ext/libev/Changes +513 -0
- data/ext/libev/LICENSE +37 -0
- data/ext/libev/README +58 -0
- data/ext/libev/README.embed +3 -0
- data/ext/libev/ev.c +5214 -0
- data/ext/libev/ev.h +849 -0
- data/ext/libev/ev_epoll.c +285 -0
- data/ext/libev/ev_kqueue.c +218 -0
- data/ext/libev/ev_poll.c +151 -0
- data/ext/libev/ev_port.c +189 -0
- data/ext/libev/ev_select.c +316 -0
- data/ext/libev/ev_vars.h +204 -0
- data/ext/libev/ev_win32.c +162 -0
- data/ext/libev/ev_wrap.h +200 -0
- data/ext/libev/test_libev_win32.c +123 -0
- data/lib/polyphony.rb +7 -2
- data/lib/polyphony/core.rb +1 -1
- data/lib/polyphony/core/{coroutine.rb → coprocess.rb} +10 -10
- data/lib/polyphony/core/exceptions.rb +5 -5
- data/lib/polyphony/core/supervisor.rb +16 -16
- data/lib/polyphony/core/thread.rb +1 -1
- data/lib/polyphony/extensions/io.rb +43 -42
- data/lib/polyphony/extensions/kernel.rb +10 -34
- data/lib/polyphony/extensions/postgres.rb +3 -2
- data/lib/polyphony/extensions/redis.rb +1 -1
- data/lib/polyphony/extensions/socket.rb +8 -4
- data/lib/polyphony/extensions/ssl.rb +0 -54
- data/lib/polyphony/http/agent.rb +4 -10
- data/lib/polyphony/http/http1.rb +25 -25
- data/lib/polyphony/http/http1_request.rb +38 -26
- data/lib/polyphony/http/http2.rb +4 -5
- data/lib/polyphony/http/http2_request.rb +12 -18
- data/lib/polyphony/http/rack.rb +1 -3
- data/lib/polyphony/http/server.rb +9 -9
- data/lib/polyphony/net.rb +2 -2
- data/lib/polyphony/resource_pool.rb +5 -1
- data/lib/polyphony/version.rb +1 -1
- data/lib/polyphony/websocket.rb +52 -0
- data/polyphony.gemspec +31 -0
- data/test/test_coprocess.rb +131 -0
- data/test/test_core.rb +274 -0
- data/test/test_ev.rb +117 -0
- data/test/test_io.rb +38 -0
- metadata +113 -7
- data/lib/polyphony/core/async.rb +0 -36
- data/lib/polyphony/net_old.rb +0 -299
data/lib/polyphony/http/rack.rb
CHANGED
|
@@ -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[
|
|
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, :
|
|
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
|
|
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)
|
data/lib/polyphony/net.rb
CHANGED
|
@@ -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
|
data/lib/polyphony/version.rb
CHANGED
|
@@ -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
|
data/polyphony.gemspec
ADDED
|
@@ -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
|
data/test/test_core.rb
ADDED
|
@@ -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
|