polyphony 0.72 → 0.75

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +15 -11
  3. data/.github/workflows/test_io_uring.yml +32 -0
  4. data/.gitignore +3 -1
  5. data/CHANGELOG.md +24 -0
  6. data/Gemfile.lock +16 -13
  7. data/bin/pdbg +0 -0
  8. data/bin/polyphony-debug +0 -0
  9. data/bin/stress.rb +0 -0
  10. data/bin/test +0 -0
  11. data/docs/api-reference/exception.md +5 -1
  12. data/examples/core/ring.rb +29 -0
  13. data/ext/polyphony/backend_common.c +90 -12
  14. data/ext/polyphony/backend_common.h +9 -1
  15. data/ext/polyphony/backend_io_uring.c +257 -134
  16. data/ext/polyphony/backend_io_uring_context.c +1 -0
  17. data/ext/polyphony/backend_io_uring_context.h +2 -1
  18. data/ext/polyphony/backend_libev.c +33 -29
  19. data/ext/polyphony/event.c +5 -2
  20. data/ext/polyphony/extconf.rb +1 -0
  21. data/ext/polyphony/polyphony.c +11 -1
  22. data/ext/polyphony/polyphony.h +9 -2
  23. data/ext/polyphony/queue.c +10 -5
  24. data/ext/polyphony/runqueue_ring_buffer.c +3 -1
  25. data/ext/polyphony/socket_extensions.c +5 -2
  26. data/ext/polyphony/thread.c +1 -1
  27. data/lib/polyphony/{extensions → core}/debug.rb +0 -0
  28. data/lib/polyphony/core/global_api.rb +0 -3
  29. data/lib/polyphony/extensions/exception.rb +45 -0
  30. data/lib/polyphony/extensions/fiber.rb +85 -4
  31. data/lib/polyphony/extensions/{core.rb → kernel.rb} +0 -73
  32. data/lib/polyphony/extensions/openssl.rb +5 -1
  33. data/lib/polyphony/extensions/process.rb +19 -0
  34. data/lib/polyphony/extensions/socket.rb +12 -6
  35. data/lib/polyphony/extensions/thread.rb +9 -3
  36. data/lib/polyphony/extensions/timeout.rb +10 -0
  37. data/lib/polyphony/extensions.rb +9 -0
  38. data/lib/polyphony/version.rb +1 -1
  39. data/lib/polyphony.rb +4 -4
  40. data/test/helper.rb +0 -5
  41. data/test/test_backend.rb +3 -5
  42. data/test/test_global_api.rb +21 -12
  43. data/test/test_io.rb +2 -2
  44. data/test/test_kernel.rb +2 -2
  45. data/test/test_process_supervision.rb +1 -1
  46. data/test/test_signal.rb +20 -1
  47. data/test/test_socket.rb +35 -2
  48. data/test/test_thread.rb +1 -1
  49. data/test/test_thread_pool.rb +1 -1
  50. data/test/test_throttler.rb +3 -3
  51. data/test/test_timer.rb +1 -1
  52. data/test/test_trace.rb +7 -1
  53. metadata +11 -5
@@ -17,6 +17,7 @@ const char *op_type_to_str(enum op_type type) {
17
17
  case OP_ACCEPT: return "ACCEPT";
18
18
  case OP_CONNECT: return "CONNECT";
19
19
  case OP_CHAIN: return "CHAIN";
20
+ case OP_CLOSE: return "CLOSE";
20
21
  default: return "";
21
22
  };
22
23
  }
@@ -15,7 +15,8 @@ enum op_type {
15
15
  OP_POLL,
16
16
  OP_ACCEPT,
17
17
  OP_CONNECT,
18
- OP_CHAIN
18
+ OP_CHAIN,
19
+ OP_CLOSE
19
20
  };
20
21
 
21
22
  typedef struct op_context {
@@ -287,7 +287,6 @@ VALUE Backend_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof,
287
287
  io_verify_blocking_mode(fptr, io, Qfalse);
288
288
  rectify_io_file_pos(fptr);
289
289
  watcher.fiber = Qnil;
290
- OBJ_TAINT(str);
291
290
 
292
291
  while (1) {
293
292
  backend->base.op_count++;
@@ -698,10 +697,13 @@ VALUE Backend_connect(VALUE self, VALUE sock, VALUE host, VALUE port) {
698
697
  Backend_t *backend;
699
698
  struct libev_io watcher;
700
699
  rb_io_t *fptr;
701
- struct sockaddr_in addr;
702
- char *host_buf = StringValueCStr(host);
700
+ struct sockaddr *ai_addr;
701
+ int ai_addrlen;
703
702
  VALUE switchpoint_result = Qnil;
704
703
  VALUE underlying_sock = rb_ivar_get(sock, ID_ivar_io);
704
+
705
+ ai_addrlen = backend_getaddrinfo(host, port, &ai_addr);
706
+
705
707
  if (underlying_sock != Qnil) sock = underlying_sock;
706
708
 
707
709
  GetBackend(self, backend);
@@ -709,12 +711,8 @@ VALUE Backend_connect(VALUE self, VALUE sock, VALUE host, VALUE port) {
709
711
  io_verify_blocking_mode(fptr, sock, Qfalse);
710
712
  watcher.fiber = Qnil;
711
713
 
712
- addr.sin_family = AF_INET;
713
- addr.sin_addr.s_addr = inet_addr(host_buf);
714
- addr.sin_port = htons(NUM2INT(port));
715
-
716
714
  backend->base.op_count++;
717
- int result = connect(fptr->fd, (struct sockaddr *)&addr, sizeof(addr));
715
+ int result = connect(fptr->fd, ai_addr, ai_addrlen);
718
716
  if (result < 0) {
719
717
  int e = errno;
720
718
  if (e != EINPROGRESS) rb_syserr_fail(e, strerror(e));
@@ -1147,33 +1145,39 @@ VALUE Backend_sleep(VALUE self, VALUE duration) {
1147
1145
  noreturn VALUE Backend_timer_loop(VALUE self, VALUE interval) {
1148
1146
  Backend_t *backend;
1149
1147
  struct libev_timer watcher;
1150
- double interval_d = NUM2DBL(interval);
1151
-
1152
- GetBackend(self, backend);
1153
1148
  watcher.fiber = rb_fiber_current();
1149
+ uint64_t interval_ns = NUM2DBL(interval) * 1e9;
1150
+ uint64_t next_time_ns = 0;
1151
+ VALUE resume_value = Qnil;
1154
1152
 
1155
- double next_time = 0.;
1153
+ GetBackend(self, backend);
1156
1154
 
1157
1155
  while (1) {
1158
- double now = current_time();
1159
- if (next_time == 0.) next_time = current_time() + interval_d;
1160
- double sleep_duration = next_time - now;
1161
- if (sleep_duration < 0) sleep_duration = 0;
1162
-
1163
- VALUE switchpoint_result = Qnil;
1164
- ev_timer_init(&watcher.timer, Backend_timer_callback, sleep_duration, 0.);
1165
- ev_timer_start(backend->ev_loop, &watcher.timer);
1166
- backend->base.op_count++;
1167
- switchpoint_result = backend_await((struct Backend_base *)backend);
1168
- ev_timer_stop(backend->ev_loop, &watcher.timer);
1169
- RAISE_IF_EXCEPTION(switchpoint_result);
1170
- RB_GC_GUARD(switchpoint_result);
1156
+ uint64_t now_ns = current_time_ns();
1157
+ if (next_time_ns == 0) next_time_ns = now_ns + interval_ns;
1158
+
1159
+ if (next_time_ns > now_ns) {
1160
+ double sleep_duration = ((double)(next_time_ns - now_ns))/1e9;
1161
+ ev_timer_init(&watcher.timer, Backend_timer_callback, sleep_duration, 0.);
1162
+ ev_timer_start(backend->ev_loop, &watcher.timer);
1163
+ backend->base.op_count++;
1164
+ resume_value = backend_await((struct Backend_base *)backend);
1165
+ ev_timer_stop(backend->ev_loop, &watcher.timer);
1166
+ RAISE_IF_EXCEPTION(resume_value);
1167
+ }
1168
+ else {
1169
+ resume_value = backend_snooze();
1170
+ RAISE_IF_EXCEPTION(resume_value);
1171
+ }
1171
1172
 
1172
1173
  rb_yield(Qnil);
1173
- do {
1174
- next_time += interval_d;
1175
- } while (next_time <= now);
1174
+
1175
+ while (1) {
1176
+ next_time_ns += interval_ns;
1177
+ if (next_time_ns > now_ns) break;
1178
+ }
1176
1179
  }
1180
+ RB_GC_GUARD(resume_value);
1177
1181
  }
1178
1182
 
1179
1183
  struct libev_timeout {
@@ -1364,7 +1368,7 @@ inline VALUE Backend_run_idle_tasks(VALUE self) {
1364
1368
  return self;
1365
1369
  }
1366
1370
 
1367
- inline int splice_chunks_write(Backend_t *backend, int fd, VALUE str, struct libev_rw_io *watcher, VALUE *result) {
1371
+ static inline int splice_chunks_write(Backend_t *backend, int fd, VALUE str, struct libev_rw_io *watcher, VALUE *result) {
1368
1372
  char *buf = RSTRING_PTR(str);
1369
1373
  int len = RSTRING_LEN(str);
1370
1374
  int left = len;
@@ -59,14 +59,17 @@ VALUE Event_signal(int argc, VALUE *argv, VALUE self) {
59
59
 
60
60
  VALUE Event_await(VALUE self) {
61
61
  Event_t *event;
62
+ VALUE switchpoint_result;
63
+ VALUE backend;
64
+
62
65
  GetEvent(self, event);
63
66
 
64
67
  if (event->waiting_fiber != Qnil)
65
68
  rb_raise(rb_eRuntimeError, "Event is already awaited by another fiber");
66
69
 
67
- VALUE backend = rb_ivar_get(rb_thread_current(), ID_ivar_backend);
70
+ backend = rb_ivar_get(rb_thread_current(), ID_ivar_backend);
68
71
  event->waiting_fiber = rb_fiber_current();
69
- VALUE switchpoint_result = Backend_wait_event(backend, Qnil);
72
+ switchpoint_result = Backend_wait_event(backend, Qnil);
70
73
  event->waiting_fiber = Qnil;
71
74
 
72
75
  RAISE_IF_EXCEPTION(switchpoint_result);
@@ -51,6 +51,7 @@ $defs << '-DPOLYPHONY_PLAYGROUND' if ENV['POLYPHONY_PLAYGROUND']
51
51
 
52
52
  CONFIG['optflags'] << ' -fno-strict-aliasing' unless RUBY_PLATFORM =~ /mswin/
53
53
 
54
+ have_func('rb_fiber_transfer', 'ruby.h')
54
55
 
55
56
  dir_config 'polyphony_ext'
56
57
  create_makefile 'polyphony_ext'
@@ -9,16 +9,18 @@ ID ID_clear;
9
9
  ID ID_each;
10
10
  ID ID_inspect;
11
11
  ID ID_invoke;
12
- ID ID_new;
13
12
  ID ID_ivar_blocking_mode;
14
13
  ID ID_ivar_io;
15
14
  ID ID_ivar_parked;
16
15
  ID ID_ivar_runnable;
17
16
  ID ID_ivar_running;
18
17
  ID ID_ivar_thread;
18
+ ID ID_new;
19
+ ID ID_raise;
19
20
  ID ID_size;
20
21
  ID ID_signal;
21
22
  ID ID_switch_fiber;
23
+ ID ID_to_s;
22
24
  ID ID_transfer;
23
25
  ID ID_R;
24
26
  ID ID_W;
@@ -123,6 +125,10 @@ VALUE Polyphony_backend_write(int argc, VALUE *argv, VALUE self) {
123
125
  return Backend_write_m(argc, argv, BACKEND());
124
126
  }
125
127
 
128
+ VALUE Polyphony_backend_close(VALUE self, VALUE io) {
129
+ return Backend_close(BACKEND(), io);
130
+ }
131
+
126
132
  void Init_Polyphony() {
127
133
  mPolyphony = rb_define_module("Polyphony");
128
134
 
@@ -147,6 +153,8 @@ void Init_Polyphony() {
147
153
  rb_define_singleton_method(mPolyphony, "backend_wait_io", Polyphony_backend_wait_io, 2);
148
154
  rb_define_singleton_method(mPolyphony, "backend_waitpid", Polyphony_backend_waitpid, 1);
149
155
  rb_define_singleton_method(mPolyphony, "backend_write", Polyphony_backend_write, -1);
156
+ rb_define_singleton_method(mPolyphony, "backend_close", Polyphony_backend_close, 1);
157
+ rb_define_singleton_method(mPolyphony, "backend_verify_blocking_mode", Backend_verify_blocking_mode, 2);
150
158
 
151
159
  rb_define_global_function("snooze", Polyphony_snooze, 0);
152
160
  rb_define_global_function("suspend", Polyphony_suspend, 0);
@@ -166,8 +174,10 @@ void Init_Polyphony() {
166
174
  ID_ivar_running = rb_intern("@running");
167
175
  ID_ivar_thread = rb_intern("@thread");
168
176
  ID_new = rb_intern("new");
177
+ ID_raise = rb_intern("raise");
169
178
  ID_signal = rb_intern("signal");
170
179
  ID_size = rb_intern("size");
171
180
  ID_switch_fiber = rb_intern("switch_fiber");
181
+ ID_to_s = rb_intern("to_s");
172
182
  ID_transfer = rb_intern("transfer");
173
183
  }
@@ -10,7 +10,8 @@
10
10
  // debugging
11
11
  #define OBJ_ID(obj) (NUM2LONG(rb_funcall(obj, rb_intern("object_id"), 0)))
12
12
  #define INSPECT(str, obj) { printf(str); VALUE s = rb_funcall(obj, rb_intern("inspect"), 0); printf(": %s\n", StringValueCStr(s)); }
13
- #define TRACE_CALLER() { VALUE c = rb_funcall(rb_mKernel, rb_intern("caller"), 0); INSPECT("caller: ", c); }
13
+ #define CALLER() rb_funcall(rb_mKernel, rb_intern("caller"), 0)
14
+ #define TRACE_CALLER() INSPECT("caller: ", CALLER())
14
15
  #define TRACE_C_STACK() { \
15
16
  void *entries[10]; \
16
17
  size_t size = backtrace(entries, 10); \
@@ -26,7 +27,11 @@
26
27
  #define RAISE_IF_NOT_NIL(ret) if (ret != Qnil) { RAISE_EXCEPTION(ret); }
27
28
 
28
29
  // Fiber#transfer
29
- #define FIBER_TRANSFER(fiber, value) rb_funcall(fiber, ID_transfer, 1, value)
30
+ #if HAVE_RB_FIBER_TRANSFER
31
+ #define FIBER_TRANSFER(fiber, value) rb_fiber_transfer(fiber, 1, &value)
32
+ #else
33
+ #define FIBER_TRANSFER(fiber, value) rb_funcall(fiber, ID_transfer, 1, value)
34
+ #endif
30
35
 
31
36
  #define BACKEND() (rb_ivar_get(rb_thread_current(), ID_ivar_backend))
32
37
 
@@ -53,6 +58,7 @@ extern ID ID_raise;
53
58
  extern ID ID_signal;
54
59
  extern ID ID_size;
55
60
  extern ID ID_switch_fiber;
61
+ extern ID ID_to_s;
56
62
  extern ID ID_transfer;
57
63
 
58
64
  extern VALUE SYM_fiber_create;
@@ -108,6 +114,7 @@ VALUE Backend_wait_event(VALUE self, VALUE raise);
108
114
  VALUE Backend_wait_io(VALUE self, VALUE io, VALUE write);
109
115
  VALUE Backend_waitpid(VALUE self, VALUE pid);
110
116
  VALUE Backend_write_m(int argc, VALUE *argv, VALUE self);
117
+ VALUE Backend_close(VALUE self, VALUE io);
111
118
 
112
119
  VALUE Backend_poll(VALUE self, VALUE blocking);
113
120
  VALUE Backend_wait_event(VALUE self, VALUE raise_on_exception);
@@ -121,24 +121,27 @@ VALUE Queue_unshift(VALUE self, VALUE value) {
121
121
 
122
122
  VALUE Queue_shift(VALUE self) {
123
123
  Queue_t *queue;
124
- GetQueue(self, queue);
125
-
126
124
  VALUE fiber = rb_fiber_current();
127
125
  VALUE thread = rb_thread_current();
128
126
  VALUE backend = rb_ivar_get(thread, ID_ivar_backend);
127
+ VALUE value;
128
+
129
+ GetQueue(self, queue);
129
130
 
130
131
  while (1) {
132
+ VALUE switchpoint_result;
133
+
131
134
  if (queue->values.count) Fiber_make_runnable(fiber, Qnil);
132
135
 
133
136
  ring_buffer_push(&queue->shift_queue, fiber);
134
- VALUE switchpoint_result = Backend_wait_event(backend, Qnil);
137
+ switchpoint_result = Backend_wait_event(backend, Qnil);
135
138
  ring_buffer_delete(&queue->shift_queue, fiber);
136
139
 
137
140
  RAISE_IF_EXCEPTION(switchpoint_result);
138
141
  RB_GC_GUARD(switchpoint_result);
139
142
  if (queue->values.count) break;
140
143
  }
141
- VALUE value = ring_buffer_shift(&queue->values);
144
+ value = ring_buffer_shift(&queue->values);
142
145
  if ((queue->capacity) && (queue->capacity > queue->values.count))
143
146
  queue_schedule_first_blocked_fiber(&queue->push_queue);
144
147
  RB_GC_GUARD(value);
@@ -206,9 +209,11 @@ VALUE Queue_shift_each(VALUE self) {
206
209
 
207
210
  VALUE Queue_shift_all(VALUE self) {
208
211
  Queue_t *queue;
212
+ VALUE result;
213
+
209
214
  GetQueue(self, queue);
210
215
 
211
- VALUE result = ring_buffer_shift_all(&queue->values);
216
+ result = ring_buffer_shift_all(&queue->values);
212
217
  if (queue->capacity) queue_schedule_blocked_fibers_to_capacity(queue);
213
218
  return result;
214
219
  }
@@ -24,9 +24,11 @@ inline void runqueue_ring_buffer_clear(runqueue_ring_buffer *buffer) {
24
24
  static runqueue_entry nil_runqueue_entry = {(Qnil), (Qnil)};
25
25
 
26
26
  inline runqueue_entry runqueue_ring_buffer_shift(runqueue_ring_buffer *buffer) {
27
+ runqueue_entry value;
28
+
27
29
  if (buffer->count == 0) return nil_runqueue_entry;
28
30
 
29
- runqueue_entry value = buffer->entries[buffer->head];
31
+ value = buffer->entries[buffer->head];
30
32
  buffer->head = (buffer->head + 1) % buffer->size;
31
33
  buffer->count--;
32
34
  return value;
@@ -17,10 +17,13 @@ VALUE Socket_double_chevron(VALUE self, VALUE msg) {
17
17
  }
18
18
 
19
19
  void Init_SocketExtensions() {
20
+ VALUE cSocket;
21
+ VALUE cTCPSocket;
22
+
20
23
  rb_require("socket");
21
24
 
22
- VALUE cSocket = rb_const_get(rb_cObject, rb_intern("Socket"));
23
- VALUE cTCPSocket = rb_const_get(rb_cObject, rb_intern("TCPSocket"));
25
+ cSocket = rb_const_get(rb_cObject, rb_intern("Socket"));
26
+ cTCPSocket = rb_const_get(rb_cObject, rb_intern("TCPSocket"));
24
27
 
25
28
  rb_define_method(cSocket, "send", Socket_send, 2);
26
29
  rb_define_method(cTCPSocket, "send", Socket_send, 2);
@@ -40,7 +40,7 @@ VALUE Thread_fiber_schedule_and_wakeup(VALUE self, VALUE fiber, VALUE resume_obj
40
40
  }
41
41
 
42
42
  if (Backend_wakeup(rb_ivar_get(self, ID_ivar_backend)) == Qnil) {
43
- // we're not inside the ev_loop, so we just do a switchpoint
43
+ // we're not inside Backend_poll, so we just do a switchpoint
44
44
  Thread_switch_fiber(self);
45
45
  }
46
46
 
File without changes
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../extensions/core'
4
- require_relative '../extensions/fiber'
5
- require_relative './exceptions'
6
3
  require_relative './throttler'
7
4
 
8
5
  module Polyphony
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Exeption overrides
4
+ class ::Exception
5
+ class << self
6
+ attr_accessor :__disable_sanitized_backtrace__
7
+ end
8
+
9
+ attr_accessor :source_fiber, :raising_fiber
10
+
11
+ alias_method :orig_initialize, :initialize
12
+ def initialize(*args)
13
+ @raising_fiber = Fiber.current
14
+ orig_initialize(*args)
15
+ end
16
+
17
+ alias_method :orig_backtrace, :backtrace
18
+ def backtrace
19
+ unless @backtrace_called
20
+ @backtrace_called = true
21
+ return orig_backtrace
22
+ end
23
+
24
+ sanitized_backtrace
25
+ end
26
+
27
+ def sanitized_backtrace
28
+ return sanitize(orig_backtrace) unless @raising_fiber
29
+
30
+ backtrace = orig_backtrace || []
31
+ sanitize(backtrace + @raising_fiber.caller)
32
+ end
33
+
34
+ POLYPHONY_DIR = File.expand_path(File.join(__dir__, '..'))
35
+
36
+ def sanitize(backtrace)
37
+ return backtrace if ::Exception.__disable_sanitized_backtrace__
38
+
39
+ backtrace.reject { |l| l[POLYPHONY_DIR] }
40
+ end
41
+
42
+ def invoke
43
+ Kernel.raise(self)
44
+ end
45
+ end
@@ -1,22 +1,44 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'fiber'
4
3
  require_relative '../core/exceptions'
5
4
 
6
5
  module Polyphony
7
6
  # Fiber control API
8
7
  module FiberControl
8
+ # Returns the fiber's monitoring mailbox queue, used for receiving fiber
9
+ # monitoring messages.
10
+ #
11
+ # @return [Polyphony::Queue] Monitoring mailbox queue
9
12
  def monitor_mailbox
10
13
  @monitor_mailbox ||= Polyphony::Queue.new
11
14
  end
12
15
 
16
+ # call-seq:
17
+ # fiber.stop(value = nil) -> fiber
18
+ # Fiber.interrupt(value = nil) -> fiber
19
+ #
20
+ # Stops the fiber by raising a Polyphony::MoveOn exception. The given value
21
+ # will become the fiber's return value.
22
+ #
23
+ # @param value [any] Fiber's eventual return value
24
+ # @return [Fiber] fiber
13
25
  def interrupt(value = nil)
14
26
  return if @running == false
15
27
 
16
28
  schedule Polyphony::MoveOn.new(value)
29
+ self
17
30
  end
18
31
  alias_method :stop, :interrupt
19
32
 
33
+ # call-seq:
34
+ # fiber.reset(value = nil) -> fiber
35
+ # fiber.restart(value = nil) -> fiber
36
+ #
37
+ # Restarts the fiber, with the given value serving as the first value passed
38
+ # to the fiber's block.
39
+ #
40
+ # @param value [any] value passed to fiber block
41
+ # @return [Fiber] restarted fiber
20
42
  def restart(value = nil)
21
43
  raise "Can't restart main fiber" if @main
22
44
 
@@ -32,32 +54,58 @@ module Polyphony
32
54
  end
33
55
  alias_method :reset, :restart
34
56
 
57
+ # Stops a fiber by raising a Polyphony::Cancel exception.
58
+ #
59
+ # @return [Fiber] fiber
35
60
  def cancel
36
61
  return if @running == false
37
62
 
38
63
  schedule Polyphony::Cancel.new
64
+ self
39
65
  end
40
66
 
67
+ # Sets the graceful shutdown flag for the fiber.
68
+ #
69
+ # @param graceful [bool] Whether or not to perform a graceful shutdown
41
70
  def graceful_shutdown=(graceful)
42
71
  @graceful_shutdown = graceful
43
72
  end
44
73
 
74
+ # Returns the graceful shutdown flag for the fiber.
75
+ #
76
+ # @return [bool]
45
77
  def graceful_shutdown?
46
78
  @graceful_shutdown
47
79
  end
48
80
 
81
+ # Terminates the fiber, optionally setting the graceful shutdown flag.
82
+ #
83
+ # @param graceful [bool] Whether to perform a graceful shutdown
84
+ # @return [Fiber]
49
85
  def terminate(graceful = false)
50
86
  return if @running == false
51
87
 
52
88
  @graceful_shutdown = graceful
53
89
  schedule Polyphony::Terminate.new
90
+ self
54
91
  end
55
92
 
93
+ # call-seq:
94
+ # fiber.raise(message) -> fiber
95
+ # fiber.raise(exception_class) -> fiber
96
+ # fiber.raise(exception_class, exception_message) -> fiber
97
+ # fiber.raise(exception) -> fiber
98
+ #
99
+ # Raises an exception in the context of the fiber.
100
+ #
101
+ # @return [Fiber]
56
102
  def raise(*args)
57
103
  error = error_from_raise_args(args)
58
104
  schedule(error)
105
+ self
59
106
  end
60
107
 
108
+ # :no-doc:
61
109
  def error_from_raise_args(args)
62
110
  case (arg = args.shift)
63
111
  when String then RuntimeError.new(arg)
@@ -118,6 +166,17 @@ module Polyphony
118
166
 
119
167
  # Class methods for controlling fibers (namely await and select)
120
168
  module FiberControlClassMethods
169
+ # call-seq:
170
+ # Fiber.await(*fibers) -> [*results]
171
+ # Fiber.join(*fibers) -> [*results]
172
+ #
173
+ # Waits for all given fibers to terminate, then returns the respective
174
+ # return values for all terminated fibers. If any of the awaited fibers
175
+ # terminates with an uncaught exception, `Fiber.await` will await all the
176
+ # other fibers to terminate, then reraise the exception.
177
+ #
178
+ # @param *fibers [Array<Fiber>] fibers to wait for
179
+ # @return [Array<any>] return values of given fibers
121
180
  def await(*fibers)
122
181
  return [] if fibers.empty?
123
182
 
@@ -151,6 +210,12 @@ module Polyphony
151
210
  end
152
211
  alias_method :join, :await
153
212
 
213
+ # Waits for at least one of the given fibers to terminate, returning an
214
+ # array containing the first terminated fiber and its return value. If an
215
+ # exception occurs in one of the given fibers, it will be reraised.
216
+ #
217
+ # @param *fibers [Array<Fiber>] Fibers to wait for
218
+ # @return [Array] Array containing the first terminated fiber and its return value
154
219
  def select(*fibers)
155
220
  return nil if fibers.empty?
156
221
 
@@ -185,10 +250,18 @@ module Polyphony
185
250
  def schedule_priority_oob_fiber(&block)
186
251
  f = Fiber.new do
187
252
  Fiber.current.setup_raw
188
- block.call
253
+ result = block.call
189
254
  rescue Exception => e
190
255
  Thread.current.schedule_and_wakeup(Thread.main.main_fiber, e)
256
+ result = e
257
+ ensure
258
+ Thread.backend.trace(:fiber_terminate, f, result)
259
+ suspend
191
260
  end
261
+ f.oob = true
262
+ location = block.source_location
263
+ f.set_caller(["#{location.join(':')}"])
264
+ Thread.backend.trace(:fiber_create, f)
192
265
  Thread.current.schedule_and_wakeup(f, nil)
193
266
  end
194
267
  end
@@ -384,7 +457,7 @@ class ::Fiber
384
457
 
385
458
  extend Polyphony::FiberControlClassMethods
386
459
 
387
- attr_accessor :tag, :thread, :parent
460
+ attr_accessor :tag, :thread, :parent, :oob
388
461
  attr_reader :result
389
462
 
390
463
  def running?
@@ -401,7 +474,11 @@ class ::Fiber
401
474
  alias_method :to_s, :inspect
402
475
 
403
476
  def location
404
- @caller ? @caller[0] : '(root)'
477
+ if @oob
478
+ "#{@caller[0]} (oob)"
479
+ else
480
+ @caller ? @caller[0] : '(root)'
481
+ end
405
482
  end
406
483
 
407
484
  def caller
@@ -413,6 +490,10 @@ class ::Fiber
413
490
  end
414
491
  end
415
492
 
493
+ def set_caller(o)
494
+ @caller = o
495
+ end
496
+
416
497
  def main?
417
498
  @main
418
499
  end
@@ -1,73 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'fiber'
4
- require 'timeout'
5
3
  require 'open3'
6
4
 
7
- require_relative '../core/exceptions'
8
-
9
- # Exeption overrides
10
- class ::Exception
11
- class << self
12
- attr_accessor :__disable_sanitized_backtrace__
13
- end
14
-
15
- attr_accessor :source_fiber, :raising_fiber
16
-
17
- alias_method :orig_initialize, :initialize
18
- def initialize(*args)
19
- @raising_fiber = Fiber.current
20
- orig_initialize(*args)
21
- end
22
-
23
- alias_method :orig_backtrace, :backtrace
24
- def backtrace
25
- unless @backtrace_called
26
- @backtrace_called = true
27
- return orig_backtrace
28
- end
29
-
30
- sanitized_backtrace
31
- end
32
-
33
- def sanitized_backtrace
34
- return sanitize(orig_backtrace) unless @raising_fiber
35
-
36
- backtrace = orig_backtrace || []
37
- sanitize(backtrace + @raising_fiber.caller)
38
- end
39
-
40
- POLYPHONY_DIR = File.expand_path(File.join(__dir__, '..'))
41
-
42
- def sanitize(backtrace)
43
- return backtrace if ::Exception.__disable_sanitized_backtrace__
44
-
45
- backtrace.reject { |l| l[POLYPHONY_DIR] }
46
- end
47
-
48
- def invoke
49
- Kernel.raise(self)
50
- end
51
- end
52
-
53
- # Overrides for Process
54
- module ::Process
55
- class << self
56
- alias_method :orig_detach, :detach
57
- def detach(pid)
58
- fiber = spin { Polyphony.backend_waitpid(pid) }
59
- fiber.define_singleton_method(:pid) { pid }
60
- fiber
61
- end
62
-
63
- alias_method :orig_daemon, :daemon
64
- def daemon(*args)
65
- orig_daemon(*args)
66
- Polyphony.original_pid = Process.pid
67
- end
68
- end
69
- end
70
-
71
5
  # Kernel extensions (methods available to all objects / call sites)
72
6
  module ::Kernel
73
7
  alias_method :orig_sleep, :sleep
@@ -159,10 +93,3 @@ module ::Kernel
159
93
  end
160
94
  end
161
95
  end
162
-
163
- # Override Timeout to use cancel scope
164
- module ::Timeout
165
- def self.timeout(sec, klass = Timeout::Error, message = 'execution expired', &block)
166
- cancel_after(sec, with_exception: [klass, message], &block)
167
- end
168
- end
@@ -38,8 +38,10 @@ class ::OpenSSL::SSL::SSLSocket
38
38
 
39
39
  alias_method :orig_sysread, :sysread
40
40
  def sysread(maxlen, buf = +'')
41
+ # ensure socket is non blocking
42
+ Polyphony.backend_verify_blocking_mode(io, false)
41
43
  while true
42
- case (result = read_nonblock(maxlen, buf, exception: false))
44
+ case (result = sysread_nonblock(maxlen, buf, exception: false))
43
45
  when :wait_readable then Polyphony.backend_wait_io(io, false)
44
46
  when :wait_writable then Polyphony.backend_wait_io(io, true)
45
47
  else return result
@@ -49,6 +51,8 @@ class ::OpenSSL::SSL::SSLSocket
49
51
 
50
52
  alias_method :orig_syswrite, :syswrite
51
53
  def syswrite(buf)
54
+ # ensure socket is non blocking
55
+ Polyphony.backend_verify_blocking_mode(io, false)
52
56
  while true
53
57
  case (result = write_nonblock(buf, exception: false))
54
58
  when :wait_readable then Polyphony.backend_wait_io(io, false)