polyphony 0.33 → 0.34

Sign up to get free protection for your applications and to get access to all the features.
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