polyphony 0.99.4 → 0.99.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +10 -0
- data/examples/pipes/gzip_http_server.rb +2 -2
- data/examples/pipes/http_server.rb +1 -1
- data/examples/pipes/tcp_proxy.rb +1 -1
- data/ext/polyphony/backend_common.c +4 -4
- data/ext/polyphony/backend_io_uring.c +8 -8
- data/ext/polyphony/backend_libev.c +5 -5
- data/ext/polyphony/fiber.c +32 -41
- data/ext/polyphony/io_extensions.c +50 -37
- data/ext/polyphony/pipe.c +6 -18
- data/ext/polyphony/polyphony.c +63 -133
- data/ext/polyphony/queue.c +25 -63
- data/ext/polyphony/thread.c +3 -12
- data/lib/polyphony/adapters/process.rb +1 -2
- data/lib/polyphony/adapters/sequel.rb +2 -2
- data/lib/polyphony/core/debug.rb +1 -1
- data/lib/polyphony/core/exceptions.rb +1 -1
- data/lib/polyphony/core/global_api.rb +24 -38
- data/lib/polyphony/core/resource_pool.rb +7 -8
- data/lib/polyphony/core/sync.rb +1 -2
- data/lib/polyphony/core/thread_pool.rb +2 -5
- data/lib/polyphony/core/throttler.rb +1 -5
- data/lib/polyphony/core/timer.rb +24 -25
- data/lib/polyphony/extensions/fiber.rb +507 -540
- data/lib/polyphony/extensions/io.rb +3 -12
- data/lib/polyphony/extensions/openssl.rb +2 -23
- data/lib/polyphony/extensions/pipe.rb +4 -15
- data/lib/polyphony/extensions/socket.rb +15 -109
- data/lib/polyphony/extensions/thread.rb +0 -13
- data/lib/polyphony/extensions/timeout.rb +0 -1
- data/lib/polyphony/net.rb +5 -8
- data/lib/polyphony/version.rb +1 -1
- data/lib/polyphony.rb +2 -6
- data/test/test_io.rb +221 -221
- data/test/test_socket.rb +3 -3
- data/test/test_trace.rb +2 -2
- metadata +1 -1
@@ -2,228 +2,525 @@
|
|
2
2
|
|
3
3
|
require_relative '../core/exceptions'
|
4
4
|
|
5
|
-
|
5
|
+
# Fiber extensions
|
6
|
+
class ::Fiber
|
7
|
+
attr_accessor :tag, :thread, :parent, :oob
|
8
|
+
attr_reader :result
|
6
9
|
|
7
|
-
#
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
10
|
+
# Returns true if fiber is running.
|
11
|
+
#
|
12
|
+
# @return [bool] is fiber running
|
13
|
+
def running?
|
14
|
+
@running
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns a string representation of the fiber for debugging.
|
18
|
+
#
|
19
|
+
# @return [String] string representation
|
20
|
+
def inspect
|
21
|
+
if @tag
|
22
|
+
"#<Fiber #{tag}:#{object_id} #{location} (#{state})>"
|
23
|
+
else
|
24
|
+
"#<Fiber:#{object_id} #{location} (#{state})>"
|
15
25
|
end
|
26
|
+
end
|
27
|
+
alias_method :to_s, :inspect
|
16
28
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
def interrupt(value = nil)
|
28
|
-
return if @running == false
|
29
|
+
# Returns the source location for the fiber based on its caller.
|
30
|
+
#
|
31
|
+
# @return [String] source location
|
32
|
+
def location
|
33
|
+
if @oob
|
34
|
+
"#{@caller[0]} (oob)"
|
35
|
+
else
|
36
|
+
@caller ? @caller[0] : '(root)'
|
37
|
+
end
|
38
|
+
end
|
29
39
|
|
30
|
-
|
31
|
-
|
40
|
+
# Returns the fiber's caller.
|
41
|
+
#
|
42
|
+
# @return [Array<String>] caller
|
43
|
+
def caller
|
44
|
+
spin_caller = @caller || []
|
45
|
+
if @parent
|
46
|
+
spin_caller + @parent.caller
|
47
|
+
else
|
48
|
+
spin_caller
|
32
49
|
end
|
33
|
-
|
34
|
-
alias_method :move_on, :interrupt
|
50
|
+
end
|
35
51
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
# @return [Fiber] restarted fiber
|
45
|
-
def restart(value = nil)
|
46
|
-
raise "Can't restart main fiber" if @main
|
47
|
-
|
48
|
-
if @running
|
49
|
-
schedule Polyphony::Restart.new(value)
|
50
|
-
return self
|
51
|
-
end
|
52
|
+
# Sets the fiber's caller.
|
53
|
+
#
|
54
|
+
# @param caller [Array<String>] new caller
|
55
|
+
# @return [Fiber] self
|
56
|
+
def set_caller(caller)
|
57
|
+
@caller = caller
|
58
|
+
self
|
59
|
+
end
|
52
60
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
61
|
+
# Returns true if the fiber is the main fiber for its thread.
|
62
|
+
#
|
63
|
+
# @return [bool] is main fiber
|
64
|
+
def main?
|
65
|
+
@main
|
66
|
+
end
|
59
67
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
# @return [Fiber] self
|
64
|
-
def cancel(exception = Polyphony::Cancel)
|
65
|
-
return if @running == false
|
66
|
-
|
67
|
-
value = (Class === exception) ? exception.new : exception
|
68
|
-
schedule value
|
69
|
-
self
|
70
|
-
end
|
68
|
+
#########################
|
69
|
+
# Fiber control methods #
|
70
|
+
#########################
|
71
71
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
72
|
+
# Returns the fiber's monitoring mailbox queue, used for receiving fiber
|
73
|
+
# monitoring messages.
|
74
|
+
#
|
75
|
+
# @return [Polyphony::Queue] monitoring mailbox queue
|
76
|
+
def monitor_mailbox
|
77
|
+
@monitor_mailbox ||= Polyphony::Queue.new
|
78
|
+
end
|
79
79
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
80
|
+
# Stops the fiber by raising a `Polyphony::MoveOn` exception. The given
|
81
|
+
# value will become the fiber's return value.
|
82
|
+
#
|
83
|
+
# @param value [any] fiber's eventual return value
|
84
|
+
# @return [Fiber] self
|
85
|
+
def interrupt(value = nil)
|
86
|
+
return if @running == false
|
86
87
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
return if @running == false
|
93
|
-
|
94
|
-
@graceful_shutdown = graceful
|
95
|
-
schedule Polyphony::Terminate.new
|
96
|
-
self
|
97
|
-
end
|
88
|
+
schedule Polyphony::MoveOn.new(value)
|
89
|
+
self
|
90
|
+
end
|
91
|
+
alias_method :stop, :interrupt
|
92
|
+
alias_method :move_on, :interrupt
|
98
93
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
schedule(error)
|
111
|
-
self
|
94
|
+
# Restarts the fiber, with the given value serving as the first value passed
|
95
|
+
# to the fiber's block.
|
96
|
+
#
|
97
|
+
# @param value [any] value passed to fiber block
|
98
|
+
# @return [Fiber] restarted fiber
|
99
|
+
def restart(value = nil)
|
100
|
+
raise "Can't restart main fiber" if @main
|
101
|
+
|
102
|
+
if @running
|
103
|
+
schedule Polyphony::Restart.new(value)
|
104
|
+
return self
|
112
105
|
end
|
113
106
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
107
|
+
fiber = parent.spin(@tag, @caller, &@block)
|
108
|
+
@monitors&.each_key { |f| fiber.monitor(f) }
|
109
|
+
fiber.schedule(value) unless value.nil?
|
110
|
+
fiber
|
111
|
+
end
|
112
|
+
alias_method :reset, :restart
|
113
|
+
|
114
|
+
# Stops a fiber by raising a Polyphony::Cancel exception.
|
115
|
+
#
|
116
|
+
# @param exception [Class, Exception] exception or exception class
|
117
|
+
# @return [Fiber] self
|
118
|
+
def cancel(exception = Polyphony::Cancel)
|
119
|
+
return if @running == false
|
120
|
+
|
121
|
+
value = (Class === exception) ? exception.new : exception
|
122
|
+
schedule value
|
123
|
+
self
|
124
|
+
end
|
125
|
+
|
126
|
+
# Sets the graceful shutdown flag for the fiber.
|
127
|
+
#
|
128
|
+
# @param graceful [bool] Whether or not to perform a graceful shutdown
|
129
|
+
# @return [bool] graceful
|
130
|
+
def graceful_shutdown=(graceful)
|
131
|
+
@graceful_shutdown = graceful
|
132
|
+
end
|
133
|
+
|
134
|
+
# Returns the graceful shutdown flag for the fiber.
|
135
|
+
#
|
136
|
+
# @return [bool] true if graceful shutdown, otherwise false
|
137
|
+
def graceful_shutdown?
|
138
|
+
@graceful_shutdown
|
139
|
+
end
|
140
|
+
|
141
|
+
# Terminates the fiber, optionally setting the graceful shutdown flag.
|
142
|
+
#
|
143
|
+
# @param graceful [bool] Whether to perform a graceful shutdown
|
144
|
+
# @return [Fiber] self
|
145
|
+
def terminate(graceful = false)
|
146
|
+
return if @running == false
|
147
|
+
|
148
|
+
@graceful_shutdown = graceful
|
149
|
+
schedule Polyphony::Terminate.new
|
150
|
+
self
|
151
|
+
end
|
152
|
+
|
153
|
+
# Raises an exception in the context of the fiber
|
154
|
+
#
|
155
|
+
# @overload fiber.raise(message)
|
156
|
+
# @param message [String] error message
|
157
|
+
# @return [Fiber] self
|
158
|
+
# @overload fiber.raise(exception_class)
|
159
|
+
# @param exception_class [Class] exception class to raise
|
160
|
+
# @return [Fiber] self
|
161
|
+
# @overload fiber.raise(exception_class, exception_message)
|
162
|
+
# @param exception_class [Class] exception class to raise
|
163
|
+
# @param exception_message [String] exception message to raise
|
164
|
+
# @return [Fiber] self
|
165
|
+
# @overload fiber.raise(exception)
|
166
|
+
# @param any [Exception] exception to raise
|
167
|
+
# @return [Fiber] self
|
168
|
+
def raise(*args)
|
169
|
+
error = error_from_raise_args(args)
|
170
|
+
schedule(error)
|
171
|
+
self
|
172
|
+
end
|
173
|
+
|
174
|
+
# Adds an interjection to the fiber. The current operation undertaken by the
|
175
|
+
# fiber will be interrupted, and the given block will be executed, and the
|
176
|
+
# operation will be resumed. This API is experimental and might be removed
|
177
|
+
# in the future.
|
178
|
+
#
|
179
|
+
# @return [Fiber] self
|
180
|
+
def interject(&block)
|
181
|
+
raise Polyphony::Interjection.new(block)
|
182
|
+
end
|
183
|
+
|
184
|
+
# Blocks until the fiber has terminated, returning its return value.
|
185
|
+
#
|
186
|
+
# @return [any] fiber's return value
|
187
|
+
def await
|
188
|
+
Fiber.await(self).first
|
189
|
+
end
|
190
|
+
alias_method :join, :await
|
191
|
+
|
192
|
+
#############################
|
193
|
+
# Fiber supervision methods #
|
194
|
+
#############################
|
195
|
+
|
196
|
+
# Supervises the given fibers or all child fibers. The fiber is put in
|
197
|
+
# supervision mode, which means any child added after calling `#supervise`
|
198
|
+
# will automatically be supervised. Depending on the given options, fibers
|
199
|
+
# may be automatically restarted.
|
200
|
+
#
|
201
|
+
# If a block is given, the block is called whenever a supervised fiber has
|
202
|
+
# terminated. If the `:on_done` option is given, that proc will be called
|
203
|
+
# when a supervised fiber has terminated. If the `:on_error` option is
|
204
|
+
# given, that proc will be called when a supervised fiber has terminated
|
205
|
+
# with an uncaught exception. If the `:restart` option equals `:always`,
|
206
|
+
# fibers will always be restarted. If the `:restart` option equals
|
207
|
+
# `:on_error`, fibers will be restarted only when terminated with an
|
208
|
+
# uncaught exception.
|
209
|
+
#
|
210
|
+
# This method blocks indefinitely.
|
211
|
+
#
|
212
|
+
# @param fibers [Array<Fiber>] fibers to supervise
|
213
|
+
# @option opts [Proc, nil] :on_done proc to call when a supervised fiber is terminated
|
214
|
+
# @option opts [Proc, nil] :on_error proc to call when a supervised fiber is terminated with an exception
|
215
|
+
# @option opts [:always, :on_error, nil] :restart whether to restart terminated fibers
|
216
|
+
# @return [void]
|
217
|
+
def supervise(*fibers, **opts, &block)
|
218
|
+
block ||= supervise_opts_to_block(opts)
|
219
|
+
|
220
|
+
@supervise_mode = true
|
221
|
+
fibers = children if fibers.empty?
|
222
|
+
fibers.each do |f|
|
223
|
+
f.attach_to(self) unless f.parent == self
|
224
|
+
f.monitor(self)
|
123
225
|
end
|
124
226
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
227
|
+
mailbox = monitor_mailbox
|
228
|
+
|
229
|
+
while true
|
230
|
+
(fiber, result) = mailbox.shift
|
231
|
+
block&.call(fiber, result)
|
130
232
|
end
|
131
|
-
|
233
|
+
ensure
|
234
|
+
@supervise_mode = false
|
235
|
+
end
|
132
236
|
|
133
|
-
|
237
|
+
###############################
|
238
|
+
# Child fiber control methods #
|
239
|
+
###############################
|
134
240
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
241
|
+
# Returns the fiber's children.
|
242
|
+
#
|
243
|
+
# @return [Array<Fiber>] child fibers
|
244
|
+
def children
|
245
|
+
(@children ||= {}).keys
|
246
|
+
end
|
247
|
+
|
248
|
+
# Creates a new child fiber.
|
249
|
+
#
|
250
|
+
# child = fiber.spin { sleep 10; fiber.stop }
|
251
|
+
#
|
252
|
+
# @param tag [any] child fiber's tag
|
253
|
+
# @param orig_caller [Array<String>] caller to set for fiber
|
254
|
+
# @return [Fiber] child fiber
|
255
|
+
def spin(tag = nil, orig_caller = Kernel.caller, &block)
|
256
|
+
f = Fiber.new { |v| f.run(v) }
|
257
|
+
f.prepare(tag, block, orig_caller, self)
|
258
|
+
(@children ||= {})[f] = true
|
259
|
+
f.monitor(self) if @supervise_mode
|
260
|
+
f
|
261
|
+
end
|
262
|
+
|
263
|
+
# Terminates all child fibers. This method will return before the fibers are
|
264
|
+
# actually terminated.
|
265
|
+
#
|
266
|
+
# @param graceful [bool] whether to perform a graceful termination
|
267
|
+
# @return [Fiber] self
|
268
|
+
def terminate_all_children(graceful = false)
|
269
|
+
return self unless @children
|
270
|
+
|
271
|
+
e = Polyphony::Terminate.new
|
272
|
+
@children.each_key do |c|
|
273
|
+
c.graceful_shutdown = true if graceful
|
274
|
+
c.raise e
|
143
275
|
end
|
276
|
+
self
|
144
277
|
end
|
145
278
|
|
146
|
-
#
|
147
|
-
|
279
|
+
# Block until all child fibers have terminated. Returns the return values
|
280
|
+
# for all child fibers.
|
281
|
+
#
|
282
|
+
# @return [Array<any>] return values of child fibers
|
283
|
+
def await_all_children
|
284
|
+
return unless @children && !@children.empty?
|
148
285
|
|
149
|
-
|
150
|
-
|
151
|
-
# fiber.supervise(fiber_a, fiber_b)
|
152
|
-
# fiber.supervise { |f, r| handle_terminated_fiber(f, r) }
|
153
|
-
# fiber.supervise(on_done: ->(f, r) { handle_terminated_fiber(f, r) })
|
154
|
-
# fiber.supervise(on_error: ->(f, e) { handle_error(f, e) })
|
155
|
-
# fiber.supervise(*fibers, restart: always)
|
156
|
-
# fiber.supervise(*fibers, restart: on_error)
|
157
|
-
#
|
158
|
-
# Supervises the given fibers or all child fibers. The fiber is put in
|
159
|
-
# supervision mode, which means any child added after calling `#supervise`
|
160
|
-
# will automatically be supervised. Depending on the given options, fibers
|
161
|
-
# may be automatically restarted.
|
162
|
-
#
|
163
|
-
# If a block is given, the block is called whenever a supervised fiber has
|
164
|
-
# terminated. If the `:on_done` option is given, that proc will be called
|
165
|
-
# when a supervised fiber has terminated. If the `:on_error` option is
|
166
|
-
# given, that proc will be called when a supervised fiber has terminated
|
167
|
-
# with an uncaught exception. If the `:restart` option equals `:always`,
|
168
|
-
# fibers will always be restarted. If the `:restart` option equals
|
169
|
-
# `:on_error`, fibers will be restarted only when terminated with an
|
170
|
-
# uncaught exception.
|
171
|
-
#
|
172
|
-
# This method blocks indefinitely.
|
173
|
-
#
|
174
|
-
# @param fibers [Array<Fiber>] fibers to supervise
|
175
|
-
# @option opts [Proc, nil] :on_done proc to call when a supervised fiber is terminated
|
176
|
-
# @option opts [Proc, nil] :on_error proc to call when a supervised fiber is terminated with an exception
|
177
|
-
# @option opts [:always, :on_error, nil] :restart whether to restart terminated fibers
|
178
|
-
# @yield [] supervisor block
|
179
|
-
# @return [void]
|
180
|
-
def supervise(*fibers, **opts, &block)
|
181
|
-
block ||= supervise_opts_to_block(opts)
|
286
|
+
Fiber.await(*@children.keys.reject { |c| c.dead? })
|
287
|
+
end
|
182
288
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
end
|
289
|
+
# Terminates and blocks until all child fibers have terminated.
|
290
|
+
#
|
291
|
+
# @return [Fiber] self
|
292
|
+
def shutdown_all_children(graceful = false)
|
293
|
+
return self unless @children
|
189
294
|
|
190
|
-
|
295
|
+
pending = []
|
296
|
+
@children.keys.each do |c|
|
297
|
+
next if c.dead?
|
191
298
|
|
192
|
-
|
193
|
-
|
194
|
-
block&.call(fiber, result)
|
195
|
-
end
|
196
|
-
ensure
|
197
|
-
@supervise_mode = false
|
299
|
+
c.terminate(graceful)
|
300
|
+
pending << c
|
198
301
|
end
|
302
|
+
Fiber.await(*pending)
|
303
|
+
self
|
304
|
+
end
|
199
305
|
|
200
|
-
|
306
|
+
# Attaches all child fibers to a new parent.
|
307
|
+
#
|
308
|
+
# @param parent [Fiber] new parent
|
309
|
+
# @return [Fiber] self
|
310
|
+
def attach_all_children_to(parent)
|
311
|
+
@children&.keys.each { |c| c.attach_to(parent) }
|
312
|
+
self
|
313
|
+
end
|
201
314
|
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
315
|
+
# Detaches the fiber from its current parent. The fiber will be made a child
|
316
|
+
# of the main fiber (for the current thread.)
|
317
|
+
#
|
318
|
+
# @return [Fiber] self
|
319
|
+
def detach
|
320
|
+
@parent.remove_child(self)
|
321
|
+
@parent = @thread.main_fiber
|
322
|
+
@parent.add_child(self)
|
323
|
+
self
|
324
|
+
end
|
325
|
+
|
326
|
+
# Attaches the fiber to a new parent.
|
327
|
+
#
|
328
|
+
# @param parent [Fiber] new parent
|
329
|
+
# @return [Fiber] self
|
330
|
+
def attach_to(parent)
|
331
|
+
@parent.remove_child(self)
|
332
|
+
@parent = parent
|
333
|
+
parent.add_child(self)
|
334
|
+
self
|
335
|
+
end
|
336
|
+
|
337
|
+
# Attaches the fiber to the new parent and monitors the new parent.
|
338
|
+
#
|
339
|
+
# @param parent [Fiber] new parent
|
340
|
+
# @return [Fiber] self
|
341
|
+
def attach_and_monitor(parent)
|
342
|
+
@parent.remove_child(self)
|
343
|
+
@parent = parent
|
344
|
+
parent.add_child(self)
|
345
|
+
monitor(parent)
|
346
|
+
self
|
347
|
+
end
|
348
|
+
|
349
|
+
# Adds a child fiber reference. Used internally.
|
350
|
+
#
|
351
|
+
# @param child_fiber [Fiber] child fiber
|
352
|
+
# @return [Fiber] self
|
353
|
+
def add_child(child_fiber)
|
354
|
+
(@children ||= {})[child_fiber] = true
|
355
|
+
child_fiber.monitor(self) if @supervise_mode
|
356
|
+
self
|
357
|
+
end
|
358
|
+
|
359
|
+
# Removes a child fiber reference. Used internally.
|
360
|
+
#
|
361
|
+
# @param child_fiber [Fiber] child fiber to be removed
|
362
|
+
# @return [Fiber] self
|
363
|
+
def remove_child(child_fiber)
|
364
|
+
@children.delete(child_fiber) if @children
|
365
|
+
self
|
366
|
+
end
|
367
|
+
|
368
|
+
############################
|
369
|
+
# Fiber life cycle methods #
|
370
|
+
############################
|
371
|
+
|
372
|
+
# Prepares a fiber for running.
|
373
|
+
#
|
374
|
+
# @param tag [any] fiber's tag
|
375
|
+
# @param block [Proc] fiber's block
|
376
|
+
# @param caller [Array<String>] fiber's caller
|
377
|
+
# @param parent [Fiber] fiber's parent
|
378
|
+
# @return [void]
|
379
|
+
def prepare(tag, block, caller, parent)
|
380
|
+
@thread = Thread.current
|
381
|
+
@tag = tag
|
382
|
+
@parent = parent
|
383
|
+
@caller = caller
|
384
|
+
@block = block
|
385
|
+
Thread.backend.trace(:spin, self, Kernel.caller[1..-1])
|
386
|
+
schedule
|
387
|
+
end
|
388
|
+
|
389
|
+
# Runs the fiber's block and handles uncaught exceptions.
|
390
|
+
#
|
391
|
+
# @param first_value [any] value passed to fiber on first resume
|
392
|
+
# @return [void]
|
393
|
+
def run(first_value)
|
394
|
+
Kernel.raise first_value if first_value.is_a?(Exception)
|
395
|
+
@running = true
|
396
|
+
|
397
|
+
Thread.backend.trace(:unblock, self, first_value, @caller)
|
398
|
+
result = @block.(first_value)
|
399
|
+
finalize(result)
|
400
|
+
rescue Polyphony::Restart => e
|
401
|
+
restart_self(e.value)
|
402
|
+
rescue Polyphony::MoveOn, Polyphony::Terminate => e
|
403
|
+
finalize(e.value)
|
404
|
+
rescue Exception => e
|
405
|
+
e.source_fiber = self
|
406
|
+
finalize(e, true)
|
407
|
+
end
|
408
|
+
|
409
|
+
# Performs setup for a "raw" Fiber created using Fiber.new. Note that this
|
410
|
+
# fiber is an orphan fiber (has no parent), since we cannot control how the
|
411
|
+
# fiber terminates after it has already been created. Calling #setup_raw
|
412
|
+
# allows the fiber to be scheduled and to receive messages.
|
413
|
+
#
|
414
|
+
# @return [void]
|
415
|
+
def setup_raw
|
416
|
+
@thread = Thread.current
|
417
|
+
@running = true
|
418
|
+
end
|
419
|
+
|
420
|
+
# Sets up the fiber as the main fiber for the current thread.
|
421
|
+
#
|
422
|
+
# @return [void]
|
423
|
+
def setup_main_fiber
|
424
|
+
@main = true
|
425
|
+
@tag = :main
|
426
|
+
@thread = Thread.current
|
427
|
+
@running = true
|
428
|
+
@children&.clear
|
429
|
+
end
|
430
|
+
|
431
|
+
# Resets the fiber's state and reruns the fiber.
|
432
|
+
#
|
433
|
+
# @param first_value [Fiber] first_value to pass to fiber after restarting
|
434
|
+
# @return [void]
|
435
|
+
def restart_self(first_value)
|
436
|
+
@mailbox = nil
|
437
|
+
run(first_value)
|
438
|
+
end
|
439
|
+
|
440
|
+
# Finalizes the fiber, handling its return value or any uncaught exception.
|
441
|
+
#
|
442
|
+
# @param result [any] return value
|
443
|
+
# @param uncaught_exception [Exception, nil] uncaught exception
|
444
|
+
# @return [void]
|
445
|
+
def finalize(result, uncaught_exception = false)
|
446
|
+
result, uncaught_exception = finalize_children(result, uncaught_exception)
|
447
|
+
Thread.backend.trace(:terminate, self, result)
|
448
|
+
@result = result
|
449
|
+
|
450
|
+
inform_monitors(result, uncaught_exception)
|
451
|
+
@running = false
|
452
|
+
ensure
|
453
|
+
@parent&.remove_child(self)
|
454
|
+
# Prevent fiber from being resumed after terminating
|
455
|
+
@thread.fiber_unschedule(self)
|
456
|
+
Thread.current.switch_fiber
|
457
|
+
end
|
458
|
+
|
459
|
+
# Shuts down all children of the current fiber. If any exception occurs while
|
460
|
+
# the children are shut down, it is returned along with the uncaught_exception
|
461
|
+
# flag set. Otherwise, it returns the given arguments.
|
462
|
+
#
|
463
|
+
# @param result [any] fiber's return value
|
464
|
+
# @param uncaught_exception [Exception, nil] uncaught exception
|
465
|
+
# @return [void]
|
466
|
+
def finalize_children(result, uncaught_exception)
|
467
|
+
shutdown_all_children(graceful_shutdown?)
|
468
|
+
[result, uncaught_exception]
|
469
|
+
rescue Exception => e
|
470
|
+
[e, true]
|
471
|
+
end
|
472
|
+
|
473
|
+
# Informs the fiber's monitors it is terminated.
|
474
|
+
#
|
475
|
+
# @param result [any] fiber's return value
|
476
|
+
# @param uncaught_exception [Exception, nil] uncaught exception
|
477
|
+
# @return [void]
|
478
|
+
def inform_monitors(result, uncaught_exception)
|
479
|
+
if @monitors
|
480
|
+
msg = [self, result]
|
481
|
+
@monitors.each_key { |f| f.monitor_mailbox << msg }
|
482
|
+
end
|
483
|
+
|
484
|
+
if uncaught_exception && @parent
|
485
|
+
parent_is_monitor = @monitors&.has_key?(@parent)
|
486
|
+
@parent.schedule_with_priority(result) unless parent_is_monitor
|
217
487
|
end
|
218
488
|
end
|
219
489
|
|
220
|
-
#
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
490
|
+
# Adds a fiber to the list of monitoring fibers. Monitoring fibers will be
|
491
|
+
# notified on their monitor mailboxes when the fiber is terminated.
|
492
|
+
#
|
493
|
+
# @param fiber [Fiber] monitoring fiber
|
494
|
+
# @return [Fiber] self
|
495
|
+
def monitor(fiber)
|
496
|
+
(@monitors ||= {})[fiber] = true
|
497
|
+
self
|
498
|
+
end
|
499
|
+
|
500
|
+
# Removes a monitor fiber.
|
501
|
+
#
|
502
|
+
# @param fiber [Fiber] monitoring fiber
|
503
|
+
# @return [Fiber] self
|
504
|
+
def unmonitor(fiber)
|
505
|
+
(@monitors ||= []).delete(fiber)
|
506
|
+
self
|
507
|
+
end
|
508
|
+
|
509
|
+
# Returns the list of monitoring fibers.
|
510
|
+
#
|
511
|
+
# @return [Array<Fiber>] monitoring fibers
|
512
|
+
def monitors
|
513
|
+
@monitors&.keys || []
|
514
|
+
end
|
515
|
+
|
516
|
+
# Returns true if the fiber is dead.
|
517
|
+
#
|
518
|
+
# @return [bool] is fiber dead
|
519
|
+
def dead?
|
520
|
+
state == :dead
|
521
|
+
end
|
522
|
+
|
523
|
+
class << self
|
227
524
|
# Waits for all given fibers to terminate, then returns the respective
|
228
525
|
# return values for all terminated fibers. If any of the awaited fibers
|
229
526
|
# terminates with an uncaught exception, `Fiber.await` will await all the
|
@@ -302,7 +599,6 @@ module Polyphony
|
|
302
599
|
# also be scheduled with priority. This method is mainly used trapping
|
303
600
|
# signals (see also the patched `Kernel#trap`)
|
304
601
|
#
|
305
|
-
# @yield [] given block
|
306
602
|
# @return [void]
|
307
603
|
def schedule_priority_oob_fiber(&block)
|
308
604
|
oob_fiber = Fiber.new do
|
@@ -333,363 +629,34 @@ module Polyphony
|
|
333
629
|
end
|
334
630
|
end
|
335
631
|
|
336
|
-
|
337
|
-
module ChildFiberControl
|
338
|
-
|
339
|
-
# Returns the fiber's children.
|
340
|
-
#
|
341
|
-
# @return [Array<Fiber>] child fibers
|
342
|
-
def children
|
343
|
-
(@children ||= {}).keys
|
344
|
-
end
|
345
|
-
|
346
|
-
# Creates a new child fiber.
|
347
|
-
#
|
348
|
-
# child = fiber.spin { sleep 10; fiber.stop }
|
349
|
-
#
|
350
|
-
# @param tag [any] child fiber's tag
|
351
|
-
# @param orig_caller [Array<String>] caller to set for fiber
|
352
|
-
# @yield [any] child fiber's block
|
353
|
-
# @return [Fiber] child fiber
|
354
|
-
def spin(tag = nil, orig_caller = Kernel.caller, &block)
|
355
|
-
f = Fiber.new { |v| f.run(v) }
|
356
|
-
f.prepare(tag, block, orig_caller, self)
|
357
|
-
(@children ||= {})[f] = true
|
358
|
-
f.monitor(self) if @supervise_mode
|
359
|
-
f
|
360
|
-
end
|
361
|
-
|
362
|
-
# Terminates all child fibers. This method will return before the fibers are
|
363
|
-
# actually terminated.
|
364
|
-
#
|
365
|
-
# @param graceful [bool] whether to perform a graceful termination
|
366
|
-
# @return [Fiber] self
|
367
|
-
def terminate_all_children(graceful = false)
|
368
|
-
return self unless @children
|
369
|
-
|
370
|
-
e = Polyphony::Terminate.new
|
371
|
-
@children.each_key do |c|
|
372
|
-
c.graceful_shutdown = true if graceful
|
373
|
-
c.raise e
|
374
|
-
end
|
375
|
-
self
|
376
|
-
end
|
377
|
-
|
378
|
-
# Block until all child fibers have terminated. Returns the return values
|
379
|
-
# for all child fibers.
|
380
|
-
#
|
381
|
-
# @return [Array<any>] return values of child fibers
|
382
|
-
def await_all_children
|
383
|
-
return unless @children && !@children.empty?
|
384
|
-
|
385
|
-
Fiber.await(*@children.keys.reject { |c| c.dead? })
|
386
|
-
end
|
387
|
-
|
388
|
-
# Terminates and blocks until all child fibers have terminated.
|
389
|
-
#
|
390
|
-
# @return [Fiber] self
|
391
|
-
def shutdown_all_children(graceful = false)
|
392
|
-
return self unless @children
|
393
|
-
|
394
|
-
pending = []
|
395
|
-
@children.keys.each do |c|
|
396
|
-
next if c.dead?
|
397
|
-
|
398
|
-
c.terminate(graceful)
|
399
|
-
pending << c
|
400
|
-
end
|
401
|
-
Fiber.await(*pending)
|
402
|
-
self
|
403
|
-
end
|
404
|
-
|
405
|
-
# Attaches all child fibers to a new parent.
|
406
|
-
#
|
407
|
-
# @param parent [Fiber] new parent
|
408
|
-
# @return [Fiber] self
|
409
|
-
def attach_all_children_to(parent)
|
410
|
-
@children&.keys.each { |c| c.attach_to(parent) }
|
411
|
-
self
|
412
|
-
end
|
413
|
-
|
414
|
-
# Detaches the fiber from its current parent. The fiber will be made a child
|
415
|
-
# of the main fiber (for the current thread.)
|
416
|
-
#
|
417
|
-
# @return [Fiber] self
|
418
|
-
def detach
|
419
|
-
@parent.remove_child(self)
|
420
|
-
@parent = @thread.main_fiber
|
421
|
-
@parent.add_child(self)
|
422
|
-
self
|
423
|
-
end
|
424
|
-
|
425
|
-
# Attaches the fiber to a new parent.
|
426
|
-
#
|
427
|
-
# @param parent [Fiber] new parent
|
428
|
-
# @return [Fiber] self
|
429
|
-
def attach_to(parent)
|
430
|
-
@parent.remove_child(self)
|
431
|
-
@parent = parent
|
432
|
-
parent.add_child(self)
|
433
|
-
self
|
434
|
-
end
|
435
|
-
|
436
|
-
# Attaches the fiber to the new parent and monitors the new parent.
|
437
|
-
#
|
438
|
-
# @param parent [Fiber] new parent
|
439
|
-
# @return [Fiber] self
|
440
|
-
def attach_and_monitor(parent)
|
441
|
-
@parent.remove_child(self)
|
442
|
-
@parent = parent
|
443
|
-
parent.add_child(self)
|
444
|
-
monitor(parent)
|
445
|
-
self
|
446
|
-
end
|
447
|
-
|
448
|
-
# Adds a child fiber reference. Used internally.
|
449
|
-
#
|
450
|
-
# @param child_fiber [Fiber] child fiber
|
451
|
-
# @return [Fiber] self
|
452
|
-
def add_child(child_fiber)
|
453
|
-
(@children ||= {})[child_fiber] = true
|
454
|
-
child_fiber.monitor(self) if @supervise_mode
|
455
|
-
self
|
456
|
-
end
|
457
|
-
|
458
|
-
# Removes a child fiber reference. Used internally.
|
459
|
-
#
|
460
|
-
# @param child_fiber [Fiber] child fiber to be removed
|
461
|
-
# @return [Fiber] self
|
462
|
-
def remove_child(child_fiber)
|
463
|
-
@children.delete(child_fiber) if @children
|
464
|
-
self
|
465
|
-
end
|
466
|
-
end
|
467
|
-
|
468
|
-
# Fiber life cycle methods
|
469
|
-
module FiberLifeCycle
|
470
|
-
|
471
|
-
# Prepares a fiber for running.
|
472
|
-
#
|
473
|
-
# @param tag [any] fiber's tag
|
474
|
-
# @param block [Proc] fiber's block
|
475
|
-
# @param caller [Array<String>] fiber's caller
|
476
|
-
# @param parent [Fiber] fiber's parent
|
477
|
-
# @return [void]
|
478
|
-
def prepare(tag, block, caller, parent)
|
479
|
-
@thread = Thread.current
|
480
|
-
@tag = tag
|
481
|
-
@parent = parent
|
482
|
-
@caller = caller
|
483
|
-
@block = block
|
484
|
-
Thread.backend.trace(:spin, self, Kernel.caller[1..-1])
|
485
|
-
schedule
|
486
|
-
end
|
487
|
-
|
488
|
-
# Runs the fiber's block and handles uncaught exceptions.
|
489
|
-
#
|
490
|
-
# @param first_value [any] value passed to fiber on first resume
|
491
|
-
# @return [void]
|
492
|
-
def run(first_value)
|
493
|
-
Kernel.raise first_value if first_value.is_a?(Exception)
|
494
|
-
@running = true
|
495
|
-
|
496
|
-
Thread.backend.trace(:unblock, self, first_value, @caller)
|
497
|
-
result = @block.(first_value)
|
498
|
-
finalize(result)
|
499
|
-
rescue Polyphony::Restart => e
|
500
|
-
restart_self(e.value)
|
501
|
-
rescue Polyphony::MoveOn, Polyphony::Terminate => e
|
502
|
-
finalize(e.value)
|
503
|
-
rescue Exception => e
|
504
|
-
e.source_fiber = self
|
505
|
-
finalize(e, true)
|
506
|
-
end
|
507
|
-
|
508
|
-
# Performs setup for a "raw" Fiber created using Fiber.new. Note that this
|
509
|
-
# fiber is an orphan fiber (has no parent), since we cannot control how the
|
510
|
-
# fiber terminates after it has already been created. Calling #setup_raw
|
511
|
-
# allows the fiber to be scheduled and to receive messages.
|
512
|
-
#
|
513
|
-
# @return [void]
|
514
|
-
def setup_raw
|
515
|
-
@thread = Thread.current
|
516
|
-
@running = true
|
517
|
-
end
|
518
|
-
|
519
|
-
# Sets up the fiber as the main fiber for the current thread.
|
520
|
-
#
|
521
|
-
# @return [void]
|
522
|
-
def setup_main_fiber
|
523
|
-
@main = true
|
524
|
-
@tag = :main
|
525
|
-
@thread = Thread.current
|
526
|
-
@running = true
|
527
|
-
@children&.clear
|
528
|
-
end
|
529
|
-
|
530
|
-
# Resets the fiber's state and reruns the fiber.
|
531
|
-
#
|
532
|
-
# @param first_value [Fiber] first_value to pass to fiber after restarting
|
533
|
-
# @return [void]
|
534
|
-
def restart_self(first_value)
|
535
|
-
@mailbox = nil
|
536
|
-
run(first_value)
|
537
|
-
end
|
538
|
-
|
539
|
-
# Finalizes the fiber, handling its return value or any uncaught exception.
|
540
|
-
#
|
541
|
-
# @param result [any] return value
|
542
|
-
# @param uncaught_exception [Exception, nil] uncaught exception
|
543
|
-
# @return [void]
|
544
|
-
def finalize(result, uncaught_exception = false)
|
545
|
-
result, uncaught_exception = finalize_children(result, uncaught_exception)
|
546
|
-
Thread.backend.trace(:terminate, self, result)
|
547
|
-
@result = result
|
548
|
-
|
549
|
-
inform_monitors(result, uncaught_exception)
|
550
|
-
@running = false
|
551
|
-
ensure
|
552
|
-
@parent&.remove_child(self)
|
553
|
-
# Prevent fiber from being resumed after terminating
|
554
|
-
@thread.fiber_unschedule(self)
|
555
|
-
Thread.current.switch_fiber
|
556
|
-
end
|
557
|
-
|
558
|
-
# Shuts down all children of the current fiber. If any exception occurs while
|
559
|
-
# the children are shut down, it is returned along with the uncaught_exception
|
560
|
-
# flag set. Otherwise, it returns the given arguments.
|
561
|
-
#
|
562
|
-
# @param result [any] fiber's return value
|
563
|
-
# @param uncaught_exception [Exception, nil] uncaught exception
|
564
|
-
# @return [void]
|
565
|
-
def finalize_children(result, uncaught_exception)
|
566
|
-
shutdown_all_children(graceful_shutdown?)
|
567
|
-
[result, uncaught_exception]
|
568
|
-
rescue Exception => e
|
569
|
-
[e, true]
|
570
|
-
end
|
571
|
-
|
572
|
-
# Informs the fiber's monitors it is terminated.
|
573
|
-
#
|
574
|
-
# @param result [any] fiber's return value
|
575
|
-
# @param uncaught_exception [Exception, nil] uncaught exception
|
576
|
-
# @return [void]
|
577
|
-
def inform_monitors(result, uncaught_exception)
|
578
|
-
if @monitors
|
579
|
-
msg = [self, result]
|
580
|
-
@monitors.each_key { |f| f.monitor_mailbox << msg }
|
581
|
-
end
|
582
|
-
|
583
|
-
if uncaught_exception && @parent
|
584
|
-
parent_is_monitor = @monitors&.has_key?(@parent)
|
585
|
-
@parent.schedule_with_priority(result) unless parent_is_monitor
|
586
|
-
end
|
587
|
-
end
|
632
|
+
private
|
588
633
|
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
self
|
597
|
-
end
|
598
|
-
|
599
|
-
# Removes a monitor fiber.
|
600
|
-
#
|
601
|
-
# @param fiber [Fiber] monitoring fiber
|
602
|
-
# @return [Fiber] self
|
603
|
-
def unmonitor(fiber)
|
604
|
-
(@monitors ||= []).delete(fiber)
|
605
|
-
self
|
606
|
-
end
|
607
|
-
|
608
|
-
# Returns the list of monitoring fibers.
|
609
|
-
#
|
610
|
-
# @return [Array<Fiber>] monitoring fibers
|
611
|
-
def monitors
|
612
|
-
@monitors&.keys || []
|
613
|
-
end
|
614
|
-
|
615
|
-
# Returns true if the fiber is dead.
|
616
|
-
#
|
617
|
-
# @return [bool] is fiber dead
|
618
|
-
def dead?
|
619
|
-
state == :dead
|
634
|
+
# @!visibility private
|
635
|
+
def error_from_raise_args(args)
|
636
|
+
case (arg = args.shift)
|
637
|
+
when String then RuntimeError.new(arg)
|
638
|
+
when Class then arg.new(args.shift)
|
639
|
+
when Exception then arg
|
640
|
+
else RuntimeError.new
|
620
641
|
end
|
621
642
|
end
|
622
|
-
end
|
623
643
|
|
624
|
-
#
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
include Polyphony::FiberLifeCycle
|
644
|
+
# @!visibility private
|
645
|
+
def supervise_opts_to_block(opts)
|
646
|
+
block = opts[:on_done] || opts[:on_error]
|
647
|
+
restart = opts[:restart]
|
648
|
+
return nil unless block || restart
|
630
649
|
|
631
|
-
|
650
|
+
error_only = !!opts[:on_error]
|
651
|
+
restart_always = (restart == :always) || (restart == true)
|
652
|
+
restart_on_error = restart == :on_error
|
632
653
|
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
#
|
638
|
-
# @return [bool] is fiber running
|
639
|
-
def running?
|
640
|
-
@running
|
641
|
-
end
|
642
|
-
|
643
|
-
# Returns a string representation of the fiber for debugging.
|
644
|
-
#
|
645
|
-
# @return [String] string representation
|
646
|
-
def inspect
|
647
|
-
if @tag
|
648
|
-
"#<Fiber #{tag}:#{object_id} #{location} (#{state})>"
|
649
|
-
else
|
650
|
-
"#<Fiber:#{object_id} #{location} (#{state})>"
|
651
|
-
end
|
652
|
-
end
|
653
|
-
alias_method :to_s, :inspect
|
654
|
-
|
655
|
-
# Returns the source location for the fiber based on its caller.
|
656
|
-
#
|
657
|
-
# @return [String] source location
|
658
|
-
def location
|
659
|
-
if @oob
|
660
|
-
"#{@caller[0]} (oob)"
|
661
|
-
else
|
662
|
-
@caller ? @caller[0] : '(root)'
|
663
|
-
end
|
664
|
-
end
|
665
|
-
|
666
|
-
# Returns the fiber's caller.
|
667
|
-
#
|
668
|
-
# @return [Array<String>] caller
|
669
|
-
def caller
|
670
|
-
spin_caller = @caller || []
|
671
|
-
if @parent
|
672
|
-
spin_caller + @parent.caller
|
673
|
-
else
|
674
|
-
spin_caller
|
654
|
+
->(f, r) do
|
655
|
+
is_error = r.is_a?(Exception)
|
656
|
+
block.(f, r) if block && (!error_only || is_error)
|
657
|
+
f.restart if restart_always || (restart_on_error && is_error)
|
675
658
|
end
|
676
659
|
end
|
677
|
-
|
678
|
-
# Sets the fiber's caller.
|
679
|
-
#
|
680
|
-
# @param caller [Array<String>] new caller
|
681
|
-
# @return [Fiber] self
|
682
|
-
def set_caller(caller)
|
683
|
-
@caller = caller
|
684
|
-
self
|
685
|
-
end
|
686
|
-
|
687
|
-
# Returns true if the fiber is the main fiber for its thread.
|
688
|
-
#
|
689
|
-
# @return [bool] is main fiber
|
690
|
-
def main?
|
691
|
-
@main
|
692
|
-
end
|
693
660
|
end
|
694
661
|
|
695
662
|
Fiber.current.setup_main_fiber
|