polyphony 0.43.8
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 +7 -0
- data/.gitbook.yaml +4 -0
- data/.github/workflows/test.yml +29 -0
- data/.gitignore +59 -0
- data/.rubocop.yml +175 -0
- data/CHANGELOG.md +393 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +141 -0
- data/LICENSE +21 -0
- data/README.md +51 -0
- data/Rakefile +26 -0
- data/TODO.md +201 -0
- data/bin/polyphony-debug +87 -0
- data/docs/_config.yml +64 -0
- data/docs/_includes/head.html +40 -0
- data/docs/_includes/title.html +1 -0
- data/docs/_sass/custom/custom.scss +10 -0
- data/docs/_sass/overrides.scss +0 -0
- data/docs/_user-guide/all-about-timers.md +126 -0
- data/docs/_user-guide/index.md +9 -0
- data/docs/_user-guide/web-server.md +136 -0
- data/docs/api-reference/exception.md +27 -0
- data/docs/api-reference/fiber.md +425 -0
- data/docs/api-reference/index.md +9 -0
- data/docs/api-reference/io.md +36 -0
- data/docs/api-reference/object.md +99 -0
- data/docs/api-reference/polyphony-baseexception.md +33 -0
- data/docs/api-reference/polyphony-cancel.md +26 -0
- data/docs/api-reference/polyphony-moveon.md +24 -0
- data/docs/api-reference/polyphony-net.md +20 -0
- data/docs/api-reference/polyphony-process.md +28 -0
- data/docs/api-reference/polyphony-resourcepool.md +59 -0
- data/docs/api-reference/polyphony-restart.md +18 -0
- data/docs/api-reference/polyphony-terminate.md +18 -0
- data/docs/api-reference/polyphony-threadpool.md +67 -0
- data/docs/api-reference/polyphony-throttler.md +77 -0
- data/docs/api-reference/polyphony.md +36 -0
- data/docs/api-reference/thread.md +88 -0
- data/docs/assets/img/echo-fibers.svg +1 -0
- data/docs/assets/img/sleeping-fiber.svg +1 -0
- data/docs/faq.md +195 -0
- data/docs/favicon.ico +0 -0
- data/docs/getting-started/index.md +10 -0
- data/docs/getting-started/installing.md +34 -0
- data/docs/getting-started/overview.md +486 -0
- data/docs/getting-started/tutorial.md +359 -0
- data/docs/index.md +94 -0
- data/docs/main-concepts/concurrency.md +151 -0
- data/docs/main-concepts/design-principles.md +161 -0
- data/docs/main-concepts/exception-handling.md +291 -0
- data/docs/main-concepts/extending.md +89 -0
- data/docs/main-concepts/fiber-scheduling.md +197 -0
- data/docs/main-concepts/index.md +9 -0
- data/docs/polyphony-logo.png +0 -0
- data/examples/adapters/concurrent-ruby.rb +9 -0
- data/examples/adapters/pg_client.rb +36 -0
- data/examples/adapters/pg_notify.rb +35 -0
- data/examples/adapters/pg_pool.rb +43 -0
- data/examples/adapters/pg_transaction.rb +31 -0
- data/examples/adapters/redis_blpop.rb +12 -0
- data/examples/adapters/redis_channels.rb +122 -0
- data/examples/adapters/redis_client.rb +19 -0
- data/examples/adapters/redis_pubsub.rb +26 -0
- data/examples/adapters/redis_pubsub_perf.rb +68 -0
- data/examples/core/01-spinning-up-fibers.rb +18 -0
- data/examples/core/02-awaiting-fibers.rb +20 -0
- data/examples/core/03-interrupting.rb +39 -0
- data/examples/core/04-handling-signals.rb +19 -0
- data/examples/core/xx-agent.rb +102 -0
- data/examples/core/xx-at_exit.rb +29 -0
- data/examples/core/xx-caller.rb +12 -0
- data/examples/core/xx-channels.rb +45 -0
- data/examples/core/xx-daemon.rb +14 -0
- data/examples/core/xx-deadlock.rb +8 -0
- data/examples/core/xx-deferring-an-operation.rb +14 -0
- data/examples/core/xx-erlang-style-genserver.rb +81 -0
- data/examples/core/xx-exception-backtrace.rb +40 -0
- data/examples/core/xx-fork-cleanup.rb +22 -0
- data/examples/core/xx-fork-spin.rb +42 -0
- data/examples/core/xx-fork-terminate.rb +27 -0
- data/examples/core/xx-forking.rb +24 -0
- data/examples/core/xx-move_on.rb +23 -0
- data/examples/core/xx-pingpong.rb +18 -0
- data/examples/core/xx-queue-async.rb +120 -0
- data/examples/core/xx-readpartial.rb +18 -0
- data/examples/core/xx-recurrent-timer.rb +12 -0
- data/examples/core/xx-resource_delegate.rb +31 -0
- data/examples/core/xx-signals.rb +16 -0
- data/examples/core/xx-sleep-forever.rb +9 -0
- data/examples/core/xx-sleeping.rb +25 -0
- data/examples/core/xx-snooze-starve.rb +16 -0
- data/examples/core/xx-spin-fork.rb +49 -0
- data/examples/core/xx-spin_error_backtrace.rb +33 -0
- data/examples/core/xx-state-machine.rb +51 -0
- data/examples/core/xx-stop.rb +20 -0
- data/examples/core/xx-supervise-process.rb +30 -0
- data/examples/core/xx-supervisors.rb +21 -0
- data/examples/core/xx-thread-selector-sleep.rb +51 -0
- data/examples/core/xx-thread-selector-snooze.rb +46 -0
- data/examples/core/xx-thread-sleep.rb +17 -0
- data/examples/core/xx-thread-snooze.rb +34 -0
- data/examples/core/xx-thread_pool.rb +17 -0
- data/examples/core/xx-throttling.rb +18 -0
- data/examples/core/xx-timeout.rb +10 -0
- data/examples/core/xx-timer-gc.rb +17 -0
- data/examples/core/xx-trace.rb +79 -0
- data/examples/core/xx-using-a-mutex.rb +21 -0
- data/examples/core/xx-worker-thread.rb +30 -0
- data/examples/io/tunnel.rb +48 -0
- data/examples/io/xx-backticks.rb +11 -0
- data/examples/io/xx-echo_client.rb +25 -0
- data/examples/io/xx-echo_client_from_stdin.rb +21 -0
- data/examples/io/xx-echo_pipe.rb +16 -0
- data/examples/io/xx-echo_server.rb +17 -0
- data/examples/io/xx-echo_server_with_timeout.rb +34 -0
- data/examples/io/xx-echo_stdin.rb +14 -0
- data/examples/io/xx-happy-eyeballs.rb +36 -0
- data/examples/io/xx-httparty.rb +38 -0
- data/examples/io/xx-irb.rb +17 -0
- data/examples/io/xx-net-http.rb +15 -0
- data/examples/io/xx-open.rb +16 -0
- data/examples/io/xx-switch.rb +15 -0
- data/examples/io/xx-system.rb +11 -0
- data/examples/io/xx-tcpserver.rb +15 -0
- data/examples/io/xx-tcpsocket.rb +18 -0
- data/examples/io/xx-zip.rb +19 -0
- data/examples/performance/fiber_transfer.rb +47 -0
- data/examples/performance/fs_read.rb +38 -0
- data/examples/performance/mem-usage.rb +56 -0
- data/examples/performance/messaging.rb +29 -0
- data/examples/performance/multi_snooze.rb +33 -0
- data/examples/performance/snooze.rb +39 -0
- data/examples/performance/snooze_raw.rb +39 -0
- data/examples/performance/thread-vs-fiber/polyphony_mt_server.rb +74 -0
- data/examples/performance/thread-vs-fiber/polyphony_server.rb +45 -0
- data/examples/performance/thread-vs-fiber/polyphony_server_read_loop.rb +58 -0
- data/examples/performance/thread-vs-fiber/threaded_server.rb +27 -0
- data/examples/performance/thread-vs-fiber/xx-httparty_multi.rb +36 -0
- data/examples/performance/thread-vs-fiber/xx-httparty_threaded.rb +29 -0
- data/examples/performance/thread_pool_perf.rb +63 -0
- data/examples/performance/xx-array.rb +11 -0
- data/examples/performance/xx-fiber-switch.rb +9 -0
- data/examples/performance/xx-snooze.rb +15 -0
- data/examples/xx-spin.rb +32 -0
- data/ext/libev/Changes +548 -0
- data/ext/libev/LICENSE +37 -0
- data/ext/libev/README +59 -0
- data/ext/libev/README.embed +3 -0
- data/ext/libev/ev.c +5279 -0
- data/ext/libev/ev.h +856 -0
- data/ext/libev/ev_epoll.c +296 -0
- data/ext/libev/ev_kqueue.c +224 -0
- data/ext/libev/ev_linuxaio.c +642 -0
- data/ext/libev/ev_poll.c +156 -0
- data/ext/libev/ev_port.c +192 -0
- data/ext/libev/ev_select.c +316 -0
- data/ext/libev/ev_vars.h +215 -0
- data/ext/libev/ev_win32.c +162 -0
- data/ext/libev/ev_wrap.h +216 -0
- data/ext/libev/test_libev_win32.c +123 -0
- data/ext/polyphony/extconf.rb +20 -0
- data/ext/polyphony/fiber.c +109 -0
- data/ext/polyphony/libev.c +2 -0
- data/ext/polyphony/libev.h +9 -0
- data/ext/polyphony/libev_agent.c +882 -0
- data/ext/polyphony/polyphony.c +71 -0
- data/ext/polyphony/polyphony.h +97 -0
- data/ext/polyphony/polyphony_ext.c +21 -0
- data/ext/polyphony/queue.c +168 -0
- data/ext/polyphony/ring_buffer.c +96 -0
- data/ext/polyphony/ring_buffer.h +28 -0
- data/ext/polyphony/thread.c +208 -0
- data/ext/polyphony/tracing.c +11 -0
- data/lib/polyphony.rb +136 -0
- data/lib/polyphony/adapters/fs.rb +19 -0
- data/lib/polyphony/adapters/irb.rb +52 -0
- data/lib/polyphony/adapters/postgres.rb +110 -0
- data/lib/polyphony/adapters/process.rb +33 -0
- data/lib/polyphony/adapters/redis.rb +67 -0
- data/lib/polyphony/adapters/trace.rb +138 -0
- data/lib/polyphony/core/channel.rb +46 -0
- data/lib/polyphony/core/exceptions.rb +36 -0
- data/lib/polyphony/core/global_api.rb +124 -0
- data/lib/polyphony/core/resource_pool.rb +117 -0
- data/lib/polyphony/core/sync.rb +21 -0
- data/lib/polyphony/core/thread_pool.rb +64 -0
- data/lib/polyphony/core/throttler.rb +41 -0
- data/lib/polyphony/event.rb +17 -0
- data/lib/polyphony/extensions/core.rb +174 -0
- data/lib/polyphony/extensions/fiber.rb +379 -0
- data/lib/polyphony/extensions/io.rb +221 -0
- data/lib/polyphony/extensions/openssl.rb +81 -0
- data/lib/polyphony/extensions/socket.rb +150 -0
- data/lib/polyphony/extensions/thread.rb +108 -0
- data/lib/polyphony/net.rb +77 -0
- data/lib/polyphony/version.rb +5 -0
- data/polyphony.gemspec +40 -0
- data/test/coverage.rb +54 -0
- data/test/eg.rb +27 -0
- data/test/helper.rb +56 -0
- data/test/q.rb +24 -0
- data/test/run.rb +5 -0
- data/test/stress.rb +25 -0
- data/test/test_agent.rb +130 -0
- data/test/test_event.rb +59 -0
- data/test/test_ext.rb +196 -0
- data/test/test_fiber.rb +988 -0
- data/test/test_global_api.rb +352 -0
- data/test/test_io.rb +249 -0
- data/test/test_kernel.rb +57 -0
- data/test/test_process_supervision.rb +46 -0
- data/test/test_queue.rb +112 -0
- data/test/test_resource_pool.rb +138 -0
- data/test/test_signal.rb +100 -0
- data/test/test_socket.rb +34 -0
- data/test/test_supervise.rb +103 -0
- data/test/test_thread.rb +170 -0
- data/test/test_thread_pool.rb +101 -0
- data/test/test_throttler.rb +50 -0
- data/test/test_trace.rb +68 -0
- metadata +482 -0
data/test/test_event.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'helper'
|
4
|
+
|
5
|
+
class EventTest < MiniTest::Test
|
6
|
+
def test_that_event_receives_signal_across_threads
|
7
|
+
count = 0
|
8
|
+
a = Polyphony::Event.new
|
9
|
+
spin {
|
10
|
+
a.await
|
11
|
+
count += 1
|
12
|
+
}
|
13
|
+
snooze
|
14
|
+
t = Thread.new do
|
15
|
+
orig_sleep 0.001
|
16
|
+
a.signal
|
17
|
+
end
|
18
|
+
suspend
|
19
|
+
assert_equal 1, count
|
20
|
+
ensure
|
21
|
+
t&.kill
|
22
|
+
t&.join
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_that_event_coalesces_signals
|
26
|
+
count = 0
|
27
|
+
a = Polyphony::Event.new
|
28
|
+
|
29
|
+
coproc = spin {
|
30
|
+
loop {
|
31
|
+
a.await
|
32
|
+
count += 1
|
33
|
+
spin { coproc.stop }
|
34
|
+
}
|
35
|
+
}
|
36
|
+
snooze
|
37
|
+
t = Thread.new do
|
38
|
+
orig_sleep 0.001
|
39
|
+
3.times { a.signal }
|
40
|
+
end
|
41
|
+
|
42
|
+
coproc.await
|
43
|
+
assert_equal 1, count
|
44
|
+
ensure
|
45
|
+
t&.kill
|
46
|
+
t&.join
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_exception_while_waiting_for_event
|
50
|
+
e = Polyphony::Event.new
|
51
|
+
|
52
|
+
f = spin { e.await }
|
53
|
+
g = spin { f.raise 'foo' }
|
54
|
+
|
55
|
+
assert_raises(RuntimeError) do
|
56
|
+
f.await
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/test/test_ext.rb
ADDED
@@ -0,0 +1,196 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'helper'
|
4
|
+
|
5
|
+
class ExceptionTest < MiniTest::Test
|
6
|
+
def test_sanitize
|
7
|
+
prev_disable = Exception.__disable_sanitized_backtrace__
|
8
|
+
Exception.__disable_sanitized_backtrace__ = false
|
9
|
+
|
10
|
+
begin
|
11
|
+
lineno = __LINE__ + 1
|
12
|
+
spin { raise 'foo' }
|
13
|
+
suspend
|
14
|
+
rescue => e
|
15
|
+
end
|
16
|
+
|
17
|
+
assert_kind_of Exception, e
|
18
|
+
backtrace = e.backtrace
|
19
|
+
location = "#{__FILE__}:#{lineno}"
|
20
|
+
assert_match /#{location}/, backtrace[0]
|
21
|
+
polyphony_re = /^#{Exception::POLYPHONY_DIR}/
|
22
|
+
assert_equal [], backtrace.select { |l| l =~ polyphony_re }
|
23
|
+
|
24
|
+
Exception.__disable_sanitized_backtrace__ = true
|
25
|
+
begin
|
26
|
+
lineno = __LINE__ + 1
|
27
|
+
spin { raise 'foo' }
|
28
|
+
suspend
|
29
|
+
rescue => e
|
30
|
+
end
|
31
|
+
|
32
|
+
assert_kind_of Exception, e
|
33
|
+
backtrace = e.backtrace
|
34
|
+
location = "#{__FILE__}:#{lineno}"
|
35
|
+
assert_match /#{location}/, backtrace[0]
|
36
|
+
assert_match /lib\/polyphony\/extensions\/fiber.rb/, backtrace[1]
|
37
|
+
assert_match /lib\/polyphony\/extensions\/fiber.rb/, backtrace[2]
|
38
|
+
ensure
|
39
|
+
Exception.__disable_sanitized_backtrace__ = prev_disable
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
class ProcessTest < MiniTest::Test
|
45
|
+
def test_detach
|
46
|
+
pid = Polyphony.fork { sleep 0.05; exit! 42 }
|
47
|
+
buffer = []
|
48
|
+
spin { 3.times { |i| buffer << i; snooze } }
|
49
|
+
w = Process.detach(pid)
|
50
|
+
|
51
|
+
assert_kind_of Fiber, w
|
52
|
+
result = w.await
|
53
|
+
|
54
|
+
assert_equal [0, 1, 2], buffer
|
55
|
+
assert_equal [pid, 42], result
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class KernelTest < MiniTest::Test
|
60
|
+
def test_backticks
|
61
|
+
buffer = []
|
62
|
+
spin { 3.times { |i| buffer << i; snooze } }
|
63
|
+
data = `sleep 0.01; echo hello`
|
64
|
+
|
65
|
+
assert_equal [0, 1, 2], buffer
|
66
|
+
assert_equal "hello\n", data
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_backticks_stderr
|
70
|
+
prev_stderr = $stderr
|
71
|
+
$stderr = err_io = StringIO.new
|
72
|
+
|
73
|
+
data = `>&2 echo "error"`
|
74
|
+
$stderr.rewind
|
75
|
+
$stderr = prev_stderr
|
76
|
+
|
77
|
+
assert_equal '', data
|
78
|
+
assert_equal "error\n", err_io.read
|
79
|
+
ensure
|
80
|
+
$stderr = prev_stderr
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_gets
|
84
|
+
prev_stdin = $stdin
|
85
|
+
i, o = IO.pipe
|
86
|
+
$stdin = i
|
87
|
+
|
88
|
+
spin { o << "hello\n" }
|
89
|
+
s = gets
|
90
|
+
|
91
|
+
assert_equal "hello\n", s
|
92
|
+
ensure
|
93
|
+
$stdin = prev_stdin
|
94
|
+
end
|
95
|
+
|
96
|
+
def test_multiline_gets
|
97
|
+
prev_stdin = $stdin
|
98
|
+
i, o = IO.pipe
|
99
|
+
$stdin = i
|
100
|
+
|
101
|
+
spin do
|
102
|
+
o << "hello\n"
|
103
|
+
o << "world\n"
|
104
|
+
o << "nice\n"
|
105
|
+
o << "to\n"
|
106
|
+
o << "meet\n"
|
107
|
+
o << "you\n"
|
108
|
+
end
|
109
|
+
|
110
|
+
s = +''
|
111
|
+
6.times { s << gets }
|
112
|
+
|
113
|
+
assert_equal "hello\nworld\nnice\nto\nmeet\nyou\n", s
|
114
|
+
ensure
|
115
|
+
$stdin = prev_stdin
|
116
|
+
end
|
117
|
+
|
118
|
+
def test_gets_from_argv
|
119
|
+
prev_stdin = $stdin
|
120
|
+
|
121
|
+
ARGV << __FILE__
|
122
|
+
ARGV << __FILE__
|
123
|
+
|
124
|
+
contents = IO.read(__FILE__).lines
|
125
|
+
count = contents.size
|
126
|
+
|
127
|
+
buffer = []
|
128
|
+
(count * 2).times { |i| s = gets; buffer << s }
|
129
|
+
assert_equal contents * 2, buffer
|
130
|
+
|
131
|
+
i, o = IO.pipe
|
132
|
+
$stdin = i
|
133
|
+
|
134
|
+
spin { o << "hello\n" }
|
135
|
+
s = gets
|
136
|
+
|
137
|
+
assert_equal "hello\n", s
|
138
|
+
ensure
|
139
|
+
$stdin = prev_stdin
|
140
|
+
end
|
141
|
+
|
142
|
+
def test_gets_from_bad_argv
|
143
|
+
prev_stdin = $stdin
|
144
|
+
|
145
|
+
ARGV << 'foobar'
|
146
|
+
|
147
|
+
begin
|
148
|
+
gets
|
149
|
+
rescue => e
|
150
|
+
end
|
151
|
+
|
152
|
+
assert_kind_of Errno::ENOENT, e
|
153
|
+
ensure
|
154
|
+
$stdin = prev_stdin
|
155
|
+
end
|
156
|
+
|
157
|
+
def test_system
|
158
|
+
prev_stdout = $stdout
|
159
|
+
$stdout = out_io = StringIO.new
|
160
|
+
|
161
|
+
buffer = []
|
162
|
+
spin { 3.times { |i| buffer << i; snooze } }
|
163
|
+
system('sleep 0.01; echo hello')
|
164
|
+
out_io.rewind
|
165
|
+
$stdout = prev_stdout
|
166
|
+
|
167
|
+
assert_equal [0, 1, 2], buffer
|
168
|
+
assert_equal "hello\n", out_io.read
|
169
|
+
ensure
|
170
|
+
$stdout = prev_stdout
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
class TimeoutTest < MiniTest::Test
|
175
|
+
def test_that_timeout_yields_to_other_fibers
|
176
|
+
buffer = []
|
177
|
+
spin { 3.times { |i| buffer << i; snooze } }
|
178
|
+
assert_raises(Timeout::Error) { Timeout.timeout(0.05) { sleep 1 } }
|
179
|
+
assert_equal [0, 1, 2], buffer
|
180
|
+
end
|
181
|
+
|
182
|
+
class MyTimeout < Exception
|
183
|
+
end
|
184
|
+
|
185
|
+
def test_that_timeout_method_accepts_custom_error_class_and_message
|
186
|
+
buffer = []
|
187
|
+
spin { 3.times { |i| buffer << i; snooze } }
|
188
|
+
begin
|
189
|
+
Timeout.timeout(0.05, MyTimeout, 'foo') { sleep 1 }
|
190
|
+
rescue Exception => e
|
191
|
+
end
|
192
|
+
|
193
|
+
assert_kind_of MyTimeout, e
|
194
|
+
assert_equal 'foo', e.message
|
195
|
+
end
|
196
|
+
end
|
data/test/test_fiber.rb
ADDED
@@ -0,0 +1,988 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'helper'
|
4
|
+
|
5
|
+
class FiberTest < MiniTest::Test
|
6
|
+
def test_spin_initial_state
|
7
|
+
result = nil
|
8
|
+
f = Fiber.current.spin { result = 42 }
|
9
|
+
assert_nil result
|
10
|
+
f.await
|
11
|
+
assert_equal 42, result
|
12
|
+
ensure
|
13
|
+
f&.stop
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_children_parent
|
17
|
+
assert_nil Fiber.current.parent
|
18
|
+
|
19
|
+
f1 = spin {}
|
20
|
+
f2 = spin {}
|
21
|
+
|
22
|
+
assert_equal [f1, f2], Fiber.current.children
|
23
|
+
assert_equal Fiber.current, f1.parent
|
24
|
+
assert_equal Fiber.current, f2.parent
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_spin_from_different_fiber
|
28
|
+
f1 = spin { sleep }
|
29
|
+
f2 = f1.spin { sleep }
|
30
|
+
assert_equal f1, f2.parent
|
31
|
+
assert_equal [f2], f1.children
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_await
|
35
|
+
result = nil
|
36
|
+
f = Fiber.current.spin do
|
37
|
+
snooze
|
38
|
+
result = 42
|
39
|
+
end
|
40
|
+
f.await
|
41
|
+
assert_equal 42, result
|
42
|
+
ensure
|
43
|
+
f&.stop
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_await_from_multiple_fibers
|
47
|
+
buffer = []
|
48
|
+
f1 = spin {
|
49
|
+
sleep 0.02
|
50
|
+
buffer << :foo
|
51
|
+
}
|
52
|
+
f2 = spin {
|
53
|
+
f1.await
|
54
|
+
buffer << :bar
|
55
|
+
}
|
56
|
+
f3 = spin {
|
57
|
+
f1.await
|
58
|
+
buffer << :baz
|
59
|
+
}
|
60
|
+
Fiber.await(f2, f3)
|
61
|
+
assert_equal [:foo, :bar, :baz], buffer
|
62
|
+
assert_equal 0, Fiber.current.children.size
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_await_from_multiple_fibers_with_interruption
|
66
|
+
buffer = []
|
67
|
+
f1 = spin {
|
68
|
+
sleep 0.02
|
69
|
+
buffer << :foo
|
70
|
+
}
|
71
|
+
f2 = spin {
|
72
|
+
f1.await
|
73
|
+
buffer << :bar
|
74
|
+
}
|
75
|
+
f3 = spin {
|
76
|
+
f1.await
|
77
|
+
buffer << :baz
|
78
|
+
}
|
79
|
+
snooze
|
80
|
+
f2.stop
|
81
|
+
f3.stop
|
82
|
+
snooze
|
83
|
+
f1.stop
|
84
|
+
|
85
|
+
snooze
|
86
|
+
assert_equal [], Fiber.current.children
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_schedule
|
90
|
+
values = []
|
91
|
+
fibers = (0..2).map { |i| spin { suspend; values << i } }
|
92
|
+
snooze
|
93
|
+
|
94
|
+
fibers[0].schedule
|
95
|
+
assert_equal [], values
|
96
|
+
|
97
|
+
snooze
|
98
|
+
|
99
|
+
assert_equal [0], values
|
100
|
+
assert_equal :dead, fibers[0].state
|
101
|
+
|
102
|
+
fibers[1].schedule
|
103
|
+
fibers[2].schedule
|
104
|
+
|
105
|
+
assert_equal [0], values
|
106
|
+
snooze
|
107
|
+
assert_equal [0, 1, 2], values
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_cross_thread_schedule
|
111
|
+
buffer = []
|
112
|
+
worker_fiber = nil
|
113
|
+
async = Polyphony::Event.new
|
114
|
+
worker = Thread.new do
|
115
|
+
worker_fiber = Fiber.current
|
116
|
+
async.signal
|
117
|
+
suspend
|
118
|
+
buffer << :foo
|
119
|
+
end
|
120
|
+
|
121
|
+
async.await
|
122
|
+
assert worker_fiber
|
123
|
+
worker_fiber.schedule
|
124
|
+
worker.join
|
125
|
+
assert_equal [:foo], buffer
|
126
|
+
ensure
|
127
|
+
worker&.kill
|
128
|
+
worker&.join
|
129
|
+
end
|
130
|
+
|
131
|
+
def test_ev_loop_anti_starve_mechanism
|
132
|
+
async = Polyphony::Event.new
|
133
|
+
t = Thread.new do
|
134
|
+
f = spin_loop { snooze }
|
135
|
+
sleep 0.001
|
136
|
+
async.signal(:foo)
|
137
|
+
end
|
138
|
+
|
139
|
+
result = move_on_after(1) { async.await }
|
140
|
+
|
141
|
+
assert_equal :foo, result
|
142
|
+
ensure
|
143
|
+
t&.kill
|
144
|
+
t&.join
|
145
|
+
end
|
146
|
+
|
147
|
+
def test_tag
|
148
|
+
assert_equal :main, Fiber.current.tag
|
149
|
+
Fiber.current.tag = :foo
|
150
|
+
assert_equal :foo, Fiber.current.tag
|
151
|
+
|
152
|
+
f = Fiber.current.spin(:bar) { }
|
153
|
+
assert_equal :bar, f.tag
|
154
|
+
end
|
155
|
+
|
156
|
+
def test_await_return_value
|
157
|
+
f = Fiber.current.spin { %i[foo bar] }
|
158
|
+
assert_equal %i[foo bar], f.await
|
159
|
+
ensure
|
160
|
+
f&.stop
|
161
|
+
end
|
162
|
+
|
163
|
+
def test_await_with_error
|
164
|
+
result = nil
|
165
|
+
f = Fiber.current.spin { raise 'foo' }
|
166
|
+
begin
|
167
|
+
result = f.await
|
168
|
+
rescue Exception => e
|
169
|
+
result = { error: e }
|
170
|
+
end
|
171
|
+
assert_kind_of Hash, result
|
172
|
+
assert_kind_of RuntimeError, result[:error]
|
173
|
+
assert_equal f, result[:error].source_fiber
|
174
|
+
ensure
|
175
|
+
f&.stop
|
176
|
+
end
|
177
|
+
|
178
|
+
def test_raise
|
179
|
+
result = []
|
180
|
+
error = nil
|
181
|
+
f = Fiber.current.spin do
|
182
|
+
result << 1
|
183
|
+
2.times { snooze }
|
184
|
+
result << 2
|
185
|
+
end
|
186
|
+
f2 = spin { f.raise }
|
187
|
+
assert_equal 0, result.size
|
188
|
+
begin
|
189
|
+
f.await
|
190
|
+
rescue Exception => e
|
191
|
+
error = e
|
192
|
+
end
|
193
|
+
assert_equal 1, result.size
|
194
|
+
assert_equal 1, result[0]
|
195
|
+
assert_kind_of RuntimeError, error
|
196
|
+
assert_equal f, error.source_fiber
|
197
|
+
ensure
|
198
|
+
f&.stop
|
199
|
+
end
|
200
|
+
|
201
|
+
class MyError < RuntimeError
|
202
|
+
end
|
203
|
+
|
204
|
+
def test_raise_with_error_class
|
205
|
+
result = []
|
206
|
+
error = nil
|
207
|
+
f = Fiber.current.spin do
|
208
|
+
result << 1
|
209
|
+
2.times { snooze }
|
210
|
+
result << 2
|
211
|
+
end
|
212
|
+
spin { f.raise MyError }
|
213
|
+
assert_equal 0, result.size
|
214
|
+
begin
|
215
|
+
f.await
|
216
|
+
rescue Exception => e
|
217
|
+
error = e
|
218
|
+
end
|
219
|
+
assert_equal 1, result.size
|
220
|
+
assert_equal 1, result[0]
|
221
|
+
assert_kind_of MyError, error
|
222
|
+
ensure
|
223
|
+
f&.stop
|
224
|
+
end
|
225
|
+
|
226
|
+
def test_raise_with_error_class_and_message
|
227
|
+
result = []
|
228
|
+
error = nil
|
229
|
+
f = Fiber.current.spin do
|
230
|
+
result << 1
|
231
|
+
2.times { snooze }
|
232
|
+
result << 2
|
233
|
+
end
|
234
|
+
spin { f.raise(MyError, 'foo') }
|
235
|
+
assert_equal 0, result.size
|
236
|
+
begin
|
237
|
+
f.await
|
238
|
+
rescue Exception => e
|
239
|
+
error = e
|
240
|
+
end
|
241
|
+
assert_equal 1, result.size
|
242
|
+
assert_equal 1, result[0]
|
243
|
+
assert_kind_of MyError, error
|
244
|
+
assert_equal 'foo', error.message
|
245
|
+
ensure
|
246
|
+
f&.stop
|
247
|
+
end
|
248
|
+
|
249
|
+
def test_raise_with_message
|
250
|
+
result = []
|
251
|
+
error = nil
|
252
|
+
f = Fiber.current.spin do
|
253
|
+
result << 1
|
254
|
+
2.times { snooze }
|
255
|
+
result << 2
|
256
|
+
end
|
257
|
+
f2 = spin { f.raise 'foo' }
|
258
|
+
assert_equal 0, result.size
|
259
|
+
begin
|
260
|
+
f.await
|
261
|
+
rescue Exception => e
|
262
|
+
error = e
|
263
|
+
end
|
264
|
+
assert_equal 1, result.size
|
265
|
+
assert_equal 1, result[0]
|
266
|
+
assert_kind_of RuntimeError, error
|
267
|
+
assert_equal 'foo', error.message
|
268
|
+
assert_equal f, error.source_fiber
|
269
|
+
ensure
|
270
|
+
f&.stop
|
271
|
+
end
|
272
|
+
|
273
|
+
def test_raise_with_exception
|
274
|
+
result = []
|
275
|
+
error = nil
|
276
|
+
f = Fiber.current.spin do
|
277
|
+
result << 1
|
278
|
+
2.times { snooze }
|
279
|
+
result << 2
|
280
|
+
end
|
281
|
+
spin { f.raise MyError.new('bar') }
|
282
|
+
assert_equal 0, result.size
|
283
|
+
begin
|
284
|
+
f.await
|
285
|
+
rescue Exception => e
|
286
|
+
error = e
|
287
|
+
end
|
288
|
+
assert_equal 1, result.size
|
289
|
+
assert_equal 1, result[0]
|
290
|
+
assert_kind_of MyError, error
|
291
|
+
assert_equal 'bar', error.message
|
292
|
+
ensure
|
293
|
+
f&.stop
|
294
|
+
end
|
295
|
+
|
296
|
+
def test_cancel
|
297
|
+
result = []
|
298
|
+
error = nil
|
299
|
+
f = Fiber.current.spin do
|
300
|
+
result << 1
|
301
|
+
2.times { snooze }
|
302
|
+
result << 2
|
303
|
+
end
|
304
|
+
spin { f.cancel }
|
305
|
+
assert_equal 0, result.size
|
306
|
+
begin
|
307
|
+
f.await
|
308
|
+
rescue Polyphony::Cancel => e
|
309
|
+
error = e
|
310
|
+
end
|
311
|
+
assert_equal 1, result.size
|
312
|
+
assert_equal 1, result[0]
|
313
|
+
assert_kind_of Polyphony::Cancel, error
|
314
|
+
ensure
|
315
|
+
f&.stop
|
316
|
+
end
|
317
|
+
|
318
|
+
def test_interrupt
|
319
|
+
# that is, stopped without exception
|
320
|
+
result = []
|
321
|
+
f = Fiber.current.spin do
|
322
|
+
result << 1
|
323
|
+
2.times { snooze }
|
324
|
+
result << 2
|
325
|
+
3
|
326
|
+
end
|
327
|
+
spin { f.interrupt(42) }
|
328
|
+
|
329
|
+
await_result = f.await
|
330
|
+
assert_equal 1, result.size
|
331
|
+
assert_equal 42, await_result
|
332
|
+
ensure
|
333
|
+
f&.stop
|
334
|
+
end
|
335
|
+
|
336
|
+
def test_terminate
|
337
|
+
buffer = []
|
338
|
+
f = spin do
|
339
|
+
buffer << :foo
|
340
|
+
sleep 1
|
341
|
+
buffer << :bar
|
342
|
+
rescue Polyphony::Terminate
|
343
|
+
buffer << :terminate
|
344
|
+
end
|
345
|
+
snooze
|
346
|
+
f.terminate
|
347
|
+
snooze
|
348
|
+
assert_equal [:foo, :terminate], buffer
|
349
|
+
end
|
350
|
+
|
351
|
+
def test_interrupt_timer
|
352
|
+
result = []
|
353
|
+
f = Fiber.current.spin do
|
354
|
+
result << :start
|
355
|
+
result << Thread.current.agent.sleep(1)
|
356
|
+
end
|
357
|
+
snooze
|
358
|
+
f.interrupt
|
359
|
+
f.join
|
360
|
+
|
361
|
+
assert_equal [:start], result
|
362
|
+
end
|
363
|
+
|
364
|
+
def test_stop
|
365
|
+
# that is, stopped without exception
|
366
|
+
result = []
|
367
|
+
f = Fiber.current.spin do
|
368
|
+
result << 1
|
369
|
+
2.times { snooze }
|
370
|
+
result << 2
|
371
|
+
3
|
372
|
+
end
|
373
|
+
spin { f.stop(42) }
|
374
|
+
|
375
|
+
await_result = f.await
|
376
|
+
assert_equal 1, result.size
|
377
|
+
assert_equal 42, await_result
|
378
|
+
ensure
|
379
|
+
f&.stop
|
380
|
+
end
|
381
|
+
|
382
|
+
def test_interrupt_before_start
|
383
|
+
result = []
|
384
|
+
f = Fiber.current.spin do
|
385
|
+
result << 1
|
386
|
+
end
|
387
|
+
f.interrupt(42)
|
388
|
+
snooze
|
389
|
+
|
390
|
+
assert_equal :dead, f.state
|
391
|
+
assert_equal [], result
|
392
|
+
assert_equal 42, f.result
|
393
|
+
end
|
394
|
+
|
395
|
+
def test_interrupt_nested_fiber
|
396
|
+
result = nil
|
397
|
+
f2 = nil
|
398
|
+
f1 = spin do
|
399
|
+
f2 = spin do
|
400
|
+
snooze
|
401
|
+
result = 42
|
402
|
+
end
|
403
|
+
f2.await
|
404
|
+
result && result += 1
|
405
|
+
end
|
406
|
+
spin { f2.interrupt }
|
407
|
+
suspend
|
408
|
+
assert_nil result
|
409
|
+
assert_equal :dead, f1.state
|
410
|
+
assert_equal :dead, f2.state
|
411
|
+
ensure
|
412
|
+
f1&.stop
|
413
|
+
f2&.stop
|
414
|
+
end
|
415
|
+
|
416
|
+
def test_state
|
417
|
+
counter = 0
|
418
|
+
f = spin do
|
419
|
+
3.times do
|
420
|
+
snooze
|
421
|
+
counter += 1
|
422
|
+
end
|
423
|
+
suspend
|
424
|
+
end
|
425
|
+
|
426
|
+
assert_equal :runnable, f.state
|
427
|
+
assert_equal :running, Fiber.current.state
|
428
|
+
snooze
|
429
|
+
assert_equal :runnable, f.state
|
430
|
+
snooze while counter < 3
|
431
|
+
assert_equal :waiting, f.state
|
432
|
+
f.stop
|
433
|
+
snooze
|
434
|
+
assert_equal :dead, f.state
|
435
|
+
ensure
|
436
|
+
f&.stop
|
437
|
+
end
|
438
|
+
|
439
|
+
def test_main?
|
440
|
+
f = spin {
|
441
|
+
sleep
|
442
|
+
}
|
443
|
+
assert_nil f.main?
|
444
|
+
assert_equal true, Fiber.current.main?
|
445
|
+
end
|
446
|
+
|
447
|
+
def test_exception_propagation
|
448
|
+
# error is propagated to calling fiber
|
449
|
+
raised_error = nil
|
450
|
+
spin do
|
451
|
+
spin do
|
452
|
+
raise 'foo'
|
453
|
+
end
|
454
|
+
snooze # allow nested fiber to run before finishing
|
455
|
+
end
|
456
|
+
suspend
|
457
|
+
rescue Exception => e
|
458
|
+
raised_error = e
|
459
|
+
ensure
|
460
|
+
assert raised_error
|
461
|
+
assert_equal 'foo', raised_error.message
|
462
|
+
end
|
463
|
+
|
464
|
+
def test_await_multiple_fibers
|
465
|
+
f1 = spin { sleep 0.01; :foo }
|
466
|
+
f2 = spin { sleep 0.01; :bar }
|
467
|
+
f3 = spin { sleep 0.01; :baz }
|
468
|
+
|
469
|
+
result = Fiber.await(f1, f2, f3)
|
470
|
+
assert_equal %i{foo bar baz}, result
|
471
|
+
end
|
472
|
+
|
473
|
+
def test_join_multiple_fibers
|
474
|
+
f1 = spin { sleep 0.01; :foo }
|
475
|
+
f2 = spin { sleep 0.01; :bar }
|
476
|
+
f3 = spin { sleep 0.01; :baz }
|
477
|
+
|
478
|
+
result = Fiber.join(f1, f2, f3)
|
479
|
+
assert_equal %i{foo bar baz}, result
|
480
|
+
end
|
481
|
+
|
482
|
+
def test_select_from_multiple_fibers
|
483
|
+
sleep 0
|
484
|
+
buffer = []
|
485
|
+
f1 = spin { sleep 0.1; buffer << :foo; :foo }
|
486
|
+
f2 = spin { sleep 0.3; buffer << :bar; :bar }
|
487
|
+
f3 = spin { sleep 0.5; buffer << :baz; :baz }
|
488
|
+
|
489
|
+
selected, result = Fiber.select(f1, f2, f3)
|
490
|
+
assert_equal :foo, result
|
491
|
+
assert_equal f1, selected
|
492
|
+
assert_equal [:foo], buffer
|
493
|
+
end
|
494
|
+
|
495
|
+
def test_caller
|
496
|
+
location = /^#{__FILE__}:#{__LINE__ + 1}/
|
497
|
+
f = spin do
|
498
|
+
sleep 0.01
|
499
|
+
end
|
500
|
+
snooze
|
501
|
+
|
502
|
+
caller = f.caller
|
503
|
+
assert_match location, caller[0]
|
504
|
+
end
|
505
|
+
|
506
|
+
def test_location
|
507
|
+
location = /^#{__FILE__}:#{__LINE__ + 1}/
|
508
|
+
f = spin do
|
509
|
+
sleep 0.01
|
510
|
+
end
|
511
|
+
snooze
|
512
|
+
|
513
|
+
assert f.location =~ location
|
514
|
+
end
|
515
|
+
|
516
|
+
def test_when_done
|
517
|
+
flag = nil
|
518
|
+
values = []
|
519
|
+
f = spin do
|
520
|
+
snooze until flag
|
521
|
+
end
|
522
|
+
f.when_done { values << 42 }
|
523
|
+
|
524
|
+
snooze
|
525
|
+
assert values.empty?
|
526
|
+
snooze
|
527
|
+
flag = true
|
528
|
+
assert values.empty?
|
529
|
+
assert f.alive?
|
530
|
+
|
531
|
+
snooze
|
532
|
+
assert_equal [42], values
|
533
|
+
assert !f.running?
|
534
|
+
end
|
535
|
+
|
536
|
+
def test_children
|
537
|
+
assert_equal [], Fiber.current.children
|
538
|
+
|
539
|
+
f = spin { sleep 1 }
|
540
|
+
snooze
|
541
|
+
assert_equal [f], Fiber.current.children
|
542
|
+
|
543
|
+
f.stop
|
544
|
+
snooze
|
545
|
+
assert_equal [], Fiber.current.children
|
546
|
+
end
|
547
|
+
|
548
|
+
def test_inspect
|
549
|
+
expected = format('#<Fiber:%s (root) (running)>', Fiber.current.object_id)
|
550
|
+
assert_equal expected, Fiber.current.inspect
|
551
|
+
|
552
|
+
spin_line_no = __LINE__ + 1
|
553
|
+
f = spin { :foo }
|
554
|
+
|
555
|
+
expected = format(
|
556
|
+
'#<Fiber:%s %s:%d:in `test_inspect\' (runnable)>',
|
557
|
+
f.object_id,
|
558
|
+
__FILE__,
|
559
|
+
spin_line_no
|
560
|
+
)
|
561
|
+
assert_equal expected, f.inspect
|
562
|
+
|
563
|
+
f.await
|
564
|
+
expected = format(
|
565
|
+
'#<Fiber:%s %s:%d:in `test_inspect\' (dead)>',
|
566
|
+
f.object_id,
|
567
|
+
__FILE__,
|
568
|
+
spin_line_no
|
569
|
+
)
|
570
|
+
assert_equal expected, f.inspect
|
571
|
+
end
|
572
|
+
|
573
|
+
def test_system_exit_in_fiber
|
574
|
+
error = nil
|
575
|
+
spin do
|
576
|
+
spin { raise SystemExit }.await
|
577
|
+
end
|
578
|
+
|
579
|
+
begin
|
580
|
+
suspend
|
581
|
+
rescue Exception => error
|
582
|
+
end
|
583
|
+
|
584
|
+
assert_kind_of SystemExit, error
|
585
|
+
end
|
586
|
+
|
587
|
+
def test_interrupt_in_fiber
|
588
|
+
error = nil
|
589
|
+
spin do
|
590
|
+
spin { raise Interrupt }.await
|
591
|
+
end
|
592
|
+
|
593
|
+
begin
|
594
|
+
suspend
|
595
|
+
rescue Exception => error
|
596
|
+
end
|
597
|
+
|
598
|
+
assert_kind_of Interrupt, error
|
599
|
+
end
|
600
|
+
|
601
|
+
def test_signal_exception_in_fiber
|
602
|
+
error = nil
|
603
|
+
spin do
|
604
|
+
spin { raise SignalException.new('HUP') }.await
|
605
|
+
end
|
606
|
+
|
607
|
+
begin
|
608
|
+
suspend
|
609
|
+
rescue Exception => error
|
610
|
+
end
|
611
|
+
|
612
|
+
assert_kind_of SignalException, error
|
613
|
+
end
|
614
|
+
|
615
|
+
def test_signal_handling_int
|
616
|
+
i, o = IO.pipe
|
617
|
+
pid = Polyphony.fork do
|
618
|
+
f = spin { sleep 100 }
|
619
|
+
begin
|
620
|
+
i.close
|
621
|
+
f.await
|
622
|
+
rescue Exception => e
|
623
|
+
o << e.class.name
|
624
|
+
o.close
|
625
|
+
end
|
626
|
+
end
|
627
|
+
sleep 0.1
|
628
|
+
f = spin { Thread.current.agent.waitpid(pid) }
|
629
|
+
o.close
|
630
|
+
Process.kill('INT', pid)
|
631
|
+
f.await
|
632
|
+
klass = i.read
|
633
|
+
i.close
|
634
|
+
assert_equal 'Interrupt', klass
|
635
|
+
end
|
636
|
+
|
637
|
+
def test_signal_handling_term
|
638
|
+
i, o = IO.pipe
|
639
|
+
pid = Polyphony.fork do
|
640
|
+
f = spin { sleep 100 }
|
641
|
+
begin
|
642
|
+
i.close
|
643
|
+
f.await
|
644
|
+
rescue Exception => e
|
645
|
+
o << e.class.name
|
646
|
+
o.close
|
647
|
+
end
|
648
|
+
end
|
649
|
+
sleep 0.2
|
650
|
+
f = spin { Thread.current.agent.waitpid(pid) }
|
651
|
+
o.close
|
652
|
+
Process.kill('TERM', pid)
|
653
|
+
f.await
|
654
|
+
klass = i.read
|
655
|
+
o.close
|
656
|
+
assert_equal 'SystemExit', klass
|
657
|
+
end
|
658
|
+
|
659
|
+
def test_main_fiber_child_termination_after_fork
|
660
|
+
i, o = IO.pipe
|
661
|
+
pid = Polyphony.fork do
|
662
|
+
i.close
|
663
|
+
spin do
|
664
|
+
sleep 100
|
665
|
+
rescue Exception => e
|
666
|
+
o << e.class.to_s
|
667
|
+
o.close
|
668
|
+
raise e
|
669
|
+
end
|
670
|
+
suspend
|
671
|
+
end
|
672
|
+
o.close
|
673
|
+
spin do
|
674
|
+
sleep 0.2
|
675
|
+
Process.kill('TERM', pid)
|
676
|
+
end
|
677
|
+
Thread.current.agent.waitpid(pid)
|
678
|
+
klass = i.read
|
679
|
+
i.close
|
680
|
+
assert_equal 'Polyphony::Terminate', klass
|
681
|
+
end
|
682
|
+
|
683
|
+
def test_setup_raw
|
684
|
+
buffer = []
|
685
|
+
f = Fiber.new { buffer << receive }
|
686
|
+
|
687
|
+
assert_raises(NoMethodError) { f << 'foo' }
|
688
|
+
snooze
|
689
|
+
f.setup_raw
|
690
|
+
assert_equal Thread.current, f.thread
|
691
|
+
assert_nil f.parent
|
692
|
+
|
693
|
+
f.schedule
|
694
|
+
f << 'bar'
|
695
|
+
snooze
|
696
|
+
assert_equal ['bar'], buffer
|
697
|
+
end
|
698
|
+
end
|
699
|
+
|
700
|
+
class MailboxTest < MiniTest::Test
|
701
|
+
def test_that_fiber_can_receive_messages
|
702
|
+
msgs = []
|
703
|
+
f = spin { loop { msgs << receive } }
|
704
|
+
|
705
|
+
snooze # allow fiber to start
|
706
|
+
|
707
|
+
3.times do |i|
|
708
|
+
f << i
|
709
|
+
sleep 0
|
710
|
+
end
|
711
|
+
sleep 0
|
712
|
+
|
713
|
+
assert_equal [0, 1, 2], msgs
|
714
|
+
ensure
|
715
|
+
f&.stop
|
716
|
+
end
|
717
|
+
|
718
|
+
def test_that_multiple_messages_sent_at_once_arrive_in_order
|
719
|
+
msgs = []
|
720
|
+
f = spin { loop { msgs << receive } }
|
721
|
+
|
722
|
+
snooze # allow f to start
|
723
|
+
|
724
|
+
3.times { |i| f << i }
|
725
|
+
|
726
|
+
sleep 0.01
|
727
|
+
|
728
|
+
assert_equal [0, 1, 2], msgs
|
729
|
+
ensure
|
730
|
+
f&.stop
|
731
|
+
end
|
732
|
+
|
733
|
+
def test_that_sent_message_are_queued_before_calling_receive
|
734
|
+
buffer = []
|
735
|
+
receiver = spin { suspend; 3.times { buffer << receive } }
|
736
|
+
sender = spin { 3.times { |i| receiver << (i * 10) } }
|
737
|
+
|
738
|
+
sender.await
|
739
|
+
receiver.schedule
|
740
|
+
receiver.await
|
741
|
+
|
742
|
+
assert_equal [0, 10, 20], buffer
|
743
|
+
end
|
744
|
+
|
745
|
+
def test_cross_thread_send_receive
|
746
|
+
ping_receive_buffer = []
|
747
|
+
pong_receive_buffer = []
|
748
|
+
|
749
|
+
pong = Thread.new do
|
750
|
+
sleep 0.05
|
751
|
+
loop do
|
752
|
+
peer, data = receive
|
753
|
+
pong_receive_buffer << data
|
754
|
+
peer << 'pong'
|
755
|
+
end
|
756
|
+
end
|
757
|
+
|
758
|
+
ping = Thread.new do
|
759
|
+
sleep 0.05
|
760
|
+
3.times do
|
761
|
+
pong << [Fiber.current, 'ping']
|
762
|
+
data = receive
|
763
|
+
ping_receive_buffer << data
|
764
|
+
end
|
765
|
+
end
|
766
|
+
|
767
|
+
ping.join
|
768
|
+
pong.kill
|
769
|
+
ping = pong = nil
|
770
|
+
|
771
|
+
assert_equal %w{pong pong pong}, ping_receive_buffer
|
772
|
+
assert_equal %w{ping ping ping}, pong_receive_buffer
|
773
|
+
ensure
|
774
|
+
pong&.kill
|
775
|
+
ping&.kill
|
776
|
+
pong&.join
|
777
|
+
ping&.join
|
778
|
+
end
|
779
|
+
|
780
|
+
def test_message_queueing
|
781
|
+
messages = []
|
782
|
+
f = spin do
|
783
|
+
loop {
|
784
|
+
msg = receive
|
785
|
+
break if msg == 'stop'
|
786
|
+
|
787
|
+
messages << msg
|
788
|
+
}
|
789
|
+
end
|
790
|
+
|
791
|
+
100.times { f << 'foo' }
|
792
|
+
f << 'stop'
|
793
|
+
|
794
|
+
f.await
|
795
|
+
assert_equal ['foo'] * 100, messages
|
796
|
+
end
|
797
|
+
|
798
|
+
def test_receive_pending
|
799
|
+
assert_equal [], receive_pending
|
800
|
+
|
801
|
+
(1..5).each { |i| Fiber.current << i }
|
802
|
+
assert_equal (1..5).to_a, receive_pending
|
803
|
+
assert_equal [], receive_pending
|
804
|
+
end
|
805
|
+
|
806
|
+
def test_receive_pending_on_termination
|
807
|
+
buffer = []
|
808
|
+
worker = spin do
|
809
|
+
loop { buffer << receive }
|
810
|
+
rescue Polyphony::Terminate
|
811
|
+
receive_pending.each { |r| buffer << r }
|
812
|
+
end
|
813
|
+
|
814
|
+
worker << 1
|
815
|
+
worker << 2
|
816
|
+
10.times { snooze }
|
817
|
+
assert_equal [1, 2], buffer
|
818
|
+
|
819
|
+
worker << 3
|
820
|
+
worker << 4
|
821
|
+
worker << 5
|
822
|
+
worker.terminate
|
823
|
+
worker.await
|
824
|
+
|
825
|
+
assert_equal (1..5).to_a, buffer
|
826
|
+
end
|
827
|
+
end
|
828
|
+
|
829
|
+
class FiberControlTest < MiniTest::Test
|
830
|
+
def test_await_multiple
|
831
|
+
f1 = spin {
|
832
|
+
snooze
|
833
|
+
:foo
|
834
|
+
}
|
835
|
+
f2 = spin {
|
836
|
+
snooze
|
837
|
+
:bar
|
838
|
+
}
|
839
|
+
result = Fiber.await(f1, f2)
|
840
|
+
assert_equal [:foo, :bar], result
|
841
|
+
end
|
842
|
+
|
843
|
+
def test_await_multiple_with_raised_error
|
844
|
+
f1 = spin {
|
845
|
+
snooze
|
846
|
+
raise 'foo'
|
847
|
+
}
|
848
|
+
f2 = spin {
|
849
|
+
snooze
|
850
|
+
:bar
|
851
|
+
}
|
852
|
+
f3 = spin {
|
853
|
+
sleep 3
|
854
|
+
}
|
855
|
+
error = nil
|
856
|
+
begin
|
857
|
+
Fiber.await(f1, f2, f3)
|
858
|
+
rescue => error
|
859
|
+
end
|
860
|
+
assert_kind_of RuntimeError, error
|
861
|
+
assert_equal 'foo', error.message
|
862
|
+
assert_equal f1, error.source_fiber
|
863
|
+
|
864
|
+
assert_equal :dead, f1.state
|
865
|
+
assert_equal :dead, f2.state
|
866
|
+
assert_equal :dead, f3.state
|
867
|
+
end
|
868
|
+
|
869
|
+
def test_await_multiple_with_interruption
|
870
|
+
f1 = spin { sleep 0.01; :foo }
|
871
|
+
f2 = spin { sleep 1; :bar }
|
872
|
+
spin { snooze; f2.interrupt(:baz) }
|
873
|
+
result = Fiber.await(f1, f2)
|
874
|
+
assert_equal [:foo, :baz], result
|
875
|
+
end
|
876
|
+
|
877
|
+
def test_select
|
878
|
+
buffer = []
|
879
|
+
f1 = spin { snooze; buffer << :foo; :foo }
|
880
|
+
f2 = spin { :bar }
|
881
|
+
result = Fiber.select(f1, f2)
|
882
|
+
assert_equal [f2, :bar], result
|
883
|
+
assert_equal [:foo], buffer
|
884
|
+
assert_equal :dead, f1.state
|
885
|
+
end
|
886
|
+
|
887
|
+
def test_select_with_raised_error
|
888
|
+
f1 = spin { snooze; raise 'foo' }
|
889
|
+
f2 = spin { sleep 3 }
|
890
|
+
|
891
|
+
result = nil
|
892
|
+
begin
|
893
|
+
result = Fiber.select(f1, f2)
|
894
|
+
rescue => result
|
895
|
+
end
|
896
|
+
|
897
|
+
assert_kind_of RuntimeError, result
|
898
|
+
assert_equal 'foo', result.message
|
899
|
+
assert_equal f1, result.source_fiber
|
900
|
+
assert_equal :dead, f1.state
|
901
|
+
assert_equal :dead, f2.state
|
902
|
+
end
|
903
|
+
|
904
|
+
def test_select_with_interruption
|
905
|
+
f1 = spin { sleep 0.01; :foo }
|
906
|
+
f2 = spin { sleep 1; :bar }
|
907
|
+
spin { snooze; f2.interrupt(:baz) }
|
908
|
+
result = Fiber.select(f1, f2)
|
909
|
+
assert_equal [f2, :baz], result
|
910
|
+
end
|
911
|
+
end
|
912
|
+
|
913
|
+
class SupervisionTest < MiniTest::Test
|
914
|
+
def test_exception_during_termination
|
915
|
+
f2 = nil
|
916
|
+
f = spin do
|
917
|
+
f2 = spin do
|
918
|
+
sleep
|
919
|
+
rescue Polyphony::Terminate
|
920
|
+
raise 'foo'
|
921
|
+
end
|
922
|
+
sleep
|
923
|
+
end
|
924
|
+
|
925
|
+
sleep 0.01
|
926
|
+
e = nil
|
927
|
+
begin
|
928
|
+
f.terminate
|
929
|
+
f.await
|
930
|
+
rescue => e
|
931
|
+
end
|
932
|
+
|
933
|
+
assert_kind_of RuntimeError, e
|
934
|
+
assert_equal 'foo', e.message
|
935
|
+
assert_equal f2, e.source_fiber
|
936
|
+
end
|
937
|
+
end
|
938
|
+
|
939
|
+
class RestartTest < MiniTest::Test
|
940
|
+
def test_restart
|
941
|
+
buffer = []
|
942
|
+
f = spin {
|
943
|
+
buffer << 1
|
944
|
+
receive
|
945
|
+
buffer << 2
|
946
|
+
}
|
947
|
+
snooze
|
948
|
+
assert_equal [1], buffer
|
949
|
+
f2 = f.restart
|
950
|
+
assert_equal f2, f
|
951
|
+
assert_equal [1], buffer
|
952
|
+
snooze
|
953
|
+
assert_equal [1, 1], buffer
|
954
|
+
|
955
|
+
f << 'foo'
|
956
|
+
sleep 0.1
|
957
|
+
assert_equal [1, 1, 2], buffer
|
958
|
+
end
|
959
|
+
|
960
|
+
def test_restart_after_finalization
|
961
|
+
buffer = []
|
962
|
+
parent = spin {
|
963
|
+
sleep
|
964
|
+
}
|
965
|
+
|
966
|
+
f = parent.spin { |v|
|
967
|
+
buffer << Fiber.current
|
968
|
+
buffer << v
|
969
|
+
buffer << receive
|
970
|
+
buffer << :done
|
971
|
+
}
|
972
|
+
f.schedule('foo')
|
973
|
+
f << 'bar'
|
974
|
+
snooze
|
975
|
+
f.await
|
976
|
+
|
977
|
+
assert_equal [f, 'foo', 'bar', :done], buffer
|
978
|
+
assert_equal parent, f.parent
|
979
|
+
|
980
|
+
f2 = f.restart('baz')
|
981
|
+
assert f2 != f
|
982
|
+
assert_equal parent, f2.parent
|
983
|
+
|
984
|
+
f2 << 42
|
985
|
+
f2.await
|
986
|
+
assert_equal [f, 'foo', 'bar', :done, f2, 'baz', 42, :done], buffer
|
987
|
+
end
|
988
|
+
end
|