polyphony 0.99.4 → 0.99.6
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 +11 -0
- data/.yardopts +0 -2
- data/README.md +1 -1
- data/docs/readme.md +1 -1
- data/docs/tutorial.md +2 -2
- 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 +33 -42
- data/ext/polyphony/io_extensions.c +50 -37
- data/ext/polyphony/pipe.c +6 -20
- data/ext/polyphony/polyphony.c +72 -144
- data/ext/polyphony/queue.c +23 -63
- data/ext/polyphony/thread.c +4 -13
- data/lib/polyphony/adapters/process.rb +2 -5
- data/lib/polyphony/adapters/sequel.rb +2 -2
- data/lib/polyphony/core/debug.rb +1 -4
- data/lib/polyphony/core/exceptions.rb +1 -5
- data/lib/polyphony/core/resource_pool.rb +7 -8
- data/lib/polyphony/core/sync.rb +5 -8
- data/lib/polyphony/core/thread_pool.rb +3 -10
- data/lib/polyphony/core/throttler.rb +1 -5
- data/lib/polyphony/core/timer.rb +23 -30
- data/lib/polyphony/extensions/fiber.rb +513 -543
- data/lib/polyphony/extensions/io.rb +5 -14
- data/lib/polyphony/extensions/object.rb +283 -2
- data/lib/polyphony/extensions/openssl.rb +5 -26
- data/lib/polyphony/extensions/pipe.rb +6 -17
- data/lib/polyphony/extensions/socket.rb +24 -118
- data/lib/polyphony/extensions/thread.rb +3 -18
- data/lib/polyphony/extensions/timeout.rb +0 -1
- data/lib/polyphony/net.rb +5 -9
- 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 +5 -9
- data/docs/index.md +0 -94
- data/docs/link_rewriter.rb +0 -17
- data/docs/main-concepts/index.md +0 -9
- data/lib/polyphony/core/global_api.rb +0 -309
- /data/{assets → docs/assets}/echo-fibers.svg +0 -0
- /data/{assets → docs/assets}/polyphony-logo.png +0 -0
- /data/{assets → docs/assets}/sleeping-fiber.svg +0 -0
@@ -2,228 +2,530 @@
|
|
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
|
+
def supervise(*fibers, **opts, &block)
|
217
|
+
block ||= supervise_opts_to_block(opts)
|
218
|
+
|
219
|
+
@supervise_mode = true
|
220
|
+
fibers = children if fibers.empty?
|
221
|
+
fibers.each do |f|
|
222
|
+
f.attach_to(self) unless f.parent == self
|
223
|
+
f.monitor(self)
|
123
224
|
end
|
124
225
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
226
|
+
mailbox = monitor_mailbox
|
227
|
+
|
228
|
+
while true
|
229
|
+
(fiber, result) = mailbox.shift
|
230
|
+
block&.call(fiber, result)
|
130
231
|
end
|
131
|
-
|
232
|
+
ensure
|
233
|
+
@supervise_mode = false
|
234
|
+
end
|
132
235
|
|
133
|
-
|
236
|
+
###############################
|
237
|
+
# Child fiber control methods #
|
238
|
+
###############################
|
134
239
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
240
|
+
# Returns the fiber's children.
|
241
|
+
#
|
242
|
+
# @return [Array<Fiber>] child fibers
|
243
|
+
def children
|
244
|
+
(@children ||= {}).keys
|
245
|
+
end
|
246
|
+
|
247
|
+
# Creates a new child fiber.
|
248
|
+
#
|
249
|
+
# child = fiber.spin { sleep 10; fiber.stop }
|
250
|
+
#
|
251
|
+
# @param tag [any] child fiber's tag
|
252
|
+
# @param orig_caller [Array<String>] caller to set for fiber
|
253
|
+
# @return [Fiber] child fiber
|
254
|
+
def spin(tag = nil, orig_caller = Kernel.caller, &block)
|
255
|
+
f = Fiber.new { |v| f.run(v) }
|
256
|
+
f.prepare(tag, block, orig_caller, self)
|
257
|
+
(@children ||= {})[f] = true
|
258
|
+
f.monitor(self) if @supervise_mode
|
259
|
+
f
|
260
|
+
end
|
261
|
+
|
262
|
+
# Terminates all child fibers. This method will return before the fibers are
|
263
|
+
# actually terminated.
|
264
|
+
#
|
265
|
+
# @param graceful [bool] whether to perform a graceful termination
|
266
|
+
# @return [Fiber] self
|
267
|
+
def terminate_all_children(graceful = false)
|
268
|
+
return self unless @children
|
269
|
+
|
270
|
+
e = Polyphony::Terminate.new
|
271
|
+
@children.each_key do |c|
|
272
|
+
c.graceful_shutdown = true if graceful
|
273
|
+
c.raise e
|
143
274
|
end
|
275
|
+
self
|
144
276
|
end
|
145
277
|
|
146
|
-
#
|
147
|
-
|
278
|
+
# Block until all child fibers have terminated. Returns the return values
|
279
|
+
# for all child fibers.
|
280
|
+
#
|
281
|
+
# @return [Array<any>] return values of child fibers
|
282
|
+
def await_all_children
|
283
|
+
return unless @children && !@children.empty?
|
148
284
|
|
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)
|
182
|
-
|
183
|
-
@supervise_mode = true
|
184
|
-
fibers = children if fibers.empty?
|
185
|
-
fibers.each do |f|
|
186
|
-
f.attach_to(self) unless f.parent == self
|
187
|
-
f.monitor(self)
|
188
|
-
end
|
285
|
+
Fiber.await(*@children.keys.reject { |c| c.dead? })
|
286
|
+
end
|
189
287
|
|
190
|
-
|
288
|
+
# Terminates and blocks until all child fibers have terminated.
|
289
|
+
#
|
290
|
+
# @return [Fiber] self
|
291
|
+
def shutdown_all_children(graceful = false)
|
292
|
+
return self unless @children
|
191
293
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
294
|
+
pending = []
|
295
|
+
@children.keys.each do |c|
|
296
|
+
next if c.dead?
|
297
|
+
|
298
|
+
c.terminate(graceful)
|
299
|
+
pending << c
|
198
300
|
end
|
301
|
+
Fiber.await(*pending)
|
302
|
+
self
|
303
|
+
end
|
199
304
|
|
200
|
-
|
305
|
+
# Attaches all child fibers to a new parent.
|
306
|
+
#
|
307
|
+
# @param parent [Fiber] new parent
|
308
|
+
# @return [Fiber] self
|
309
|
+
def attach_all_children_to(parent)
|
310
|
+
@children&.keys.each { |c| c.attach_to(parent) }
|
311
|
+
self
|
312
|
+
end
|
201
313
|
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
314
|
+
# Detaches the fiber from its current parent. The fiber will be made a child
|
315
|
+
# of the main fiber (for the current thread.)
|
316
|
+
#
|
317
|
+
# @return [Fiber] self
|
318
|
+
def detach
|
319
|
+
@parent.remove_child(self)
|
320
|
+
@parent = @thread.main_fiber
|
321
|
+
@parent.add_child(self)
|
322
|
+
self
|
323
|
+
end
|
324
|
+
|
325
|
+
# Attaches the fiber to a new parent.
|
326
|
+
#
|
327
|
+
# @param parent [Fiber] new parent
|
328
|
+
# @return [Fiber] self
|
329
|
+
def attach_to(parent)
|
330
|
+
@parent.remove_child(self)
|
331
|
+
@parent = parent
|
332
|
+
parent.add_child(self)
|
333
|
+
self
|
334
|
+
end
|
335
|
+
|
336
|
+
# Attaches the fiber to the new parent and monitors the new parent.
|
337
|
+
#
|
338
|
+
# @param parent [Fiber] new parent
|
339
|
+
# @return [Fiber] self
|
340
|
+
def attach_and_monitor(parent)
|
341
|
+
@parent.remove_child(self)
|
342
|
+
@parent = parent
|
343
|
+
parent.add_child(self)
|
344
|
+
monitor(parent)
|
345
|
+
self
|
346
|
+
end
|
347
|
+
|
348
|
+
# Adds a child fiber reference. Used internally.
|
349
|
+
#
|
350
|
+
# @param child_fiber [Fiber] child fiber
|
351
|
+
# @return [Fiber] self
|
352
|
+
def add_child(child_fiber)
|
353
|
+
(@children ||= {})[child_fiber] = true
|
354
|
+
child_fiber.monitor(self) if @supervise_mode
|
355
|
+
self
|
356
|
+
end
|
357
|
+
|
358
|
+
# Removes a child fiber reference. Used internally.
|
359
|
+
#
|
360
|
+
# @param child_fiber [Fiber] child fiber to be removed
|
361
|
+
# @return [Fiber] self
|
362
|
+
def remove_child(child_fiber)
|
363
|
+
@children.delete(child_fiber) if @children
|
364
|
+
self
|
365
|
+
end
|
366
|
+
|
367
|
+
############################
|
368
|
+
# Fiber life cycle methods #
|
369
|
+
############################
|
370
|
+
|
371
|
+
# Prepares a fiber for running.
|
372
|
+
#
|
373
|
+
# @param tag [any] fiber's tag
|
374
|
+
# @param block [Proc] fiber's block
|
375
|
+
# @param caller [Array<String>] fiber's caller
|
376
|
+
# @param parent [Fiber] fiber's parent
|
377
|
+
# @return [Fiber] self
|
378
|
+
def prepare(tag, block, caller, parent)
|
379
|
+
@thread = Thread.current
|
380
|
+
@tag = tag
|
381
|
+
@parent = parent
|
382
|
+
@caller = caller
|
383
|
+
@block = block
|
384
|
+
Thread.backend.trace(:spin, self, Kernel.caller[1..-1])
|
385
|
+
schedule
|
386
|
+
self
|
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 [any] fiber result
|
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
|
+
result
|
401
|
+
rescue Polyphony::Restart => e
|
402
|
+
restart_self(e.value)
|
403
|
+
rescue Polyphony::MoveOn, Polyphony::Terminate => e
|
404
|
+
finalize(e.value)
|
405
|
+
rescue Exception => e
|
406
|
+
e.source_fiber = self
|
407
|
+
finalize(e, true)
|
408
|
+
end
|
409
|
+
|
410
|
+
# Performs setup for a "raw" Fiber created using Fiber.new. Note that this
|
411
|
+
# fiber is an orphan fiber (has no parent), since we cannot control how the
|
412
|
+
# fiber terminates after it has already been created. Calling #setup_raw
|
413
|
+
# allows the fiber to be scheduled and to receive messages.
|
414
|
+
#
|
415
|
+
# @return [Fiber] self
|
416
|
+
def setup_raw
|
417
|
+
@thread = Thread.current
|
418
|
+
@running = true
|
419
|
+
self
|
420
|
+
end
|
421
|
+
|
422
|
+
# Sets up the fiber as the main fiber for the current thread.
|
423
|
+
#
|
424
|
+
# @return [Fiber] self
|
425
|
+
def setup_main_fiber
|
426
|
+
@main = true
|
427
|
+
@tag = :main
|
428
|
+
@thread = Thread.current
|
429
|
+
@running = true
|
430
|
+
@children&.clear
|
431
|
+
self
|
432
|
+
end
|
433
|
+
|
434
|
+
# Resets the fiber's state and reruns the fiber.
|
435
|
+
#
|
436
|
+
# @param first_value [Fiber] first_value to pass to fiber after restarting
|
437
|
+
# @return [any] fiber result
|
438
|
+
def restart_self(first_value)
|
439
|
+
@mailbox = nil
|
440
|
+
run(first_value)
|
441
|
+
end
|
442
|
+
|
443
|
+
# Finalizes the fiber, handling its return value or any uncaught exception.
|
444
|
+
#
|
445
|
+
# @param result [any] return value
|
446
|
+
# @param uncaught_exception [Exception, nil] uncaught exception
|
447
|
+
# @return [false]
|
448
|
+
def finalize(result, uncaught_exception = false)
|
449
|
+
result, uncaught_exception = finalize_children(result, uncaught_exception)
|
450
|
+
Thread.backend.trace(:terminate, self, result)
|
451
|
+
@result = result
|
452
|
+
|
453
|
+
inform_monitors(result, uncaught_exception)
|
454
|
+
@running = false
|
455
|
+
ensure
|
456
|
+
@parent&.remove_child(self)
|
457
|
+
# Prevent fiber from being resumed after terminating
|
458
|
+
@thread.fiber_unschedule(self)
|
459
|
+
Thread.current.switch_fiber
|
460
|
+
end
|
461
|
+
|
462
|
+
# Shuts down all children of the current fiber. If any exception occurs while
|
463
|
+
# the children are shut down, it is returned along with the uncaught_exception
|
464
|
+
# flag set. Otherwise, it returns the given arguments.
|
465
|
+
#
|
466
|
+
# @param result [any] fiber's return value
|
467
|
+
# @param uncaught_exception [Exception, nil] uncaught exception
|
468
|
+
# @return [Array] array containing result and uncaught exception if any
|
469
|
+
def finalize_children(result, uncaught_exception)
|
470
|
+
shutdown_all_children(graceful_shutdown?)
|
471
|
+
[result, uncaught_exception]
|
472
|
+
rescue Exception => e
|
473
|
+
[e, true]
|
474
|
+
end
|
475
|
+
|
476
|
+
# Informs the fiber's monitors it is terminated.
|
477
|
+
#
|
478
|
+
# @param result [any] fiber's return value
|
479
|
+
# @param uncaught_exception [Exception, nil] uncaught exception
|
480
|
+
# @return [Fiber] self
|
481
|
+
def inform_monitors(result, uncaught_exception)
|
482
|
+
if @monitors
|
483
|
+
msg = [self, result]
|
484
|
+
@monitors.each_key { |f| f.monitor_mailbox << msg }
|
485
|
+
end
|
486
|
+
|
487
|
+
if uncaught_exception && @parent
|
488
|
+
parent_is_monitor = @monitors&.has_key?(@parent)
|
489
|
+
@parent.schedule_with_priority(result) unless parent_is_monitor
|
217
490
|
end
|
491
|
+
|
492
|
+
self
|
218
493
|
end
|
219
494
|
|
220
|
-
#
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
495
|
+
# Adds a fiber to the list of monitoring fibers. Monitoring fibers will be
|
496
|
+
# notified on their monitor mailboxes when the fiber is terminated.
|
497
|
+
#
|
498
|
+
# @param fiber [Fiber] monitoring fiber
|
499
|
+
# @return [Fiber] self
|
500
|
+
def monitor(fiber)
|
501
|
+
(@monitors ||= {})[fiber] = true
|
502
|
+
self
|
503
|
+
end
|
504
|
+
|
505
|
+
# Removes a monitor fiber.
|
506
|
+
#
|
507
|
+
# @param fiber [Fiber] monitoring fiber
|
508
|
+
# @return [Fiber] self
|
509
|
+
def unmonitor(fiber)
|
510
|
+
(@monitors ||= []).delete(fiber)
|
511
|
+
self
|
512
|
+
end
|
513
|
+
|
514
|
+
# Returns the list of monitoring fibers.
|
515
|
+
#
|
516
|
+
# @return [Array<Fiber>] monitoring fibers
|
517
|
+
def monitors
|
518
|
+
@monitors&.keys || []
|
519
|
+
end
|
520
|
+
|
521
|
+
# Returns true if the fiber is dead.
|
522
|
+
#
|
523
|
+
# @return [bool] is fiber dead
|
524
|
+
def dead?
|
525
|
+
state == :dead
|
526
|
+
end
|
527
|
+
|
528
|
+
class << self
|
227
529
|
# Waits for all given fibers to terminate, then returns the respective
|
228
530
|
# return values for all terminated fibers. If any of the awaited fibers
|
229
531
|
# terminates with an uncaught exception, `Fiber.await` will await all the
|
@@ -301,9 +603,6 @@ module Polyphony
|
|
301
603
|
# running, it will bubble up to the main thread's main fiber, which will
|
302
604
|
# also be scheduled with priority. This method is mainly used trapping
|
303
605
|
# signals (see also the patched `Kernel#trap`)
|
304
|
-
#
|
305
|
-
# @yield [] given block
|
306
|
-
# @return [void]
|
307
606
|
def schedule_priority_oob_fiber(&block)
|
308
607
|
oob_fiber = Fiber.new do
|
309
608
|
Fiber.current.setup_raw
|
@@ -333,362 +632,33 @@ module Polyphony
|
|
333
632
|
end
|
334
633
|
end
|
335
634
|
|
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
|
635
|
+
private
|
404
636
|
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
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
|
637
|
+
# @!visibility private
|
638
|
+
def error_from_raise_args(args)
|
639
|
+
case (arg = args.shift)
|
640
|
+
when String then RuntimeError.new(arg)
|
641
|
+
when Class then arg.new(args.shift)
|
642
|
+
when Exception then arg
|
643
|
+
else RuntimeError.new
|
465
644
|
end
|
466
645
|
end
|
467
646
|
|
468
|
-
#
|
469
|
-
|
647
|
+
# @!visibility private
|
648
|
+
def supervise_opts_to_block(opts)
|
649
|
+
block = opts[:on_done] || opts[:on_error]
|
650
|
+
restart = opts[:restart]
|
651
|
+
return nil unless block || restart
|
470
652
|
|
471
|
-
|
472
|
-
|
473
|
-
|
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
|
653
|
+
error_only = !!opts[:on_error]
|
654
|
+
restart_always = (restart == :always) || (restart == true)
|
655
|
+
restart_on_error = restart == :on_error
|
487
656
|
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
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
|
657
|
+
->(f, r) do
|
658
|
+
is_error = r.is_a?(Exception)
|
659
|
+
block.(f, r) if block && (!error_only || is_error)
|
660
|
+
f.restart if restart_always || (restart_on_error && is_error)
|
528
661
|
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
|
588
|
-
|
589
|
-
# Adds a fiber to the list of monitoring fibers. Monitoring fibers will be
|
590
|
-
# notified on their monitor mailboxes when the fiber is terminated.
|
591
|
-
#
|
592
|
-
# @param fiber [Fiber] monitoring fiber
|
593
|
-
# @return [Fiber] self
|
594
|
-
def monitor(fiber)
|
595
|
-
(@monitors ||= {})[fiber] = true
|
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
|
620
|
-
end
|
621
|
-
end
|
622
|
-
end
|
623
|
-
|
624
|
-
# Fiber extensions
|
625
|
-
class ::Fiber
|
626
|
-
prepend Polyphony::FiberControl
|
627
|
-
include Polyphony::FiberSupervision
|
628
|
-
include Polyphony::ChildFiberControl
|
629
|
-
include Polyphony::FiberLifeCycle
|
630
|
-
|
631
|
-
extend Polyphony::FiberControlClassMethods
|
632
|
-
|
633
|
-
attr_accessor :tag, :thread, :parent, :oob
|
634
|
-
attr_reader :result
|
635
|
-
|
636
|
-
# Returns true if fiber is running.
|
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
|
675
|
-
end
|
676
|
-
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
662
|
end
|
693
663
|
end
|
694
664
|
|