polyphony 0.99.3 → 0.99.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +10 -0
- data/.yardopts +3 -1
- data/README.md +1 -1
- data/docs/readme.md +102 -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 +3 -2
@@ -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
|