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.
Files changed (221) hide show
  1. checksums.yaml +7 -0
  2. data/.gitbook.yaml +4 -0
  3. data/.github/workflows/test.yml +29 -0
  4. data/.gitignore +59 -0
  5. data/.rubocop.yml +175 -0
  6. data/CHANGELOG.md +393 -0
  7. data/Gemfile +3 -0
  8. data/Gemfile.lock +141 -0
  9. data/LICENSE +21 -0
  10. data/README.md +51 -0
  11. data/Rakefile +26 -0
  12. data/TODO.md +201 -0
  13. data/bin/polyphony-debug +87 -0
  14. data/docs/_config.yml +64 -0
  15. data/docs/_includes/head.html +40 -0
  16. data/docs/_includes/title.html +1 -0
  17. data/docs/_sass/custom/custom.scss +10 -0
  18. data/docs/_sass/overrides.scss +0 -0
  19. data/docs/_user-guide/all-about-timers.md +126 -0
  20. data/docs/_user-guide/index.md +9 -0
  21. data/docs/_user-guide/web-server.md +136 -0
  22. data/docs/api-reference/exception.md +27 -0
  23. data/docs/api-reference/fiber.md +425 -0
  24. data/docs/api-reference/index.md +9 -0
  25. data/docs/api-reference/io.md +36 -0
  26. data/docs/api-reference/object.md +99 -0
  27. data/docs/api-reference/polyphony-baseexception.md +33 -0
  28. data/docs/api-reference/polyphony-cancel.md +26 -0
  29. data/docs/api-reference/polyphony-moveon.md +24 -0
  30. data/docs/api-reference/polyphony-net.md +20 -0
  31. data/docs/api-reference/polyphony-process.md +28 -0
  32. data/docs/api-reference/polyphony-resourcepool.md +59 -0
  33. data/docs/api-reference/polyphony-restart.md +18 -0
  34. data/docs/api-reference/polyphony-terminate.md +18 -0
  35. data/docs/api-reference/polyphony-threadpool.md +67 -0
  36. data/docs/api-reference/polyphony-throttler.md +77 -0
  37. data/docs/api-reference/polyphony.md +36 -0
  38. data/docs/api-reference/thread.md +88 -0
  39. data/docs/assets/img/echo-fibers.svg +1 -0
  40. data/docs/assets/img/sleeping-fiber.svg +1 -0
  41. data/docs/faq.md +195 -0
  42. data/docs/favicon.ico +0 -0
  43. data/docs/getting-started/index.md +10 -0
  44. data/docs/getting-started/installing.md +34 -0
  45. data/docs/getting-started/overview.md +486 -0
  46. data/docs/getting-started/tutorial.md +359 -0
  47. data/docs/index.md +94 -0
  48. data/docs/main-concepts/concurrency.md +151 -0
  49. data/docs/main-concepts/design-principles.md +161 -0
  50. data/docs/main-concepts/exception-handling.md +291 -0
  51. data/docs/main-concepts/extending.md +89 -0
  52. data/docs/main-concepts/fiber-scheduling.md +197 -0
  53. data/docs/main-concepts/index.md +9 -0
  54. data/docs/polyphony-logo.png +0 -0
  55. data/examples/adapters/concurrent-ruby.rb +9 -0
  56. data/examples/adapters/pg_client.rb +36 -0
  57. data/examples/adapters/pg_notify.rb +35 -0
  58. data/examples/adapters/pg_pool.rb +43 -0
  59. data/examples/adapters/pg_transaction.rb +31 -0
  60. data/examples/adapters/redis_blpop.rb +12 -0
  61. data/examples/adapters/redis_channels.rb +122 -0
  62. data/examples/adapters/redis_client.rb +19 -0
  63. data/examples/adapters/redis_pubsub.rb +26 -0
  64. data/examples/adapters/redis_pubsub_perf.rb +68 -0
  65. data/examples/core/01-spinning-up-fibers.rb +18 -0
  66. data/examples/core/02-awaiting-fibers.rb +20 -0
  67. data/examples/core/03-interrupting.rb +39 -0
  68. data/examples/core/04-handling-signals.rb +19 -0
  69. data/examples/core/xx-agent.rb +102 -0
  70. data/examples/core/xx-at_exit.rb +29 -0
  71. data/examples/core/xx-caller.rb +12 -0
  72. data/examples/core/xx-channels.rb +45 -0
  73. data/examples/core/xx-daemon.rb +14 -0
  74. data/examples/core/xx-deadlock.rb +8 -0
  75. data/examples/core/xx-deferring-an-operation.rb +14 -0
  76. data/examples/core/xx-erlang-style-genserver.rb +81 -0
  77. data/examples/core/xx-exception-backtrace.rb +40 -0
  78. data/examples/core/xx-fork-cleanup.rb +22 -0
  79. data/examples/core/xx-fork-spin.rb +42 -0
  80. data/examples/core/xx-fork-terminate.rb +27 -0
  81. data/examples/core/xx-forking.rb +24 -0
  82. data/examples/core/xx-move_on.rb +23 -0
  83. data/examples/core/xx-pingpong.rb +18 -0
  84. data/examples/core/xx-queue-async.rb +120 -0
  85. data/examples/core/xx-readpartial.rb +18 -0
  86. data/examples/core/xx-recurrent-timer.rb +12 -0
  87. data/examples/core/xx-resource_delegate.rb +31 -0
  88. data/examples/core/xx-signals.rb +16 -0
  89. data/examples/core/xx-sleep-forever.rb +9 -0
  90. data/examples/core/xx-sleeping.rb +25 -0
  91. data/examples/core/xx-snooze-starve.rb +16 -0
  92. data/examples/core/xx-spin-fork.rb +49 -0
  93. data/examples/core/xx-spin_error_backtrace.rb +33 -0
  94. data/examples/core/xx-state-machine.rb +51 -0
  95. data/examples/core/xx-stop.rb +20 -0
  96. data/examples/core/xx-supervise-process.rb +30 -0
  97. data/examples/core/xx-supervisors.rb +21 -0
  98. data/examples/core/xx-thread-selector-sleep.rb +51 -0
  99. data/examples/core/xx-thread-selector-snooze.rb +46 -0
  100. data/examples/core/xx-thread-sleep.rb +17 -0
  101. data/examples/core/xx-thread-snooze.rb +34 -0
  102. data/examples/core/xx-thread_pool.rb +17 -0
  103. data/examples/core/xx-throttling.rb +18 -0
  104. data/examples/core/xx-timeout.rb +10 -0
  105. data/examples/core/xx-timer-gc.rb +17 -0
  106. data/examples/core/xx-trace.rb +79 -0
  107. data/examples/core/xx-using-a-mutex.rb +21 -0
  108. data/examples/core/xx-worker-thread.rb +30 -0
  109. data/examples/io/tunnel.rb +48 -0
  110. data/examples/io/xx-backticks.rb +11 -0
  111. data/examples/io/xx-echo_client.rb +25 -0
  112. data/examples/io/xx-echo_client_from_stdin.rb +21 -0
  113. data/examples/io/xx-echo_pipe.rb +16 -0
  114. data/examples/io/xx-echo_server.rb +17 -0
  115. data/examples/io/xx-echo_server_with_timeout.rb +34 -0
  116. data/examples/io/xx-echo_stdin.rb +14 -0
  117. data/examples/io/xx-happy-eyeballs.rb +36 -0
  118. data/examples/io/xx-httparty.rb +38 -0
  119. data/examples/io/xx-irb.rb +17 -0
  120. data/examples/io/xx-net-http.rb +15 -0
  121. data/examples/io/xx-open.rb +16 -0
  122. data/examples/io/xx-switch.rb +15 -0
  123. data/examples/io/xx-system.rb +11 -0
  124. data/examples/io/xx-tcpserver.rb +15 -0
  125. data/examples/io/xx-tcpsocket.rb +18 -0
  126. data/examples/io/xx-zip.rb +19 -0
  127. data/examples/performance/fiber_transfer.rb +47 -0
  128. data/examples/performance/fs_read.rb +38 -0
  129. data/examples/performance/mem-usage.rb +56 -0
  130. data/examples/performance/messaging.rb +29 -0
  131. data/examples/performance/multi_snooze.rb +33 -0
  132. data/examples/performance/snooze.rb +39 -0
  133. data/examples/performance/snooze_raw.rb +39 -0
  134. data/examples/performance/thread-vs-fiber/polyphony_mt_server.rb +74 -0
  135. data/examples/performance/thread-vs-fiber/polyphony_server.rb +45 -0
  136. data/examples/performance/thread-vs-fiber/polyphony_server_read_loop.rb +58 -0
  137. data/examples/performance/thread-vs-fiber/threaded_server.rb +27 -0
  138. data/examples/performance/thread-vs-fiber/xx-httparty_multi.rb +36 -0
  139. data/examples/performance/thread-vs-fiber/xx-httparty_threaded.rb +29 -0
  140. data/examples/performance/thread_pool_perf.rb +63 -0
  141. data/examples/performance/xx-array.rb +11 -0
  142. data/examples/performance/xx-fiber-switch.rb +9 -0
  143. data/examples/performance/xx-snooze.rb +15 -0
  144. data/examples/xx-spin.rb +32 -0
  145. data/ext/libev/Changes +548 -0
  146. data/ext/libev/LICENSE +37 -0
  147. data/ext/libev/README +59 -0
  148. data/ext/libev/README.embed +3 -0
  149. data/ext/libev/ev.c +5279 -0
  150. data/ext/libev/ev.h +856 -0
  151. data/ext/libev/ev_epoll.c +296 -0
  152. data/ext/libev/ev_kqueue.c +224 -0
  153. data/ext/libev/ev_linuxaio.c +642 -0
  154. data/ext/libev/ev_poll.c +156 -0
  155. data/ext/libev/ev_port.c +192 -0
  156. data/ext/libev/ev_select.c +316 -0
  157. data/ext/libev/ev_vars.h +215 -0
  158. data/ext/libev/ev_win32.c +162 -0
  159. data/ext/libev/ev_wrap.h +216 -0
  160. data/ext/libev/test_libev_win32.c +123 -0
  161. data/ext/polyphony/extconf.rb +20 -0
  162. data/ext/polyphony/fiber.c +109 -0
  163. data/ext/polyphony/libev.c +2 -0
  164. data/ext/polyphony/libev.h +9 -0
  165. data/ext/polyphony/libev_agent.c +882 -0
  166. data/ext/polyphony/polyphony.c +71 -0
  167. data/ext/polyphony/polyphony.h +97 -0
  168. data/ext/polyphony/polyphony_ext.c +21 -0
  169. data/ext/polyphony/queue.c +168 -0
  170. data/ext/polyphony/ring_buffer.c +96 -0
  171. data/ext/polyphony/ring_buffer.h +28 -0
  172. data/ext/polyphony/thread.c +208 -0
  173. data/ext/polyphony/tracing.c +11 -0
  174. data/lib/polyphony.rb +136 -0
  175. data/lib/polyphony/adapters/fs.rb +19 -0
  176. data/lib/polyphony/adapters/irb.rb +52 -0
  177. data/lib/polyphony/adapters/postgres.rb +110 -0
  178. data/lib/polyphony/adapters/process.rb +33 -0
  179. data/lib/polyphony/adapters/redis.rb +67 -0
  180. data/lib/polyphony/adapters/trace.rb +138 -0
  181. data/lib/polyphony/core/channel.rb +46 -0
  182. data/lib/polyphony/core/exceptions.rb +36 -0
  183. data/lib/polyphony/core/global_api.rb +124 -0
  184. data/lib/polyphony/core/resource_pool.rb +117 -0
  185. data/lib/polyphony/core/sync.rb +21 -0
  186. data/lib/polyphony/core/thread_pool.rb +64 -0
  187. data/lib/polyphony/core/throttler.rb +41 -0
  188. data/lib/polyphony/event.rb +17 -0
  189. data/lib/polyphony/extensions/core.rb +174 -0
  190. data/lib/polyphony/extensions/fiber.rb +379 -0
  191. data/lib/polyphony/extensions/io.rb +221 -0
  192. data/lib/polyphony/extensions/openssl.rb +81 -0
  193. data/lib/polyphony/extensions/socket.rb +150 -0
  194. data/lib/polyphony/extensions/thread.rb +108 -0
  195. data/lib/polyphony/net.rb +77 -0
  196. data/lib/polyphony/version.rb +5 -0
  197. data/polyphony.gemspec +40 -0
  198. data/test/coverage.rb +54 -0
  199. data/test/eg.rb +27 -0
  200. data/test/helper.rb +56 -0
  201. data/test/q.rb +24 -0
  202. data/test/run.rb +5 -0
  203. data/test/stress.rb +25 -0
  204. data/test/test_agent.rb +130 -0
  205. data/test/test_event.rb +59 -0
  206. data/test/test_ext.rb +196 -0
  207. data/test/test_fiber.rb +988 -0
  208. data/test/test_global_api.rb +352 -0
  209. data/test/test_io.rb +249 -0
  210. data/test/test_kernel.rb +57 -0
  211. data/test/test_process_supervision.rb +46 -0
  212. data/test/test_queue.rb +112 -0
  213. data/test/test_resource_pool.rb +138 -0
  214. data/test/test_signal.rb +100 -0
  215. data/test/test_socket.rb +34 -0
  216. data/test/test_supervise.rb +103 -0
  217. data/test/test_thread.rb +170 -0
  218. data/test/test_thread_pool.rb +101 -0
  219. data/test/test_throttler.rb +50 -0
  220. data/test/test_trace.rb +68 -0
  221. metadata +482 -0
@@ -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
@@ -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
@@ -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