polyphony 0.72 → 0.75

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 (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)