polyphony 0.33 → 0.34
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/Gemfile.lock +1 -1
- data/TODO.md +93 -68
- data/bin/polyphony-debug +87 -0
- data/docs/_includes/nav.html +5 -1
- data/docs/_sass/overrides.scss +4 -1
- data/docs/api-reference.md +11 -0
- data/docs/api-reference/exception.md +27 -0
- data/docs/api-reference/fiber.md +407 -0
- data/docs/api-reference/io.md +36 -0
- data/docs/api-reference/object.md +99 -0
- data/docs/api-reference/polyphony-baseexception.md +33 -0
- data/docs/api-reference/polyphony-cancel.md +26 -0
- data/docs/api-reference/polyphony-moveon.md +24 -0
- data/docs/api-reference/polyphony-net.md +20 -0
- data/docs/api-reference/polyphony-process.md +28 -0
- data/docs/api-reference/polyphony-resourcepool.md +59 -0
- data/docs/api-reference/polyphony-restart.md +18 -0
- data/docs/api-reference/polyphony-terminate.md +18 -0
- data/docs/api-reference/polyphony-threadpool.md +67 -0
- data/docs/api-reference/polyphony-throttler.md +77 -0
- data/docs/api-reference/polyphony.md +36 -0
- data/docs/api-reference/thread.md +88 -0
- data/docs/getting-started/tutorial.md +59 -156
- data/docs/index.md +2 -0
- data/examples/core/forever_sleep.rb +19 -0
- data/examples/core/xx-caller.rb +12 -0
- data/examples/core/xx-exception-backtrace.rb +40 -0
- data/examples/core/xx-fork-spin.rb +42 -0
- data/examples/core/xx-spin-fork.rb +49 -0
- data/examples/core/xx-supervise-process.rb +30 -0
- data/ext/gyro/gyro.h +1 -0
- data/ext/gyro/selector.c +8 -0
- data/ext/gyro/thread.c +8 -2
- data/lib/polyphony.rb +64 -17
- data/lib/polyphony/adapters/process.rb +29 -0
- data/lib/polyphony/adapters/trace.rb +6 -4
- data/lib/polyphony/core/exceptions.rb +5 -0
- data/lib/polyphony/core/global_api.rb +15 -0
- data/lib/polyphony/extensions/fiber.rb +89 -59
- data/lib/polyphony/version.rb +1 -1
- data/test/test_fiber.rb +23 -75
- data/test/test_global_api.rb +39 -0
- data/test/test_kernel.rb +5 -7
- data/test/test_process_supervision.rb +46 -0
- data/test/test_signal.rb +2 -3
- data/test/test_supervise.rb +103 -0
- metadata +29 -2
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'polyphony'
|
5
|
+
|
6
|
+
Exception.__disable_sanitized_backtrace__ = true
|
7
|
+
|
8
|
+
puts "Parent pid: #{Process.pid}"
|
9
|
+
|
10
|
+
i, o = IO.pipe
|
11
|
+
|
12
|
+
pid = Polyphony.fork do
|
13
|
+
puts "Child pid: #{Process.pid}"
|
14
|
+
i.close
|
15
|
+
spin do
|
16
|
+
spin do
|
17
|
+
p :sleep
|
18
|
+
sleep 1
|
19
|
+
rescue ::Interrupt => e
|
20
|
+
p 1
|
21
|
+
# the signal should be raised only in the main fiber
|
22
|
+
o.puts "1-interrupt"
|
23
|
+
end.await
|
24
|
+
rescue Polyphony::Terminate
|
25
|
+
puts "terminate!"
|
26
|
+
end.await
|
27
|
+
rescue ::Interrupt => e
|
28
|
+
p 2
|
29
|
+
o.puts "3-interrupt"
|
30
|
+
ensure
|
31
|
+
p 3
|
32
|
+
o.close
|
33
|
+
end
|
34
|
+
sleep 0.2
|
35
|
+
o.close
|
36
|
+
watcher = Gyro::Child.new(pid)
|
37
|
+
Process.kill('INT', pid)
|
38
|
+
watcher.await
|
39
|
+
buffer = i.read
|
40
|
+
|
41
|
+
puts '*' * 40
|
42
|
+
p buffer
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'polyphony'
|
5
|
+
|
6
|
+
Exception.__disable_sanitized_backtrace__ = true
|
7
|
+
|
8
|
+
puts "Parent pid: #{Process.pid}"
|
9
|
+
|
10
|
+
def start_worker
|
11
|
+
Polyphony.fork do
|
12
|
+
p :sleep
|
13
|
+
sleep 5
|
14
|
+
p :done_sleeping
|
15
|
+
ensure
|
16
|
+
p :start_worker_fork_ensure
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
f = spin do
|
21
|
+
Polyphony::ProcessSupervisor.supervise do
|
22
|
+
spin do
|
23
|
+
spin do
|
24
|
+
p :sleep
|
25
|
+
sleep 5
|
26
|
+
p :done_sleeping
|
27
|
+
end.await
|
28
|
+
end.await
|
29
|
+
ensure
|
30
|
+
p :start_worker_fork_ensure
|
31
|
+
end
|
32
|
+
# spin do
|
33
|
+
# pid = start_worker
|
34
|
+
# p [:before_child_await, pid]
|
35
|
+
# Gyro::Child.new(pid).await
|
36
|
+
# p :after_child_await
|
37
|
+
# ensure
|
38
|
+
# puts "child done"
|
39
|
+
# end
|
40
|
+
# supervise
|
41
|
+
# ensure
|
42
|
+
# puts "kill child"
|
43
|
+
# Process.kill('TERM', pid) rescue nil
|
44
|
+
end
|
45
|
+
|
46
|
+
sleep 1
|
47
|
+
puts "terminate worker"
|
48
|
+
f.terminate
|
49
|
+
f.await
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'polyphony'
|
5
|
+
|
6
|
+
Exception.__disable_sanitized_backtrace__ = true
|
7
|
+
|
8
|
+
supervisor = spin do
|
9
|
+
puts "parent pid #{Process.pid}"
|
10
|
+
|
11
|
+
Polyphony::ProcessSupervisor.supervise do
|
12
|
+
puts "child pid #{Process.pid}"
|
13
|
+
puts "go to sleep"
|
14
|
+
sleep 5
|
15
|
+
rescue Interrupt
|
16
|
+
puts "child got INT"
|
17
|
+
rescue SystemExit
|
18
|
+
puts "child got TERM"
|
19
|
+
ensure
|
20
|
+
puts "done sleeping"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
begin
|
25
|
+
supervisor.await
|
26
|
+
rescue Interrupt
|
27
|
+
exit!
|
28
|
+
# supervisor.terminate
|
29
|
+
# supervisor.await
|
30
|
+
end
|
data/ext/gyro/gyro.h
CHANGED
@@ -41,6 +41,7 @@ struct ev_loop *Gyro_Selector_ev_loop(VALUE selector);
|
|
41
41
|
ev_tstamp Gyro_Selector_now(VALUE selector);
|
42
42
|
struct ev_loop *Gyro_Selector_current_thread_ev_loop();
|
43
43
|
long Gyro_Selector_pending_count(VALUE self);
|
44
|
+
VALUE Gyro_Selector_post_fork(VALUE self);
|
44
45
|
|
45
46
|
VALUE Thread_current_event_selector();
|
46
47
|
VALUE Thread_ref(VALUE thread);
|
data/ext/gyro/selector.c
CHANGED
@@ -129,6 +129,14 @@ VALUE Gyro_Selector_stop(VALUE self) {
|
|
129
129
|
return Qnil;
|
130
130
|
}
|
131
131
|
|
132
|
+
VALUE Gyro_Selector_post_fork(VALUE self) {
|
133
|
+
struct Gyro_Selector *selector;
|
134
|
+
GetGyro_Selector(self, selector);
|
135
|
+
|
136
|
+
ev_loop_fork(selector->ev_loop);
|
137
|
+
return self;
|
138
|
+
}
|
139
|
+
|
132
140
|
VALUE Gyro_Selector_break_out_of_ev_loop(VALUE self) {
|
133
141
|
struct Gyro_Selector *selector;
|
134
142
|
GetGyro_Selector(self, selector);
|
data/ext/gyro/thread.c
CHANGED
@@ -107,6 +107,7 @@ VALUE Thread_schedule_fiber(VALUE self, VALUE fiber, VALUE value) {
|
|
107
107
|
if (rb_fiber_alive_p(fiber) != Qtrue) {
|
108
108
|
return self;
|
109
109
|
}
|
110
|
+
|
110
111
|
FIBER_TRACE(3, SYM_fiber_schedule, fiber, value);
|
111
112
|
// if fiber is already scheduled, just set the scheduled value, then return
|
112
113
|
rb_ivar_set(fiber, ID_runnable_value, value);
|
@@ -185,6 +186,8 @@ VALUE Thread_switch_fiber(VALUE self) {
|
|
185
186
|
int ref_count = Thread_fiber_ref_count(self);
|
186
187
|
if (next_fiber != Qnil) {
|
187
188
|
if (ref_count > 0) {
|
189
|
+
// this mechanism prevents event starvation in case the run queue never
|
190
|
+
// empties
|
188
191
|
Gyro_Selector_run_no_wait(selector, current_fiber, RARRAY_LEN(queue));
|
189
192
|
}
|
190
193
|
break;
|
@@ -218,8 +221,9 @@ VALUE Thread_reset_fiber_scheduling(VALUE self) {
|
|
218
221
|
}
|
219
222
|
|
220
223
|
VALUE Thread_post_fork(VALUE self) {
|
221
|
-
|
222
|
-
|
224
|
+
VALUE selector = rb_ivar_get(self, ID_ivar_event_selector);
|
225
|
+
Gyro_Selector_post_fork(selector);
|
226
|
+
|
223
227
|
return self;
|
224
228
|
}
|
225
229
|
|
@@ -259,6 +263,8 @@ void Init_Thread() {
|
|
259
263
|
rb_define_method(rb_cThread, "fiber_ref", Thread_ref, 0);
|
260
264
|
rb_define_method(rb_cThread, "fiber_unref", Thread_unref, 0);
|
261
265
|
|
266
|
+
rb_define_method(rb_cThread, "post_fork", Thread_post_fork, 0);
|
267
|
+
|
262
268
|
rb_define_method(rb_cThread, "setup_fiber_scheduling", Thread_setup_fiber_scheduling, 0);
|
263
269
|
rb_define_method(rb_cThread, "stop_event_selector", Thread_stop_event_selector, 0);
|
264
270
|
rb_define_method(rb_cThread, "reset_fiber_scheduling", Thread_reset_fiber_scheduling, 0);
|
data/lib/polyphony.rb
CHANGED
@@ -31,6 +31,7 @@ module Polyphony
|
|
31
31
|
auto_import(
|
32
32
|
Channel: './polyphony/core/channel',
|
33
33
|
FS: './polyphony/adapters/fs',
|
34
|
+
Process: './polyphony/adapters/process',
|
34
35
|
ResourcePool: './polyphony/core/resource_pool',
|
35
36
|
Sync: './polyphony/core/sync',
|
36
37
|
ThreadPool: './polyphony/core/thread_pool',
|
@@ -51,27 +52,73 @@ module Polyphony
|
|
51
52
|
end
|
52
53
|
|
53
54
|
def fork(&block)
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
Fiber.current.await_all_children
|
55
|
+
Kernel.fork do
|
56
|
+
# Since the fiber doing the fork will become the main fiber of the
|
57
|
+
# forked process, we leave it behind by transferring to a new fiber
|
58
|
+
# created in the context of the forked process, which rescues *all*
|
59
|
+
# exceptions, including Interrupt and SystemExit.
|
60
|
+
spin_forked_block(&block).transfer
|
61
61
|
end
|
62
|
-
pid
|
63
62
|
end
|
64
|
-
end
|
65
|
-
end
|
66
63
|
|
67
|
-
|
64
|
+
def spin_forked_block(&block)
|
65
|
+
Fiber.new do
|
66
|
+
run_forked_block(&block)
|
67
|
+
exit_forked_process
|
68
|
+
rescue ::SystemExit
|
69
|
+
exit_forked_process
|
70
|
+
rescue Exception => e
|
71
|
+
e.full_message
|
72
|
+
exit!
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def run_forked_block(&block)
|
77
|
+
# A race condition can arise if a TERM or INT signal is received before
|
78
|
+
# the forked process has finished initializing. To prevent this we restore
|
79
|
+
# the default signal handlers, and then reinstall the custom Polyphony
|
80
|
+
# handlers just before running the given block.
|
81
|
+
trap('SIGTERM', 'DEFAULT')
|
82
|
+
trap('SIGINT', 'DEFAULT')
|
83
|
+
|
84
|
+
Thread.current.post_fork
|
85
|
+
Thread.current.setup
|
86
|
+
Fiber.current.setup_main_fiber
|
87
|
+
|
88
|
+
install_terminating_signal_handlers
|
89
|
+
|
90
|
+
block.()
|
91
|
+
end
|
92
|
+
|
93
|
+
def exit_forked_process
|
94
|
+
Fiber.current.shutdown_all_children
|
95
|
+
# Since fork could be called from any fiber, we explicitly call exit here.
|
96
|
+
# Otherwise, the fiber might want to pass execution to another fiber that
|
97
|
+
# previously transferred execution to the forking fiber, but doesn't exist
|
98
|
+
# anymore...
|
99
|
+
#
|
100
|
+
# The call to exit will invoke the at_exit handler we use to terminate the
|
101
|
+
# (forked) main fiber's child fibers.
|
102
|
+
exit
|
103
|
+
end
|
68
104
|
|
69
|
-
def
|
70
|
-
|
71
|
-
|
72
|
-
|
105
|
+
def watch_process(cmd = nil, &block)
|
106
|
+
Polyphony::Process.watch(cmd, &block)
|
107
|
+
end
|
108
|
+
|
109
|
+
def emit_signal_exception(exception, fiber = Thread.main.main_fiber)
|
110
|
+
Thread.current.break_out_of_ev_loop(fiber, exception)
|
111
|
+
end
|
112
|
+
|
113
|
+
def install_terminating_signal_handler(signal, exception_class)
|
114
|
+
trap(signal) { emit_signal_exception(exception_class.new) }
|
115
|
+
end
|
116
|
+
|
117
|
+
def install_terminating_signal_handlers
|
118
|
+
install_terminating_signal_handler('SIGTERM', ::SystemExit)
|
119
|
+
install_terminating_signal_handler('SIGINT', ::Interrupt)
|
120
|
+
end
|
73
121
|
end
|
74
122
|
end
|
75
123
|
|
76
|
-
|
77
|
-
install_terminating_signal_handler('SIGINT', Interrupt)
|
124
|
+
Polyphony.install_terminating_signal_handlers
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
export :watch
|
4
|
+
|
5
|
+
def watch(cmd = nil, &block)
|
6
|
+
terminated = nil
|
7
|
+
pid = cmd ? Kernel.spawn(cmd) : Polyphony.fork(&block)
|
8
|
+
watcher = Gyro::Child.new(pid)
|
9
|
+
watcher.await
|
10
|
+
terminated = true
|
11
|
+
ensure
|
12
|
+
kill_process(pid) unless terminated || pid.nil?
|
13
|
+
end
|
14
|
+
|
15
|
+
def kill_process(pid)
|
16
|
+
cancel_after(5) do
|
17
|
+
kill_and_await('TERM', pid)
|
18
|
+
end
|
19
|
+
rescue Polyphony::Cancel
|
20
|
+
kill_and_await(-9, pid)
|
21
|
+
end
|
22
|
+
|
23
|
+
def kill_and_await(sig, pid)
|
24
|
+
Process.kill(sig, pid)
|
25
|
+
Gyro::Child.new(pid).await
|
26
|
+
rescue SystemCallError
|
27
|
+
# ignore
|
28
|
+
puts 'SystemCallError in kill_and_await'
|
29
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
export :new, :analyze
|
3
|
+
export :new, :analyze, :STOCK_EVENTS
|
4
4
|
|
5
5
|
require 'polyphony'
|
6
6
|
|
@@ -14,9 +14,11 @@ end
|
|
14
14
|
|
15
15
|
def trace_record(trp, start_stamp)
|
16
16
|
stamp = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - start_stamp
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
|
18
|
+
{ stamp: stamp, event: trp.event, location: "#{trp.path}:#{trp.lineno}",
|
19
|
+
self: trp.self, binding: trp.binding, fiber: tp_fiber(trp),
|
20
|
+
lineno: trp.lineno, method_id: trp.method_id,
|
21
|
+
path: trp.path, parameters: tp_params(trp),
|
20
22
|
return_value: tp_return_value(trp), schedule_value: tp_schedule_value(trp),
|
21
23
|
exception: tp_raised_exception(trp) }
|
22
24
|
end
|
@@ -12,8 +12,13 @@ class BaseException < ::Exception
|
|
12
12
|
attr_reader :value
|
13
13
|
|
14
14
|
def initialize(value = nil)
|
15
|
+
@caller_backtrace = caller
|
15
16
|
@value = value
|
16
17
|
end
|
18
|
+
|
19
|
+
def backtrace
|
20
|
+
sanitize(@caller_backtrace)
|
21
|
+
end
|
17
22
|
end
|
18
23
|
|
19
24
|
# MoveOn is used to interrupt a long-running blocking operation, while
|
@@ -23,6 +23,10 @@ module API
|
|
23
23
|
sleep interval
|
24
24
|
fiber.schedule Exceptions::Cancel.new
|
25
25
|
end
|
26
|
+
block ? cancel_after_wrap_block(canceller, &block) : canceller
|
27
|
+
end
|
28
|
+
|
29
|
+
def cancel_after_wrap_block(canceller, &block)
|
26
30
|
block.call
|
27
31
|
ensure
|
28
32
|
canceller.stop
|
@@ -54,6 +58,17 @@ module API
|
|
54
58
|
|
55
59
|
def move_on_after(interval, with_value: nil, &block)
|
56
60
|
fiber = ::Fiber.current
|
61
|
+
unless block
|
62
|
+
return spin do
|
63
|
+
sleep interval
|
64
|
+
fiber.schedule with_value
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
move_on_after_with_block(fiber, interval, with_value, &block)
|
69
|
+
end
|
70
|
+
|
71
|
+
def move_on_after_with_block(fiber, interval, with_value, &block)
|
57
72
|
canceller = spin do
|
58
73
|
sleep interval
|
59
74
|
fiber.schedule Exceptions::MoveOn.new(with_value)
|
@@ -29,10 +29,15 @@ module FiberControl
|
|
29
29
|
|
30
30
|
def restart(value = nil)
|
31
31
|
raise "Can''t restart main fiber" if @main
|
32
|
-
return parent.spin(&@block).tap { |f| f.schedule(value) } unless @running
|
33
32
|
|
34
|
-
|
35
|
-
|
33
|
+
if @running
|
34
|
+
schedule Exceptions::Restart.new(value)
|
35
|
+
return self
|
36
|
+
end
|
37
|
+
|
38
|
+
parent.spin(@tag, @caller, &@block).tap do |f|
|
39
|
+
f.schedule(value) unless value.nil?
|
40
|
+
end
|
36
41
|
end
|
37
42
|
alias_method :reset, :restart
|
38
43
|
|
@@ -61,32 +66,37 @@ module FiberControl
|
|
61
66
|
else RuntimeError.new
|
62
67
|
end
|
63
68
|
end
|
69
|
+
end
|
64
70
|
|
65
|
-
|
66
|
-
|
67
|
-
|
71
|
+
# Fiber supervision
|
72
|
+
module FiberSupervision
|
73
|
+
def supervise(opts = {})
|
74
|
+
@counter = 0
|
75
|
+
@on_child_done = proc do |fiber, result|
|
76
|
+
self << fiber unless result.is_a?(Exception)
|
77
|
+
end
|
78
|
+
loop { supervise_perform(opts) }
|
79
|
+
ensure
|
80
|
+
@on_child_done = nil
|
68
81
|
end
|
69
82
|
|
70
|
-
def supervise_perform(
|
71
|
-
|
72
|
-
|
83
|
+
def supervise_perform(opts)
|
84
|
+
fiber = receive
|
85
|
+
restart_fiber(fiber, opts) if fiber
|
86
|
+
rescue Exceptions::Restart
|
73
87
|
restart_all_children
|
74
88
|
rescue Exception => e
|
75
|
-
|
76
|
-
when nil, self
|
77
|
-
Kernel.raise e
|
78
|
-
else
|
79
|
-
handle_supervisor_exception(e, e.source_fiber, policy, &block)
|
80
|
-
end
|
81
|
-
end
|
89
|
+
Kernel.raise e if e.source_fiber.nil? || e.source_fiber == self
|
82
90
|
|
83
|
-
|
84
|
-
|
91
|
+
restart_fiber(e.source_fiber, opts)
|
92
|
+
end
|
85
93
|
|
86
|
-
|
87
|
-
|
94
|
+
def restart_fiber(fiber, opts)
|
95
|
+
opts[:watcher]&.send [:restart, fiber]
|
96
|
+
case opts[:restart]
|
97
|
+
when true
|
88
98
|
fiber.restart
|
89
|
-
when :
|
99
|
+
when :one_for_all
|
90
100
|
@children.keys.each(&:restart)
|
91
101
|
end
|
92
102
|
end
|
@@ -133,9 +143,9 @@ module FiberControlClassMethods
|
|
133
143
|
def await_select_cleanup(state)
|
134
144
|
return if state[:pending].empty?
|
135
145
|
|
136
|
-
|
146
|
+
terminate = Exceptions::Terminate.new
|
137
147
|
state[:cleanup] = true
|
138
|
-
state[:pending].each_key { |f| f.schedule(
|
148
|
+
state[:pending].each_key { |f| f.schedule(terminate) }
|
139
149
|
suspend
|
140
150
|
end
|
141
151
|
|
@@ -170,7 +180,6 @@ end
|
|
170
180
|
module FiberMessaging
|
171
181
|
def <<(value)
|
172
182
|
@mailbox << value
|
173
|
-
snooze
|
174
183
|
end
|
175
184
|
alias_method :send, :<<
|
176
185
|
|
@@ -196,9 +205,9 @@ module ChildFiberControl
|
|
196
205
|
f
|
197
206
|
end
|
198
207
|
|
199
|
-
def child_done(child_fiber)
|
208
|
+
def child_done(child_fiber, result)
|
200
209
|
@children.delete(child_fiber)
|
201
|
-
@on_child_done&.(child_fiber)
|
210
|
+
@on_child_done&.(child_fiber, result)
|
202
211
|
end
|
203
212
|
|
204
213
|
def terminate_all_children
|
@@ -220,16 +229,8 @@ module ChildFiberControl
|
|
220
229
|
end
|
221
230
|
end
|
222
231
|
|
223
|
-
# Fiber
|
224
|
-
|
225
|
-
prepend FiberControl
|
226
|
-
include FiberMessaging
|
227
|
-
include ChildFiberControl
|
228
|
-
|
229
|
-
extend FiberControlClassMethods
|
230
|
-
|
231
|
-
attr_accessor :tag, :thread, :parent
|
232
|
-
|
232
|
+
# Fiber life cycle methods
|
233
|
+
module FiberLifeCycle
|
233
234
|
def prepare(tag, block, caller, parent)
|
234
235
|
@thread = Thread.current
|
235
236
|
@tag = tag
|
@@ -241,15 +242,6 @@ class ::Fiber
|
|
241
242
|
schedule
|
242
243
|
end
|
243
244
|
|
244
|
-
def setup_main_fiber
|
245
|
-
@main = true
|
246
|
-
@tag = :main
|
247
|
-
@thread = Thread.current
|
248
|
-
@running = true
|
249
|
-
@children&.clear
|
250
|
-
@mailbox = Gyro::Queue.new
|
251
|
-
end
|
252
|
-
|
253
245
|
def run(first_value)
|
254
246
|
setup first_value
|
255
247
|
result = @block.(first_value)
|
@@ -263,17 +255,37 @@ class ::Fiber
|
|
263
255
|
finalize e, true
|
264
256
|
end
|
265
257
|
|
266
|
-
def restart_self(first_value)
|
267
|
-
@mailbox = Gyro::Queue.new
|
268
|
-
run(first_value)
|
269
|
-
end
|
270
|
-
|
271
258
|
def setup(first_value)
|
272
259
|
Kernel.raise first_value if first_value.is_a?(Exception)
|
273
260
|
|
274
261
|
@running = true
|
275
262
|
end
|
276
263
|
|
264
|
+
# Performs setup for a "raw" Fiber created using Fiber.new. Note that this
|
265
|
+
# fiber is an orphan fiber (has no parent), since we cannot control how the
|
266
|
+
# fiber terminates after it has already been created. Calling #setup_raw
|
267
|
+
# allows the fiber to be scheduled and to receive messages.
|
268
|
+
def setup_raw
|
269
|
+
@thread = Thread.current
|
270
|
+
@mailbox = Gyro::Queue.new
|
271
|
+
end
|
272
|
+
|
273
|
+
def setup_main_fiber
|
274
|
+
@main = true
|
275
|
+
@tag = :main
|
276
|
+
@thread = Thread.current
|
277
|
+
@running = true
|
278
|
+
@children&.clear
|
279
|
+
@mailbox = Gyro::Queue.new
|
280
|
+
end
|
281
|
+
|
282
|
+
def restart_self(first_value)
|
283
|
+
@mailbox = Gyro::Queue.new
|
284
|
+
@when_done_procs = nil
|
285
|
+
@waiting_fibers = nil
|
286
|
+
run(first_value)
|
287
|
+
end
|
288
|
+
|
277
289
|
def finalize(result, uncaught_exception = false)
|
278
290
|
result, uncaught_exception = finalize_children(result, uncaught_exception)
|
279
291
|
__fiber_trace__(:fiber_terminate, self, result)
|
@@ -298,29 +310,40 @@ class ::Fiber
|
|
298
310
|
end
|
299
311
|
|
300
312
|
def inform_dependants(result, uncaught_exception)
|
301
|
-
@parent
|
313
|
+
@parent&.child_done(self, result)
|
302
314
|
@when_done_procs&.each { |p| p.(result) }
|
303
|
-
has_waiting_fibers = nil
|
304
315
|
@waiting_fibers&.each_key do |f|
|
305
|
-
has_waiting_fibers = true
|
306
316
|
f.schedule(result)
|
307
317
|
end
|
308
|
-
return unless uncaught_exception &&
|
318
|
+
return unless uncaught_exception && !@waiting_fibers
|
309
319
|
|
310
320
|
# propagate unaught exception to parent
|
311
|
-
@parent
|
321
|
+
@parent&.schedule(result)
|
312
322
|
end
|
313
323
|
|
324
|
+
def when_done(&block)
|
325
|
+
@when_done_procs ||= []
|
326
|
+
@when_done_procs << block
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
# Fiber extensions
|
331
|
+
class ::Fiber
|
332
|
+
prepend FiberControl
|
333
|
+
include FiberSupervision
|
334
|
+
include FiberMessaging
|
335
|
+
include ChildFiberControl
|
336
|
+
include FiberLifeCycle
|
337
|
+
|
338
|
+
extend FiberControlClassMethods
|
339
|
+
|
340
|
+
attr_accessor :tag, :thread, :parent
|
314
341
|
attr_reader :result
|
315
342
|
|
316
343
|
def running?
|
317
344
|
@running
|
318
345
|
end
|
319
346
|
|
320
|
-
def when_done(&block)
|
321
|
-
(@when_done_procs ||= []) << block
|
322
|
-
end
|
323
|
-
|
324
347
|
def inspect
|
325
348
|
"#<Fiber:#{object_id} #{location} (#{state})>"
|
326
349
|
end
|
@@ -346,7 +369,14 @@ end
|
|
346
369
|
|
347
370
|
Fiber.current.setup_main_fiber
|
348
371
|
|
372
|
+
# This at_exit handler is needed only when the original process exits. Due to
|
373
|
+
# the behaviour of fibers on fork (and especially on exit from forked
|
374
|
+
# processes,) we use a separate mechanism to terminate fibers in forked
|
375
|
+
# processes (see Polyphony.fork).
|
376
|
+
orig_pid = Process.pid
|
349
377
|
at_exit do
|
378
|
+
next unless orig_pid == Process.pid
|
379
|
+
|
350
380
|
Fiber.current.terminate_all_children
|
351
381
|
Fiber.current.await_all_children
|
352
382
|
end
|