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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/Gemfile.lock +1 -1
  4. data/TODO.md +93 -68
  5. data/bin/polyphony-debug +87 -0
  6. data/docs/_includes/nav.html +5 -1
  7. data/docs/_sass/overrides.scss +4 -1
  8. data/docs/api-reference.md +11 -0
  9. data/docs/api-reference/exception.md +27 -0
  10. data/docs/api-reference/fiber.md +407 -0
  11. data/docs/api-reference/io.md +36 -0
  12. data/docs/api-reference/object.md +99 -0
  13. data/docs/api-reference/polyphony-baseexception.md +33 -0
  14. data/docs/api-reference/polyphony-cancel.md +26 -0
  15. data/docs/api-reference/polyphony-moveon.md +24 -0
  16. data/docs/api-reference/polyphony-net.md +20 -0
  17. data/docs/api-reference/polyphony-process.md +28 -0
  18. data/docs/api-reference/polyphony-resourcepool.md +59 -0
  19. data/docs/api-reference/polyphony-restart.md +18 -0
  20. data/docs/api-reference/polyphony-terminate.md +18 -0
  21. data/docs/api-reference/polyphony-threadpool.md +67 -0
  22. data/docs/api-reference/polyphony-throttler.md +77 -0
  23. data/docs/api-reference/polyphony.md +36 -0
  24. data/docs/api-reference/thread.md +88 -0
  25. data/docs/getting-started/tutorial.md +59 -156
  26. data/docs/index.md +2 -0
  27. data/examples/core/forever_sleep.rb +19 -0
  28. data/examples/core/xx-caller.rb +12 -0
  29. data/examples/core/xx-exception-backtrace.rb +40 -0
  30. data/examples/core/xx-fork-spin.rb +42 -0
  31. data/examples/core/xx-spin-fork.rb +49 -0
  32. data/examples/core/xx-supervise-process.rb +30 -0
  33. data/ext/gyro/gyro.h +1 -0
  34. data/ext/gyro/selector.c +8 -0
  35. data/ext/gyro/thread.c +8 -2
  36. data/lib/polyphony.rb +64 -17
  37. data/lib/polyphony/adapters/process.rb +29 -0
  38. data/lib/polyphony/adapters/trace.rb +6 -4
  39. data/lib/polyphony/core/exceptions.rb +5 -0
  40. data/lib/polyphony/core/global_api.rb +15 -0
  41. data/lib/polyphony/extensions/fiber.rb +89 -59
  42. data/lib/polyphony/version.rb +1 -1
  43. data/test/test_fiber.rb +23 -75
  44. data/test/test_global_api.rb +39 -0
  45. data/test/test_kernel.rb +5 -7
  46. data/test/test_process_supervision.rb +46 -0
  47. data/test/test_signal.rb +2 -3
  48. data/test/test_supervise.rb +103 -0
  49. 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
@@ -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);
@@ -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);
@@ -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
- ev_loop_fork(EV_DEFAULT);
222
- Thread_setup_fiber_scheduling(self);
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);
@@ -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
- pid = Kernel.fork do
55
- Gyro.post_fork
56
- Fiber.current.setup_main_fiber
57
- block.()
58
- ensure
59
- Fiber.current.terminate_all_children
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
- # install signal handlers
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 install_terminating_signal_handler(signal, exception_class)
70
- trap(signal) do
71
- exception = exception_class.new
72
- Thread.current.break_out_of_ev_loop(Thread.current.main_fiber, exception)
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
- install_terminating_signal_handler('SIGTERM', SystemExit)
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
- { stamp: stamp, self: trp.self, binding: trp.binding, event: trp.event,
18
- fiber: tp_fiber(trp), lineno: trp.lineno, method_id: trp.method_id,
19
- file: trp.path, parameters: tp_params(trp),
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
- schedule Exceptions::Restart.new(value)
35
- self
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
- def supervise(on_error: nil, &block)
66
- @on_child_done = proc { schedule }
67
- loop { supervise_perform(on_error, &block) }
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(policy, &block)
71
- suspend
72
- rescue Polyphony::Restart
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
- case e.source_fiber
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
- def handle_supervisor_exception(error, fiber, policy, &block)
84
- return block.call(fiber, error) if block
91
+ restart_fiber(e.source_fiber, opts)
92
+ end
85
93
 
86
- case policy
87
- when :restart
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 :restart_all
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
- move_on = Exceptions::MoveOn.new
146
+ terminate = Exceptions::Terminate.new
137
147
  state[:cleanup] = true
138
- state[:pending].each_key { |f| f.schedule(move_on) }
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 extensions
224
- class ::Fiber
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.child_done(self)
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 && !has_waiting_fibers
318
+ return unless uncaught_exception && !@waiting_fibers
309
319
 
310
320
  # propagate unaught exception to parent
311
- @parent.schedule(result)
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