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.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +15 -11
- data/.github/workflows/test_io_uring.yml +32 -0
- data/.gitignore +3 -1
- data/CHANGELOG.md +24 -0
- data/Gemfile.lock +16 -13
- data/bin/pdbg +0 -0
- data/bin/polyphony-debug +0 -0
- data/bin/stress.rb +0 -0
- data/bin/test +0 -0
- data/docs/api-reference/exception.md +5 -1
- data/examples/core/ring.rb +29 -0
- data/ext/polyphony/backend_common.c +90 -12
- data/ext/polyphony/backend_common.h +9 -1
- data/ext/polyphony/backend_io_uring.c +257 -134
- data/ext/polyphony/backend_io_uring_context.c +1 -0
- data/ext/polyphony/backend_io_uring_context.h +2 -1
- data/ext/polyphony/backend_libev.c +33 -29
- data/ext/polyphony/event.c +5 -2
- data/ext/polyphony/extconf.rb +1 -0
- data/ext/polyphony/polyphony.c +11 -1
- data/ext/polyphony/polyphony.h +9 -2
- data/ext/polyphony/queue.c +10 -5
- data/ext/polyphony/runqueue_ring_buffer.c +3 -1
- data/ext/polyphony/socket_extensions.c +5 -2
- data/ext/polyphony/thread.c +1 -1
- data/lib/polyphony/{extensions → core}/debug.rb +0 -0
- data/lib/polyphony/core/global_api.rb +0 -3
- data/lib/polyphony/extensions/exception.rb +45 -0
- data/lib/polyphony/extensions/fiber.rb +85 -4
- data/lib/polyphony/extensions/{core.rb → kernel.rb} +0 -73
- data/lib/polyphony/extensions/openssl.rb +5 -1
- data/lib/polyphony/extensions/process.rb +19 -0
- data/lib/polyphony/extensions/socket.rb +12 -6
- data/lib/polyphony/extensions/thread.rb +9 -3
- data/lib/polyphony/extensions/timeout.rb +10 -0
- data/lib/polyphony/extensions.rb +9 -0
- data/lib/polyphony/version.rb +1 -1
- data/lib/polyphony.rb +4 -4
- data/test/helper.rb +0 -5
- data/test/test_backend.rb +3 -5
- data/test/test_global_api.rb +21 -12
- data/test/test_io.rb +2 -2
- data/test/test_kernel.rb +2 -2
- data/test/test_process_supervision.rb +1 -1
- data/test/test_signal.rb +20 -1
- data/test/test_socket.rb +35 -2
- data/test/test_thread.rb +1 -1
- data/test/test_thread_pool.rb +1 -1
- data/test/test_throttler.rb +3 -3
- data/test/test_timer.rb +1 -1
- data/test/test_trace.rb +7 -1
- metadata +11 -5
@@ -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
|
702
|
-
|
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,
|
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
|
-
|
1153
|
+
GetBackend(self, backend);
|
1156
1154
|
|
1157
1155
|
while (1) {
|
1158
|
-
|
1159
|
-
if (
|
1160
|
-
|
1161
|
-
if (
|
1162
|
-
|
1163
|
-
|
1164
|
-
|
1165
|
-
|
1166
|
-
|
1167
|
-
|
1168
|
-
|
1169
|
-
|
1170
|
-
|
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
|
-
|
1174
|
-
|
1175
|
-
|
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;
|
data/ext/polyphony/event.c
CHANGED
@@ -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
|
-
|
70
|
+
backend = rb_ivar_get(rb_thread_current(), ID_ivar_backend);
|
68
71
|
event->waiting_fiber = rb_fiber_current();
|
69
|
-
|
72
|
+
switchpoint_result = Backend_wait_event(backend, Qnil);
|
70
73
|
event->waiting_fiber = Qnil;
|
71
74
|
|
72
75
|
RAISE_IF_EXCEPTION(switchpoint_result);
|
data/ext/polyphony/extconf.rb
CHANGED
@@ -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'
|
data/ext/polyphony/polyphony.c
CHANGED
@@ -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
|
}
|
data/ext/polyphony/polyphony.h
CHANGED
@@ -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
|
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
|
-
#
|
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);
|
data/ext/polyphony/queue.c
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
23
|
-
|
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);
|
data/ext/polyphony/thread.c
CHANGED
@@ -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
|
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
|
@@ -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
|
-
|
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 =
|
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)
|