polyphony 0.33 → 0.34
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/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
|