polyphony 0.45.2 → 0.47.0

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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +2 -0
  3. data/.gitmodules +0 -0
  4. data/CHANGELOG.md +39 -0
  5. data/Gemfile.lock +3 -3
  6. data/README.md +3 -3
  7. data/Rakefile +1 -1
  8. data/TODO.md +20 -28
  9. data/bin/test +4 -0
  10. data/examples/core/enumerable.rb +64 -0
  11. data/examples/io/raw.rb +14 -0
  12. data/examples/io/reline.rb +18 -0
  13. data/examples/performance/fiber_resume.rb +43 -0
  14. data/examples/performance/fiber_transfer.rb +13 -4
  15. data/examples/performance/multi_snooze.rb +0 -1
  16. data/examples/performance/thread-vs-fiber/compare.rb +59 -0
  17. data/examples/performance/thread-vs-fiber/em_server.rb +33 -0
  18. data/examples/performance/thread-vs-fiber/polyphony_server.rb +10 -21
  19. data/examples/performance/thread-vs-fiber/threaded_server.rb +22 -15
  20. data/examples/performance/thread_switch.rb +44 -0
  21. data/ext/liburing/liburing.h +585 -0
  22. data/ext/liburing/liburing/README.md +4 -0
  23. data/ext/liburing/liburing/barrier.h +73 -0
  24. data/ext/liburing/liburing/compat.h +15 -0
  25. data/ext/liburing/liburing/io_uring.h +343 -0
  26. data/ext/liburing/queue.c +333 -0
  27. data/ext/liburing/register.c +187 -0
  28. data/ext/liburing/setup.c +210 -0
  29. data/ext/liburing/syscall.c +54 -0
  30. data/ext/liburing/syscall.h +18 -0
  31. data/ext/polyphony/backend.h +1 -15
  32. data/ext/polyphony/backend_common.h +129 -0
  33. data/ext/polyphony/backend_io_uring.c +995 -0
  34. data/ext/polyphony/backend_io_uring_context.c +74 -0
  35. data/ext/polyphony/backend_io_uring_context.h +53 -0
  36. data/ext/polyphony/{libev_backend.c → backend_libev.c} +308 -297
  37. data/ext/polyphony/event.c +1 -1
  38. data/ext/polyphony/extconf.rb +31 -13
  39. data/ext/polyphony/fiber.c +60 -32
  40. data/ext/polyphony/libev.c +4 -0
  41. data/ext/polyphony/libev.h +8 -2
  42. data/ext/polyphony/liburing.c +8 -0
  43. data/ext/polyphony/playground.c +51 -0
  44. data/ext/polyphony/polyphony.c +9 -6
  45. data/ext/polyphony/polyphony.h +35 -19
  46. data/ext/polyphony/polyphony_ext.c +12 -4
  47. data/ext/polyphony/queue.c +100 -35
  48. data/ext/polyphony/runqueue.c +102 -0
  49. data/ext/polyphony/runqueue_ring_buffer.c +85 -0
  50. data/ext/polyphony/runqueue_ring_buffer.h +31 -0
  51. data/ext/polyphony/thread.c +42 -90
  52. data/lib/polyphony.rb +2 -2
  53. data/lib/polyphony/adapters/process.rb +0 -3
  54. data/lib/polyphony/adapters/trace.rb +2 -2
  55. data/lib/polyphony/core/exceptions.rb +0 -4
  56. data/lib/polyphony/core/global_api.rb +47 -23
  57. data/lib/polyphony/core/sync.rb +7 -5
  58. data/lib/polyphony/extensions/core.rb +14 -33
  59. data/lib/polyphony/extensions/debug.rb +13 -0
  60. data/lib/polyphony/extensions/fiber.rb +21 -3
  61. data/lib/polyphony/extensions/io.rb +15 -4
  62. data/lib/polyphony/extensions/openssl.rb +6 -0
  63. data/lib/polyphony/extensions/socket.rb +63 -10
  64. data/lib/polyphony/version.rb +1 -1
  65. data/polyphony.gemspec +1 -1
  66. data/test/helper.rb +36 -4
  67. data/test/io_uring_test.rb +55 -0
  68. data/test/stress.rb +4 -1
  69. data/test/test_backend.rb +63 -6
  70. data/test/test_ext.rb +1 -2
  71. data/test/test_fiber.rb +55 -20
  72. data/test/test_global_api.rb +132 -31
  73. data/test/test_io.rb +42 -0
  74. data/test/test_queue.rb +117 -0
  75. data/test/test_signal.rb +11 -8
  76. data/test/test_socket.rb +2 -2
  77. data/test/test_sync.rb +21 -0
  78. data/test/test_throttler.rb +3 -6
  79. data/test/test_trace.rb +7 -5
  80. metadata +36 -6
@@ -0,0 +1,31 @@
1
+ #ifndef RUNQUEUE_RING_BUFFER_H
2
+ #define RUNQUEUE_RING_BUFFER_H
3
+
4
+ #include "ruby.h"
5
+
6
+ typedef struct runqueue_entry {
7
+ VALUE fiber;
8
+ VALUE value;
9
+ } runqueue_entry;
10
+
11
+ typedef struct runqueue_ring_buffer {
12
+ runqueue_entry *entries;
13
+ unsigned int size;
14
+ unsigned int count;
15
+ unsigned int head;
16
+ unsigned int tail;
17
+ } runqueue_ring_buffer;
18
+
19
+ void runqueue_ring_buffer_init(runqueue_ring_buffer *buffer);
20
+ void runqueue_ring_buffer_free(runqueue_ring_buffer *buffer);
21
+ void runqueue_ring_buffer_mark(runqueue_ring_buffer *buffer);
22
+ int runqueue_ring_buffer_empty_p(runqueue_ring_buffer *buffer);
23
+ void runqueue_ring_buffer_clear(runqueue_ring_buffer *buffer);
24
+
25
+ runqueue_entry runqueue_ring_buffer_shift(runqueue_ring_buffer *buffer);
26
+ void runqueue_ring_buffer_unshift(runqueue_ring_buffer *buffer, VALUE fiber, VALUE value);
27
+ void runqueue_ring_buffer_push(runqueue_ring_buffer *buffer, VALUE fiber, VALUE value);
28
+
29
+ void runqueue_ring_buffer_delete(runqueue_ring_buffer *buffer, VALUE fiber);
30
+
31
+ #endif /* RUNQUEUE_RING_BUFFER_H */
@@ -4,17 +4,15 @@ ID ID_deactivate_all_watchers_post_fork;
4
4
  ID ID_ivar_backend;
5
5
  ID ID_ivar_join_wait_queue;
6
6
  ID ID_ivar_main_fiber;
7
- ID ID_ivar_result;
8
7
  ID ID_ivar_terminated;
9
- ID ID_run_queue;
10
- ID ID_runnable_next;
8
+ ID ID_ivar_runqueue;
11
9
  ID ID_stop;
12
10
 
13
11
  static VALUE Thread_setup_fiber_scheduling(VALUE self) {
14
- VALUE queue = rb_funcall(cQueue, ID_new, 0);
12
+ VALUE runqueue = rb_funcall(cRunqueue, ID_new, 0);
15
13
 
16
14
  rb_ivar_set(self, ID_ivar_main_fiber, rb_fiber_current());
17
- rb_ivar_set(self, ID_run_queue, queue);
15
+ rb_ivar_set(self, ID_ivar_runqueue, runqueue);
18
16
 
19
17
  return self;
20
18
  }
@@ -35,10 +33,10 @@ static VALUE SYM_pending_watchers;
35
33
  static VALUE Thread_fiber_scheduling_stats(VALUE self) {
36
34
  VALUE backend = rb_ivar_get(self,ID_ivar_backend);
37
35
  VALUE stats = rb_hash_new();
38
- VALUE queue = rb_ivar_get(self, ID_run_queue);
36
+ VALUE runqueue = rb_ivar_get(self, ID_ivar_runqueue);
39
37
  long pending_count;
40
38
 
41
- long scheduled_count = RARRAY_LEN(queue);
39
+ long scheduled_count = Runqueue_len(runqueue);
42
40
  rb_hash_aset(stats, SYM_scheduled_fibers, INT2NUM(scheduled_count));
43
41
 
44
42
  pending_count = __BACKEND__.pending_count(backend);
@@ -47,30 +45,18 @@ static VALUE Thread_fiber_scheduling_stats(VALUE self) {
47
45
  return stats;
48
46
  }
49
47
 
50
- VALUE Thread_schedule_fiber(VALUE self, VALUE fiber, VALUE value) {
51
- VALUE queue;
52
-
53
- if (rb_fiber_alive_p(fiber) != Qtrue) return self;
54
-
55
- int already_runnable = rb_ivar_get(fiber, ID_runnable) != Qnil;
48
+ void schedule_fiber(VALUE self, VALUE fiber, VALUE value, int prioritize) {
49
+ VALUE runqueue;
50
+ int already_runnable;
56
51
 
57
- if (already_runnable) {
58
- VALUE current_runnable_value = rb_ivar_get(fiber, ID_runnable_value);
59
-
60
- // If the fiber is already runnable and the runnable value is an exception,
61
- // we don't update the value, in order to prevent a race condition where
62
- // exceptions will be lost (see issue #33)
63
- if (TEST_EXCEPTION(current_runnable_value)) return self;
64
- }
52
+ if (rb_fiber_alive_p(fiber) != Qtrue) return;
53
+ already_runnable = rb_ivar_get(fiber, ID_ivar_runnable) != Qnil;
65
54
 
66
- rb_ivar_set(fiber, ID_runnable_value, value);
67
55
  COND_TRACE(3, SYM_fiber_schedule, fiber, value);
68
-
56
+ runqueue = rb_ivar_get(self, ID_ivar_runqueue);
57
+ (prioritize ? Runqueue_unshift : Runqueue_push)(runqueue, fiber, value, already_runnable);
69
58
  if (!already_runnable) {
70
- queue = rb_ivar_get(self, ID_run_queue);
71
- Queue_push(queue, fiber);
72
- rb_ivar_set(fiber, ID_runnable, Qtrue);
73
-
59
+ rb_ivar_set(fiber, ID_ivar_runnable, Qtrue);
74
60
  if (rb_thread_current() != self) {
75
61
  // If the fiber scheduling is done across threads, we need to make sure the
76
62
  // target thread is woken up in case it is in the middle of running its
@@ -81,46 +67,22 @@ VALUE Thread_schedule_fiber(VALUE self, VALUE fiber, VALUE value) {
81
67
  __BACKEND__.wakeup(backend);
82
68
  }
83
69
  }
70
+ }
71
+
72
+ VALUE Thread_schedule_fiber(VALUE self, VALUE fiber, VALUE value) {
73
+ schedule_fiber(self, fiber, value, 0);
84
74
  return self;
85
75
  }
86
76
 
87
77
  VALUE Thread_schedule_fiber_with_priority(VALUE self, VALUE fiber, VALUE value) {
88
- VALUE queue;
89
-
90
- if (rb_fiber_alive_p(fiber) != Qtrue) return self;
91
-
92
- COND_TRACE(3, SYM_fiber_schedule, fiber, value);
93
- rb_ivar_set(fiber, ID_runnable_value, value);
94
-
95
- queue = rb_ivar_get(self, ID_run_queue);
96
-
97
- // if fiber is already scheduled, remove it from the run queue
98
- if (rb_ivar_get(fiber, ID_runnable) != Qnil) {
99
- Queue_delete(queue, fiber);
100
- } else {
101
- rb_ivar_set(fiber, ID_runnable, Qtrue);
102
- }
103
-
104
- // the fiber is given priority by putting it at the front of the run queue
105
- Queue_unshift(queue, fiber);
106
-
107
- if (rb_thread_current() != self) {
108
- // if the fiber scheduling is done across threads, we need to make sure the
109
- // target thread is woken up in case it is in the middle of running its
110
- // event loop. Otherwise it's gonna be stuck waiting for an event to
111
- // happen, not knowing that it there's already a fiber ready to run in its
112
- // run queue.
113
- VALUE backend = rb_ivar_get(self, ID_ivar_backend);
114
- __BACKEND__.wakeup(backend);
115
- }
78
+ schedule_fiber(self, fiber, value, 1);
116
79
  return self;
117
80
  }
118
81
 
119
82
  VALUE Thread_switch_fiber(VALUE self) {
120
83
  VALUE current_fiber = rb_fiber_current();
121
- VALUE queue = rb_ivar_get(self, ID_run_queue);
122
- VALUE next_fiber;
123
- VALUE value;
84
+ VALUE runqueue = rb_ivar_get(self, ID_ivar_runqueue);
85
+ runqueue_entry next;
124
86
  VALUE backend = rb_ivar_get(self, ID_ivar_backend);
125
87
  int ref_count;
126
88
  int backend_was_polled = 0;
@@ -130,47 +92,40 @@ VALUE Thread_switch_fiber(VALUE self) {
130
92
 
131
93
  ref_count = __BACKEND__.ref_count(backend);
132
94
  while (1) {
133
- next_fiber = Queue_shift_no_wait(queue);
134
- if (next_fiber != Qnil) {
95
+ next = Runqueue_shift(runqueue);
96
+ if (next.fiber != Qnil) {
135
97
  if (backend_was_polled == 0 && ref_count > 0) {
136
98
  // this prevents event starvation in case the run queue never empties
137
- __BACKEND__.poll(backend, Qtrue, current_fiber, queue);
99
+ __BACKEND__.poll(backend, Qtrue, current_fiber, runqueue);
138
100
  }
139
101
  break;
140
102
  }
141
103
  if (ref_count == 0) break;
142
104
 
143
- __BACKEND__.poll(backend, Qnil, current_fiber, queue);
105
+ __BACKEND__.poll(backend, Qnil, current_fiber, runqueue);
144
106
  backend_was_polled = 1;
145
107
  }
146
108
 
147
- if (next_fiber == Qnil) return Qnil;
109
+ if (next.fiber == Qnil) return Qnil;
148
110
 
149
111
  // run next fiber
150
- value = rb_ivar_get(next_fiber, ID_runnable_value);
151
- COND_TRACE(3, SYM_fiber_run, next_fiber, value);
152
-
153
- rb_ivar_set(next_fiber, ID_runnable, Qnil);
154
- RB_GC_GUARD(next_fiber);
155
- RB_GC_GUARD(value);
156
- return (next_fiber == current_fiber) ?
157
- value : rb_funcall(next_fiber, ID_transfer, 1, value);
158
- }
112
+ COND_TRACE(3, SYM_fiber_run, next.fiber, next.value);
159
113
 
160
- VALUE Thread_run_queue_trace(VALUE self) {
161
- VALUE queue = rb_ivar_get(self, ID_run_queue);
162
- Queue_trace(queue);
163
- return self;
114
+ rb_ivar_set(next.fiber, ID_ivar_runnable, Qnil);
115
+ RB_GC_GUARD(next.fiber);
116
+ RB_GC_GUARD(next.value);
117
+ return (next.fiber == current_fiber) ?
118
+ next.value : FIBER_TRANSFER(next.fiber, next.value);
164
119
  }
165
120
 
166
121
  VALUE Thread_reset_fiber_scheduling(VALUE self) {
167
- VALUE queue = rb_ivar_get(self, ID_run_queue);
168
- Queue_clear(queue);
122
+ VALUE queue = rb_ivar_get(self, ID_ivar_runqueue);
123
+ Runqueue_clear(queue);
169
124
  Thread_fiber_reset_ref_count(self);
170
125
  return self;
171
126
  }
172
127
 
173
- VALUE Thread_fiber_break_out_of_ev_loop(VALUE self, VALUE fiber, VALUE resume_obj) {
128
+ VALUE Thread_fiber_schedule_and_wakeup(VALUE self, VALUE fiber, VALUE resume_obj) {
174
129
  VALUE backend = rb_ivar_get(self, ID_ivar_backend);
175
130
  if (fiber != Qnil) {
176
131
  Thread_schedule_fiber_with_priority(self, fiber, resume_obj);
@@ -193,25 +148,22 @@ void Init_Thread() {
193
148
  rb_define_method(rb_cThread, "setup_fiber_scheduling", Thread_setup_fiber_scheduling, 0);
194
149
  rb_define_method(rb_cThread, "reset_fiber_scheduling", Thread_reset_fiber_scheduling, 0);
195
150
  rb_define_method(rb_cThread, "fiber_scheduling_stats", Thread_fiber_scheduling_stats, 0);
196
- rb_define_method(rb_cThread, "break_out_of_ev_loop", Thread_fiber_break_out_of_ev_loop, 2);
151
+ rb_define_method(rb_cThread, "schedule_and_wakeup", Thread_fiber_schedule_and_wakeup, 2);
197
152
 
198
153
  rb_define_method(rb_cThread, "schedule_fiber", Thread_schedule_fiber, 2);
199
154
  rb_define_method(rb_cThread, "schedule_fiber_with_priority",
200
155
  Thread_schedule_fiber_with_priority, 2);
201
156
  rb_define_method(rb_cThread, "switch_fiber", Thread_switch_fiber, 0);
202
- rb_define_method(rb_cThread, "run_queue_trace", Thread_run_queue_trace, 0);
203
157
 
204
158
  rb_define_method(rb_cThread, "debug!", Thread_debug, 0);
205
159
 
206
- ID_deactivate_all_watchers_post_fork = rb_intern("deactivate_all_watchers_post_fork");
207
- ID_ivar_backend = rb_intern("@backend");
208
- ID_ivar_join_wait_queue = rb_intern("@join_wait_queue");
209
- ID_ivar_main_fiber = rb_intern("@main_fiber");
210
- ID_ivar_result = rb_intern("@result");
211
- ID_ivar_terminated = rb_intern("@terminated");
212
- ID_run_queue = rb_intern("run_queue");
213
- ID_runnable_next = rb_intern("runnable_next");
214
- ID_stop = rb_intern("stop");
160
+ ID_deactivate_all_watchers_post_fork = rb_intern("deactivate_all_watchers_post_fork");
161
+ ID_ivar_backend = rb_intern("@backend");
162
+ ID_ivar_join_wait_queue = rb_intern("@join_wait_queue");
163
+ ID_ivar_main_fiber = rb_intern("@main_fiber");
164
+ ID_ivar_terminated = rb_intern("@terminated");
165
+ ID_ivar_runqueue = rb_intern("@runqueue");
166
+ ID_stop = rb_intern("stop");
215
167
 
216
168
  SYM_scheduled_fibers = ID2SYM(rb_intern("scheduled_fibers"));
217
169
  SYM_pending_watchers = ID2SYM(rb_intern("pending_watchers"));
@@ -76,10 +76,10 @@ module Polyphony
76
76
  end
77
77
 
78
78
  def install_terminating_signal_handlers
79
- trap('SIGTERM', SystemExit)
79
+ trap('SIGTERM') { raise SystemExit }
80
80
  orig_trap('SIGINT') do
81
81
  orig_trap('SIGINT') { exit! }
82
- Thread.current.break_out_of_ev_loop(Thread.main.main_fiber, Interrupt.new)
82
+ Fiber.schedule_priority_oob_fiber { raise Interrupt }
83
83
  end
84
84
  end
85
85
 
@@ -24,9 +24,6 @@ module Polyphony
24
24
  def kill_and_await(sig, pid)
25
25
  ::Process.kill(sig, pid)
26
26
  Thread.current.backend.waitpid(pid)
27
- rescue SystemCallError
28
- # ignore
29
- puts 'SystemCallError in kill_and_await'
30
27
  end
31
28
  end
32
29
  end
@@ -118,13 +118,13 @@ module Polyphony
118
118
 
119
119
  ALL_FIBER_EVENTS = %i[
120
120
  fiber_create fiber_terminate fiber_schedule fiber_switchpoint fiber_run
121
- fiber_ev_loop_enter fiber_ev_loop_leave
121
+ fiber_event_poll_enter fiber_event_poll_leave
122
122
  ].freeze
123
123
 
124
124
  def event_masks(events)
125
125
  events.each_with_object([[], []]) do |e, masks|
126
126
  case e
127
- when /fiber_/
127
+ when /^fiber_/
128
128
  masks[1] += e == :fiber_all ? ALL_FIBER_EVENTS : [e]
129
129
  masks[0] << :c_return unless masks[0].include?(:c_return)
130
130
  else
@@ -14,10 +14,6 @@ module Polyphony
14
14
  @caller_backtrace = caller
15
15
  @value = value
16
16
  end
17
-
18
- def backtrace
19
- sanitize(@caller_backtrace)
20
- end
21
17
  end
22
18
 
23
19
  # MoveOn is used to interrupt a long-running blocking operation, while
@@ -16,27 +16,39 @@ module Polyphony
16
16
  end
17
17
 
18
18
  def cancel_after(interval, with_exception: Polyphony::Cancel, &block)
19
- fiber = ::Fiber.current
20
- canceller = spin do
19
+ if !block
20
+ cancel_after_blockless_canceller(Fiber.current, interval, with_exception)
21
+ elsif block.arity > 0
22
+ cancel_after_with_block(Fiber.current, interval, with_exception, &block)
23
+ else
24
+ Thread.current.backend.timeout(interval, with_exception, &block)
25
+ end
26
+ end
27
+
28
+ def cancel_after_blockless_canceller(fiber, interval, with_exception)
29
+ spin do
21
30
  sleep interval
22
31
  exception = cancel_exception(with_exception)
32
+ exception.__raising_fiber__ = nil
23
33
  fiber.schedule exception
24
34
  end
25
- block ? cancel_after_wrap_block(canceller, &block) : canceller
26
35
  end
27
36
 
28
- def cancel_exception(exception)
29
- return exception.new if exception.is_a?(Class)
30
-
31
- RuntimeError.new(exception)
32
- end
33
-
34
- def cancel_after_wrap_block(canceller, &block)
35
- block.call
37
+ def cancel_after_with_block(fiber, interval, with_exception, &block)
38
+ canceller = cancel_after_blockless_canceller(fiber, interval, with_exception)
39
+ block.call(canceller)
36
40
  ensure
37
41
  canceller.stop
38
42
  end
39
43
 
44
+ def cancel_exception(exception)
45
+ case exception
46
+ when Class then exception.new
47
+ when Array then exception[0].new(exception[1])
48
+ else RuntimeError.new(exception)
49
+ end
50
+ end
51
+
40
52
  def spin(tag = nil, &block)
41
53
  Fiber.current.spin(tag, caller, &block)
42
54
  end
@@ -51,6 +63,16 @@ module Polyphony
51
63
  end
52
64
  end
53
65
 
66
+ def spin_scope
67
+ raise unless block_given?
68
+
69
+ spin do
70
+ result = yield
71
+ Fiber.current.await_all_children
72
+ result
73
+ end.await
74
+ end
75
+
54
76
  def every(interval)
55
77
  next_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + interval
56
78
  loop do
@@ -65,15 +87,20 @@ module Polyphony
65
87
  end
66
88
 
67
89
  def move_on_after(interval, with_value: nil, &block)
68
- fiber = ::Fiber.current
69
- unless block
70
- return spin do
71
- sleep interval
72
- fiber.schedule with_value
73
- end
90
+ if !block
91
+ move_on_blockless_canceller(Fiber.current, interval, with_value)
92
+ elsif block.arity > 0
93
+ move_on_after_with_block(Fiber.current, interval, with_value, &block)
94
+ else
95
+ Thread.current.backend.timeout(interval, nil, with_value, &block)
74
96
  end
97
+ end
75
98
 
76
- move_on_after_with_block(fiber, interval, with_value, &block)
99
+ def move_on_blockless_canceller(fiber, interval, with_value)
100
+ spin do
101
+ sleep interval
102
+ fiber.schedule with_value
103
+ end
77
104
  end
78
105
 
79
106
  def move_on_after_with_block(fiber, interval, with_value, &block)
@@ -81,7 +108,7 @@ module Polyphony
81
108
  sleep interval
82
109
  fiber.schedule Polyphony::MoveOn.new(with_value)
83
110
  end
84
- block.call
111
+ block.call(canceller)
85
112
  rescue Polyphony::MoveOn => e
86
113
  e.value
87
114
  ensure
@@ -107,10 +134,7 @@ module Polyphony
107
134
  end
108
135
 
109
136
  def sleep_forever
110
- Thread.current.backend.ref
111
- loop { sleep 60 }
112
- ensure
113
- Thread.current.backend.unref
137
+ Thread.current.backend.wait_event(true)
114
138
  end
115
139
 
116
140
  def throttled_loop(rate = nil, **opts, &block)
@@ -16,11 +16,13 @@ module Polyphony
16
16
 
17
17
  def synchronize_not_holding
18
18
  @token = @store.shift
19
- @holding_fiber = Fiber.current
20
- yield
21
- ensure
22
- @holding_fiber = nil
23
- @store << @token if @token
19
+ begin
20
+ @holding_fiber = Fiber.current
21
+ yield
22
+ ensure
23
+ @holding_fiber = nil
24
+ @store << @token if @token
25
+ end
24
26
  end
25
27
 
26
28
  def conditional_release
@@ -12,7 +12,7 @@ class ::Exception
12
12
  attr_accessor :__disable_sanitized_backtrace__
13
13
  end
14
14
 
15
- attr_accessor :source_fiber
15
+ attr_accessor :source_fiber, :__raising_fiber__
16
16
 
17
17
  alias_method :orig_initialize, :initialize
18
18
  def initialize(*args)
@@ -22,8 +22,8 @@ class ::Exception
22
22
 
23
23
  alias_method :orig_backtrace, :backtrace
24
24
  def backtrace
25
- unless @first_backtrace_call
26
- @first_backtrace_call = true
25
+ unless @backtrace_called
26
+ @backtrace_called = true
27
27
  return orig_backtrace
28
28
  end
29
29
 
@@ -31,12 +31,10 @@ class ::Exception
31
31
  end
32
32
 
33
33
  def sanitized_backtrace
34
- if @__raising_fiber__
35
- backtrace = orig_backtrace || []
36
- sanitize(backtrace + @__raising_fiber__.caller)
37
- else
38
- sanitize(orig_backtrace)
39
- end
34
+ return sanitize(orig_backtrace) unless @__raising_fiber__
35
+
36
+ backtrace = orig_backtrace || []
37
+ sanitize(backtrace + @__raising_fiber__.caller)
40
38
  end
41
39
 
42
40
  POLYPHONY_DIR = File.expand_path(File.join(__dir__, '..'))
@@ -149,39 +147,22 @@ module ::Kernel
149
147
  return orig_trap(sig, command) if command.is_a? String
150
148
 
151
149
  block = command if !block && command.respond_to?(:call)
152
- exception = signal_exception(block, command)
153
150
 
154
151
  # The signal trap can be invoked at any time, including while the system
155
152
  # backend is blocking while polling for events. In order to deal with this
156
- # correctly, we spin a fiber that will run the signal handler code, then
157
- # call break_out_of_ev_loop, which will put the fiber at the front of the
158
- # run queue, then wake up the backend.
159
- #
160
- # If the command argument is an exception class however, it will be raised
161
- # directly in the context of the main fiber.
153
+ # correctly, we run the signal handler code in an out-of-band, priority
154
+ # scheduled fiber, that will pass any uncaught exception (including
155
+ # SystemExit and Interrupt) to the main thread's main fiber. See also
156
+ # `Fiber#schedule_priority_oob_fiber`.
162
157
  orig_trap(sig) do
163
- Thread.current.break_out_of_ev_loop(Thread.main.main_fiber, exception)
158
+ Fiber.schedule_priority_oob_fiber(&block)
164
159
  end
165
160
  end
166
161
  end
167
162
 
168
- def signal_exception(block, command)
169
- if block
170
- Polyphony::Interjection.new(block)
171
- elsif command.is_a?(Class)
172
- command.new
173
- else
174
- raise ArgumentError, 'Must supply block or exception or callable object'
175
- end
176
- end
177
-
178
163
  # Override Timeout to use cancel scope
179
164
  module ::Timeout
180
- def self.timeout(sec, klass = nil, message = nil, &block)
181
- cancel_after(sec, &block)
182
- rescue Polyphony::Cancel => e
183
- error = klass ? klass.new(message) : ::Timeout::Error.new
184
- error.set_backtrace(e.backtrace)
185
- raise error
165
+ def self.timeout(sec, klass = Timeout::Error, message = 'execution expired', &block)
166
+ cancel_after(sec, with_exception: [klass, message], &block)
186
167
  end
187
168
  end