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