polyphony 0.45.2 → 0.47.0

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