polyphony 0.19 → 0.20
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -1
- data/.rubocop.yml +87 -1
- data/CHANGELOG.md +35 -0
- data/Gemfile.lock +17 -6
- data/README.md +200 -139
- data/Rakefile +4 -4
- data/TODO.md +35 -7
- data/bin/poly +11 -0
- data/docs/getting-started/getting-started.md +1 -1
- data/docs/summary.md +3 -0
- data/docs/technical-overview/exception-handling.md +94 -0
- data/docs/technical-overview/fiber-scheduling.md +99 -0
- data/examples/core/cancel.rb +8 -4
- data/examples/core/channel_echo.rb +18 -17
- data/examples/core/defer.rb +12 -0
- data/examples/core/enumerator.rb +4 -4
- data/examples/core/fiber_error.rb +9 -0
- data/examples/core/fiber_error_with_backtrace.rb +73 -0
- data/examples/core/fork.rb +6 -6
- data/examples/core/genserver.rb +16 -8
- data/examples/core/lock.rb +3 -3
- data/examples/core/move_on.rb +4 -3
- data/examples/core/move_on_twice.rb +5 -5
- data/examples/core/move_on_with_ensure.rb +8 -11
- data/examples/core/move_on_with_value.rb +14 -0
- data/examples/core/{multiple_spawn.rb → multiple_spin.rb} +5 -5
- data/examples/core/nested_cancel.rb +5 -5
- data/examples/core/{nested_multiple_spawn.rb → nested_multiple_spin.rb} +6 -6
- data/examples/core/nested_spin.rb +17 -0
- data/examples/core/pingpong.rb +21 -0
- data/examples/core/pulse.rb +4 -5
- data/examples/core/resource.rb +6 -4
- data/examples/core/resource_cancel.rb +6 -9
- data/examples/core/resource_delegate.rb +3 -3
- data/examples/core/sleep.rb +3 -3
- data/examples/core/sleep_spin.rb +19 -0
- data/examples/core/snooze.rb +32 -0
- data/examples/core/spin.rb +14 -0
- data/examples/core/{spawn_cancel.rb → spin_cancel.rb} +6 -7
- data/examples/core/spin_error.rb +17 -0
- data/examples/core/spin_error_backtrace.rb +30 -0
- data/examples/core/spin_uncaught_error.rb +15 -0
- data/examples/core/supervisor.rb +8 -8
- data/examples/core/supervisor_with_cancel_scope.rb +7 -7
- data/examples/core/supervisor_with_error.rb +8 -8
- data/examples/core/supervisor_with_manual_move_on.rb +6 -7
- data/examples/core/suspend.rb +13 -0
- data/examples/core/thread.rb +1 -1
- data/examples/core/thread_cancel.rb +9 -11
- data/examples/core/thread_pool.rb +18 -14
- data/examples/core/throttle.rb +7 -7
- data/examples/core/timeout.rb +3 -3
- data/examples/fs/read.rb +7 -9
- data/examples/http/config.ru +7 -3
- data/examples/http/cuba.ru +22 -0
- data/examples/http/happy_eyeballs.rb +6 -4
- data/examples/http/http_client.rb +1 -1
- data/examples/http/http_get.rb +1 -1
- data/examples/http/http_parse_experiment.rb +21 -16
- data/examples/http/http_proxy.rb +28 -26
- data/examples/http/http_server.rb +10 -10
- data/examples/http/http_server_forked.rb +6 -5
- data/examples/http/http_server_throttled.rb +3 -3
- data/examples/http/http_ws_server.rb +11 -11
- data/examples/http/https_raw_client.rb +1 -1
- data/examples/http/https_server.rb +8 -8
- data/examples/http/https_wss_server.rb +13 -11
- data/examples/http/rack_server.rb +2 -2
- data/examples/http/rack_server_https.rb +4 -4
- data/examples/http/rack_server_https_forked.rb +5 -5
- data/examples/http/websocket_secure_server.rb +6 -6
- data/examples/http/websocket_server.rb +5 -5
- data/examples/interfaces/pg_client.rb +4 -4
- data/examples/interfaces/pg_pool.rb +13 -6
- data/examples/interfaces/pg_transaction.rb +5 -4
- data/examples/interfaces/redis_channels.rb +15 -11
- data/examples/interfaces/redis_client.rb +2 -2
- data/examples/interfaces/redis_pubsub.rb +2 -1
- data/examples/interfaces/redis_pubsub_perf.rb +13 -9
- data/examples/io/backticks.rb +11 -0
- data/examples/io/cat.rb +4 -5
- data/examples/io/echo_client.rb +9 -4
- data/examples/io/echo_client_from_stdin.rb +20 -0
- data/examples/io/echo_pipe.rb +7 -8
- data/examples/io/echo_server.rb +8 -6
- data/examples/io/echo_server_with_timeout.rb +13 -10
- data/examples/io/echo_stdin.rb +3 -3
- data/examples/io/httparty.rb +2 -2
- data/examples/io/httparty_multi.rb +8 -4
- data/examples/io/httparty_threaded.rb +6 -2
- data/examples/io/io_read.rb +2 -2
- data/examples/io/irb.rb +16 -4
- data/examples/io/net-http.rb +3 -3
- data/examples/io/open.rb +17 -0
- data/examples/io/system.rb +3 -3
- data/examples/io/tcpserver.rb +15 -0
- data/examples/io/tcpsocket.rb +6 -5
- data/examples/performance/multi_snooze.rb +29 -0
- data/examples/performance/{perf_snooze.rb → snooze.rb} +7 -5
- data/examples/performance/snooze_raw.rb +39 -0
- data/ext/gyro/async.c +165 -0
- data/ext/gyro/child.c +167 -0
- data/ext/{ev → gyro}/extconf.rb +4 -3
- data/ext/gyro/gyro.c +316 -0
- data/ext/{ev/ev.h → gyro/gyro.h} +12 -7
- data/ext/gyro/gyro_ext.c +23 -0
- data/ext/{ev → gyro}/io.c +65 -57
- data/ext/{ev → gyro}/libev.h +0 -0
- data/ext/gyro/signal.c +117 -0
- data/ext/{ev → gyro}/socket.c +61 -6
- data/ext/gyro/timer.c +199 -0
- data/ext/libev/Changes +35 -0
- data/ext/libev/README +2 -1
- data/ext/libev/ev.c +213 -151
- data/ext/libev/ev.h +95 -88
- data/ext/libev/ev_epoll.c +26 -15
- data/ext/libev/ev_kqueue.c +11 -5
- data/ext/libev/ev_linuxaio.c +642 -0
- data/ext/libev/ev_poll.c +13 -8
- data/ext/libev/ev_port.c +5 -2
- data/ext/libev/ev_vars.h +14 -3
- data/ext/libev/ev_wrap.h +16 -0
- data/lib/ev_ext.bundle +0 -0
- data/lib/polyphony.rb +46 -50
- data/lib/polyphony/auto_run.rb +12 -0
- data/lib/polyphony/core/cancel_scope.rb +11 -7
- data/lib/polyphony/core/channel.rb +16 -9
- data/lib/polyphony/core/coprocess.rb +101 -51
- data/lib/polyphony/core/exceptions.rb +14 -12
- data/lib/polyphony/core/resource_pool.rb +21 -8
- data/lib/polyphony/core/supervisor.rb +10 -5
- data/lib/polyphony/core/sync.rb +7 -6
- data/lib/polyphony/core/thread.rb +4 -4
- data/lib/polyphony/core/thread_pool.rb +4 -4
- data/lib/polyphony/core/throttler.rb +6 -4
- data/lib/polyphony/extensions/core.rb +253 -0
- data/lib/polyphony/extensions/io.rb +28 -16
- data/lib/polyphony/extensions/openssl.rb +2 -1
- data/lib/polyphony/extensions/socket.rb +47 -52
- data/lib/polyphony/http.rb +4 -3
- data/lib/polyphony/http/agent.rb +68 -57
- data/lib/polyphony/http/server.rb +5 -5
- data/lib/polyphony/http/server/http1.rb +268 -0
- data/lib/polyphony/http/server/http2.rb +62 -0
- data/lib/polyphony/http/server/http2_stream.rb +104 -0
- data/lib/polyphony/http/server/rack.rb +64 -0
- data/lib/polyphony/http/server/request.rb +119 -0
- data/lib/polyphony/net.rb +26 -15
- data/lib/polyphony/postgres.rb +17 -13
- data/lib/polyphony/redis.rb +16 -15
- data/lib/polyphony/version.rb +1 -1
- data/lib/polyphony/websocket.rb +11 -4
- data/polyphony.gemspec +13 -9
- data/test/eg.rb +27 -0
- data/test/helper.rb +25 -0
- data/test/run.rb +5 -0
- data/test/test_async.rb +33 -0
- data/test/test_coprocess.rb +239 -77
- data/test/test_core.rb +95 -61
- data/test/test_gyro.rb +148 -0
- data/test/test_http_server.rb +313 -0
- data/test/test_io.rb +79 -27
- data/test/test_kernel.rb +22 -12
- data/test/test_signal.rb +36 -0
- data/test/test_timer.rb +24 -0
- metadata +89 -33
- data/examples/core/nested_async.rb +0 -17
- data/examples/core/next_tick.rb +0 -12
- data/examples/core/sleep_spawn.rb +0 -19
- data/examples/core/spawn.rb +0 -14
- data/examples/core/spawn_error.rb +0 -28
- data/examples/performance/perf_multi_snooze.rb +0 -21
- data/ext/ev/async.c +0 -168
- data/ext/ev/child.c +0 -169
- data/ext/ev/ev_ext.c +0 -23
- data/ext/ev/ev_module.c +0 -242
- data/ext/ev/signal.c +0 -119
- data/ext/ev/timer.c +0 -197
- data/lib/polyphony/core/fiber_pool.rb +0 -98
- data/lib/polyphony/extensions/kernel.rb +0 -169
- data/lib/polyphony/http/http1_adapter.rb +0 -254
- data/lib/polyphony/http/http2_adapter.rb +0 -157
- data/lib/polyphony/http/rack.rb +0 -25
- data/lib/polyphony/http/request.rb +0 -66
- data/test/test_ev.rb +0 -110
data/ext/libev/ev_poll.c
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
/*
|
2
2
|
* libev poll fd activity backend
|
3
3
|
*
|
4
|
-
* Copyright (c) 2007,2008,2009,2010,2011 Marc Alexander Lehmann <libev@schmorp.de>
|
4
|
+
* Copyright (c) 2007,2008,2009,2010,2011,2016,2019 Marc Alexander Lehmann <libev@schmorp.de>
|
5
5
|
* All rights reserved.
|
6
6
|
*
|
7
7
|
* Redistribution and use in source and binary forms, with or without modifica-
|
@@ -41,10 +41,12 @@
|
|
41
41
|
|
42
42
|
inline_size
|
43
43
|
void
|
44
|
-
|
44
|
+
array_needsize_pollidx (int *base, int offset, int count)
|
45
45
|
{
|
46
|
-
/*
|
47
|
-
* to
|
46
|
+
/* using memset (.., -1, ...) is tempting, we we try
|
47
|
+
* to be ultraportable
|
48
|
+
*/
|
49
|
+
base += offset;
|
48
50
|
while (count--)
|
49
51
|
*base++ = -1;
|
50
52
|
}
|
@@ -57,14 +59,14 @@ poll_modify (EV_P_ int fd, int oev, int nev)
|
|
57
59
|
if (oev == nev)
|
58
60
|
return;
|
59
61
|
|
60
|
-
array_needsize (int, pollidxs, pollidxmax, fd + 1,
|
62
|
+
array_needsize (int, pollidxs, pollidxmax, fd + 1, array_needsize_pollidx);
|
61
63
|
|
62
64
|
idx = pollidxs [fd];
|
63
65
|
|
64
66
|
if (idx < 0) /* need to allocate a new pollfd */
|
65
67
|
{
|
66
68
|
pollidxs [fd] = idx = pollcnt++;
|
67
|
-
array_needsize (struct pollfd, polls, pollmax, pollcnt,
|
69
|
+
array_needsize (struct pollfd, polls, pollmax, pollcnt, array_needsize_noinit);
|
68
70
|
polls [idx].fd = fd;
|
69
71
|
}
|
70
72
|
|
@@ -108,14 +110,17 @@ poll_poll (EV_P_ ev_tstamp timeout)
|
|
108
110
|
else
|
109
111
|
for (p = polls; res; ++p)
|
110
112
|
{
|
111
|
-
assert (("libev: poll
|
113
|
+
assert (("libev: poll returned illegal result, broken BSD kernel?", p < polls + pollcnt));
|
112
114
|
|
113
115
|
if (expect_false (p->revents)) /* this expect is debatable */
|
114
116
|
{
|
115
117
|
--res;
|
116
118
|
|
117
119
|
if (expect_false (p->revents & POLLNVAL))
|
118
|
-
|
120
|
+
{
|
121
|
+
assert (("libev: poll found invalid fd in poll set", 0));
|
122
|
+
fd_kill (EV_A_ p->fd);
|
123
|
+
}
|
119
124
|
else
|
120
125
|
fd_event (
|
121
126
|
EV_A_
|
data/ext/libev/ev_port.c
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
/*
|
2
2
|
* libev solaris event port backend
|
3
3
|
*
|
4
|
-
* Copyright (c) 2007,2008,2009,2010,2011 Marc Alexander Lehmann <libev@schmorp.de>
|
4
|
+
* Copyright (c) 2007,2008,2009,2010,2011,2019 Marc Alexander Lehmann <libev@schmorp.de>
|
5
5
|
* All rights reserved.
|
6
6
|
*
|
7
7
|
* Redistribution and use in source and binary forms, with or without modifica-
|
@@ -69,7 +69,10 @@ port_associate_and_check (EV_P_ int fd, int ev)
|
|
69
69
|
)
|
70
70
|
{
|
71
71
|
if (errno == EBADFD)
|
72
|
-
|
72
|
+
{
|
73
|
+
assert (("libev: port_associate found invalid fd", errno != EBADFD));
|
74
|
+
fd_kill (EV_A_ fd);
|
75
|
+
}
|
73
76
|
else
|
74
77
|
ev_syserr ("(libev) port_associate");
|
75
78
|
}
|
data/ext/libev/ev_vars.h
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
/*
|
2
2
|
* loop member variable declarations
|
3
3
|
*
|
4
|
-
* Copyright (c) 2007,2008,2009,2010,2011,2012,2013 Marc Alexander Lehmann <libev@schmorp.de>
|
4
|
+
* Copyright (c) 2007,2008,2009,2010,2011,2012,2013,2019 Marc Alexander Lehmann <libev@schmorp.de>
|
5
5
|
* All rights reserved.
|
6
6
|
*
|
7
7
|
* Redistribution and use in source and binary forms, with or without modifica-
|
@@ -107,6 +107,17 @@ VARx(int, epoll_epermcnt)
|
|
107
107
|
VARx(int, epoll_epermmax)
|
108
108
|
#endif
|
109
109
|
|
110
|
+
#if EV_USE_LINUXAIO || EV_GENWRAP
|
111
|
+
VARx(aio_context_t, linuxaio_ctx)
|
112
|
+
VARx(int, linuxaio_iteration)
|
113
|
+
VARx(struct aniocb **, linuxaio_iocbps)
|
114
|
+
VARx(int, linuxaio_iocbpmax)
|
115
|
+
VARx(struct iocb **, linuxaio_submits)
|
116
|
+
VARx(int, linuxaio_submitcnt)
|
117
|
+
VARx(int, linuxaio_submitmax)
|
118
|
+
VARx(ev_io, linuxaio_epoll_w)
|
119
|
+
#endif
|
120
|
+
|
110
121
|
#if EV_USE_KQUEUE || EV_GENWRAP
|
111
122
|
VARx(pid_t, kqueue_fd_pid)
|
112
123
|
VARx(struct kevent *, kqueue_changes)
|
@@ -195,8 +206,8 @@ VARx(unsigned int, loop_depth) /* #ev_run enters - #ev_run leaves */
|
|
195
206
|
|
196
207
|
VARx(void *, userdata)
|
197
208
|
/* C++ doesn't support the ev_loop_callback typedef here. stinks. */
|
198
|
-
VAR (release_cb, void (*release_cb)(EV_P)
|
199
|
-
VAR (acquire_cb, void (*acquire_cb)(EV_P)
|
209
|
+
VAR (release_cb, void (*release_cb)(EV_P) EV_NOEXCEPT)
|
210
|
+
VAR (acquire_cb, void (*acquire_cb)(EV_P) EV_NOEXCEPT)
|
200
211
|
VAR (invoke_cb , ev_loop_callback invoke_cb)
|
201
212
|
#endif
|
202
213
|
|
data/ext/libev/ev_wrap.h
CHANGED
@@ -50,6 +50,14 @@
|
|
50
50
|
#define kqueue_eventmax ((loop)->kqueue_eventmax)
|
51
51
|
#define kqueue_events ((loop)->kqueue_events)
|
52
52
|
#define kqueue_fd_pid ((loop)->kqueue_fd_pid)
|
53
|
+
#define linuxaio_ctx ((loop)->linuxaio_ctx)
|
54
|
+
#define linuxaio_epoll_w ((loop)->linuxaio_epoll_w)
|
55
|
+
#define linuxaio_iocbpmax ((loop)->linuxaio_iocbpmax)
|
56
|
+
#define linuxaio_iocbps ((loop)->linuxaio_iocbps)
|
57
|
+
#define linuxaio_iteration ((loop)->linuxaio_iteration)
|
58
|
+
#define linuxaio_submitcnt ((loop)->linuxaio_submitcnt)
|
59
|
+
#define linuxaio_submitmax ((loop)->linuxaio_submitmax)
|
60
|
+
#define linuxaio_submits ((loop)->linuxaio_submits)
|
53
61
|
#define loop_count ((loop)->loop_count)
|
54
62
|
#define loop_depth ((loop)->loop_depth)
|
55
63
|
#define loop_done ((loop)->loop_done)
|
@@ -149,6 +157,14 @@
|
|
149
157
|
#undef kqueue_eventmax
|
150
158
|
#undef kqueue_events
|
151
159
|
#undef kqueue_fd_pid
|
160
|
+
#undef linuxaio_ctx
|
161
|
+
#undef linuxaio_epoll_w
|
162
|
+
#undef linuxaio_iocbpmax
|
163
|
+
#undef linuxaio_iocbps
|
164
|
+
#undef linuxaio_iteration
|
165
|
+
#undef linuxaio_submitcnt
|
166
|
+
#undef linuxaio_submitmax
|
167
|
+
#undef linuxaio_submits
|
152
168
|
#undef loop_count
|
153
169
|
#undef loop_depth
|
154
170
|
#undef loop_done
|
data/lib/ev_ext.bundle
ADDED
Binary file
|
data/lib/polyphony.rb
CHANGED
@@ -5,60 +5,24 @@ require 'modulation/gem'
|
|
5
5
|
export_default :Polyphony
|
6
6
|
|
7
7
|
require 'fiber'
|
8
|
-
require_relative './
|
9
|
-
import('./polyphony/extensions/kernel')
|
10
|
-
import('./polyphony/extensions/io')
|
8
|
+
require_relative './gyro_ext'
|
11
9
|
|
10
|
+
import './polyphony/extensions/core'
|
11
|
+
import './polyphony/extensions/io'
|
12
|
+
|
13
|
+
# Main Polyphony API
|
12
14
|
module Polyphony
|
13
|
-
exceptions = import
|
15
|
+
exceptions = import './polyphony/core/exceptions'
|
14
16
|
Cancel = exceptions::Cancel
|
15
17
|
MoveOn = exceptions::MoveOn
|
16
18
|
|
17
|
-
Coprocess = import
|
18
|
-
|
19
|
-
Net = import('./polyphony/net')
|
20
|
-
|
19
|
+
Coprocess = import './polyphony/core/coprocess'
|
20
|
+
Net = import './polyphony/net'
|
21
21
|
|
22
|
-
def self.trap(sig, ref = false, &callback)
|
23
|
-
sig = Signal.list[sig.to_s.upcase] if sig.is_a?(Symbol)
|
24
|
-
watcher = EV::Signal.new(sig, &callback)
|
25
|
-
EV.unref unless ref
|
26
|
-
watcher
|
27
|
-
end
|
28
|
-
|
29
|
-
def self.fork(&block)
|
30
|
-
EV.break
|
31
|
-
pid = Kernel.fork do
|
32
|
-
FiberPool.reset!
|
33
|
-
EV.post_fork
|
34
|
-
Fiber.current.coprocess = Coprocess.new(Fiber.current)
|
35
|
-
|
36
|
-
block.()
|
37
|
-
|
38
|
-
# We cannot simply depend on the at_exit block (see below) to yield to the
|
39
|
-
# reactor fiber. Doing that will raise a FiberError complaining: "fiber
|
40
|
-
# called across stack rewinding barrier". Apparently this is a bug in
|
41
|
-
# Ruby, so the workaround is to yield just before exiting.
|
42
|
-
suspend
|
43
|
-
end
|
44
|
-
EV.restart
|
45
|
-
pid
|
46
|
-
end
|
47
|
-
|
48
|
-
def self.debug
|
49
|
-
@debug
|
50
|
-
end
|
51
|
-
|
52
|
-
def self.debug=(value)
|
53
|
-
@debug = value
|
54
|
-
end
|
55
|
-
|
56
22
|
auto_import(
|
57
23
|
CancelScope: './polyphony/core/cancel_scope',
|
58
24
|
Channel: './polyphony/core/channel',
|
59
|
-
# Coprocess: './polyphony/core/coprocess',
|
60
25
|
FS: './polyphony/fs',
|
61
|
-
# Net: './polyphony/net',
|
62
26
|
ResourcePool: './polyphony/core/resource_pool',
|
63
27
|
Supervisor: './polyphony/core/supervisor',
|
64
28
|
Sync: './polyphony/core/sync',
|
@@ -66,11 +30,43 @@ module Polyphony
|
|
66
30
|
ThreadPool: './polyphony/core/thread_pool',
|
67
31
|
Websocket: './polyphony/websocket'
|
68
32
|
)
|
69
|
-
end
|
70
33
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
34
|
+
class << self
|
35
|
+
def trap(sig, ref = false, &callback)
|
36
|
+
sig = Signal.list[sig.to_s.upcase] if sig.is_a?(Symbol)
|
37
|
+
watcher = Gyro::Signal.new(sig, &callback)
|
38
|
+
Gyro.unref unless ref
|
39
|
+
watcher
|
40
|
+
end
|
41
|
+
|
42
|
+
def fork(&block)
|
43
|
+
Gyro.break
|
44
|
+
pid = Kernel.fork do
|
45
|
+
setup_forked_process
|
46
|
+
block.()
|
47
|
+
|
48
|
+
# We cannot simply depend on the at_exit block (see polyphony/auto_run)
|
49
|
+
# to yield to the reactor fiber. Doing that will raise a FiberError
|
50
|
+
# complaining: "fiber called across stack rewinding barrier". Apparently
|
51
|
+
# this is a bug in Ruby, so the workaround is to yield just before
|
52
|
+
# exiting.
|
53
|
+
suspend
|
54
|
+
end
|
55
|
+
Gyro.restart
|
56
|
+
pid
|
57
|
+
end
|
58
|
+
|
59
|
+
def reset!
|
60
|
+
Fiber.root.scheduled_value = nil
|
61
|
+
Gyro.restart
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def setup_forked_process
|
67
|
+
Gyro.post_fork
|
68
|
+
Fiber.set_root_fiber
|
69
|
+
Fiber.current.coprocess = Coprocess.new(Fiber.current)
|
70
|
+
end
|
71
|
+
end
|
76
72
|
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../polyphony'
|
4
|
+
|
5
|
+
at_exit do
|
6
|
+
repl = (Pry.current rescue nil) || (IRB.CurrentContext rescue nil)
|
7
|
+
|
8
|
+
# in most cases, once the root fiber is done there are still pending
|
9
|
+
# operations going on. If the reactor loop is not done, we suspend the root
|
10
|
+
# fiber until it is done
|
11
|
+
suspend if $__reactor_fiber__&.alive? && !repl
|
12
|
+
end
|
@@ -8,19 +8,23 @@ Exceptions = import('./exceptions')
|
|
8
8
|
|
9
9
|
# A cancellation scope that can be used to cancel an asynchronous task
|
10
10
|
class CancelScope
|
11
|
-
def initialize(opts = {})
|
11
|
+
def initialize(opts = {}, &block)
|
12
12
|
@opts = opts
|
13
|
-
|
13
|
+
call(&block) if block
|
14
|
+
end
|
15
|
+
|
16
|
+
def error_class
|
17
|
+
@opts[:mode] == :cancel ? Exceptions::Cancel : Exceptions::MoveOn
|
14
18
|
end
|
15
19
|
|
16
20
|
def cancel!
|
17
21
|
@cancelled = true
|
18
22
|
@fiber.cancelled = true
|
19
|
-
@fiber.transfer
|
23
|
+
@fiber.transfer error_class.new(self, @opts[:value])
|
20
24
|
end
|
21
25
|
|
22
26
|
def start_timeout
|
23
|
-
@timeout =
|
27
|
+
@timeout = Gyro::Timer.new(@opts[:timeout], 0)
|
24
28
|
@timeout.start { cancel! }
|
25
29
|
end
|
26
30
|
|
@@ -41,11 +45,11 @@ class CancelScope
|
|
41
45
|
e.scope == self ? e.value : raise(e)
|
42
46
|
ensure
|
43
47
|
@timeout&.stop
|
44
|
-
protect(&@
|
48
|
+
protect(&@on_cancel) if @cancelled && @on_cancel
|
45
49
|
end
|
46
50
|
|
47
|
-
def
|
48
|
-
@
|
51
|
+
def on_cancel(&block)
|
52
|
+
@on_cancel = block
|
49
53
|
end
|
50
54
|
|
51
55
|
def cancelled?
|
@@ -4,6 +4,8 @@ export_default :Channel
|
|
4
4
|
|
5
5
|
Exceptions = import('./exceptions')
|
6
6
|
|
7
|
+
# Implements a unidirectional communication channel along the lines of Go
|
8
|
+
# (buffered) channels.
|
7
9
|
class Channel
|
8
10
|
def initialize
|
9
11
|
@payload_queue = []
|
@@ -15,25 +17,30 @@ class Channel
|
|
15
17
|
@waiting_queue.slice(0..-1).each { |f| f.schedule(stop) }
|
16
18
|
end
|
17
19
|
|
18
|
-
def <<(
|
20
|
+
def <<(value)
|
19
21
|
if @waiting_queue.empty?
|
20
|
-
@payload_queue <<
|
22
|
+
@payload_queue << value
|
21
23
|
else
|
22
|
-
@waiting_queue.shift&.schedule(
|
24
|
+
@waiting_queue.shift&.schedule(value)
|
23
25
|
end
|
24
26
|
snooze
|
25
27
|
end
|
26
28
|
|
27
29
|
def receive
|
28
|
-
|
30
|
+
Gyro.ref
|
29
31
|
if @payload_queue.empty?
|
30
32
|
@waiting_queue << Fiber.current
|
33
|
+
suspend
|
31
34
|
else
|
32
|
-
|
33
|
-
Fiber.current.schedule(payload)
|
35
|
+
receive_from_queue
|
34
36
|
end
|
35
|
-
suspend
|
36
37
|
ensure
|
37
|
-
|
38
|
+
Gyro.unref
|
38
39
|
end
|
39
|
-
|
40
|
+
|
41
|
+
def receive_from_queue
|
42
|
+
payload = @payload_queue.shift
|
43
|
+
snooze
|
44
|
+
payload
|
45
|
+
end
|
46
|
+
end
|
@@ -2,72 +2,116 @@
|
|
2
2
|
|
3
3
|
export_default :Coprocess
|
4
4
|
|
5
|
-
import
|
6
|
-
|
7
|
-
FiberPool = import('./fiber_pool')
|
8
|
-
Exceptions = import('./exceptions')
|
5
|
+
import '../extensions/core'
|
6
|
+
Exceptions = import './exceptions'
|
9
7
|
|
10
8
|
# Encapsulates an asynchronous task
|
11
9
|
class Coprocess
|
12
|
-
|
10
|
+
# inter-coprocess message passing
|
11
|
+
module Messaging
|
12
|
+
def <<(value)
|
13
|
+
if @receive_waiting && @fiber
|
14
|
+
@fiber&.schedule value
|
15
|
+
else
|
16
|
+
@queued_messages ||= []
|
17
|
+
@queued_messages << value
|
18
|
+
end
|
19
|
+
snooze
|
20
|
+
end
|
21
|
+
|
22
|
+
def receive
|
23
|
+
if !@queued_messages || @queued_messages&.empty?
|
24
|
+
wait_for_message
|
25
|
+
else
|
26
|
+
value = @queued_messages.shift
|
27
|
+
snooze
|
28
|
+
value
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def wait_for_message
|
33
|
+
Gyro.ref
|
34
|
+
@receive_waiting = true
|
35
|
+
suspend
|
36
|
+
ensure
|
37
|
+
Gyro.unref
|
38
|
+
@receive_waiting = nil
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
include Messaging
|
43
|
+
|
44
|
+
@@list = {}
|
13
45
|
|
46
|
+
def self.list
|
47
|
+
@@list
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.count
|
51
|
+
@@list.size
|
52
|
+
end
|
53
|
+
|
54
|
+
attr_reader :result, :fiber
|
14
55
|
|
15
56
|
def initialize(fiber = nil, &block)
|
16
57
|
@fiber = fiber
|
17
58
|
@block = block
|
18
59
|
end
|
19
60
|
|
20
|
-
def run
|
21
|
-
@
|
22
|
-
|
23
|
-
@fiber = FiberPool.run do
|
24
|
-
@fiber.coprocess = self
|
25
|
-
@result = (@block || block2).call(self)
|
26
|
-
rescue Exceptions::MoveOn, Exceptions::Stop => e
|
27
|
-
@result = e.value
|
28
|
-
rescue Exception => e
|
29
|
-
e.cleanup_backtrace(@caller) if Exceptions.debug
|
30
|
-
@result = e
|
31
|
-
ensure
|
32
|
-
@fiber.coprocess = nil
|
33
|
-
@fiber = nil
|
34
|
-
@awaiting_fiber&.schedule @result
|
35
|
-
@when_done&.()
|
36
|
-
|
37
|
-
# if result is an error and nobody's waiting on us, we need to raise it
|
38
|
-
raise @result if @result.is_a?(Exception) && !@awaiting_fiber
|
39
|
-
end
|
61
|
+
def run
|
62
|
+
@calling_fiber = Fiber.current
|
40
63
|
|
41
|
-
@
|
64
|
+
@fiber = Fiber.new { execute }
|
42
65
|
@fiber.schedule
|
66
|
+
@ran = true
|
43
67
|
self
|
44
68
|
end
|
45
69
|
|
46
|
-
def
|
47
|
-
|
48
|
-
@
|
49
|
-
@fiber
|
50
|
-
|
70
|
+
def execute
|
71
|
+
# uncaught_exception = nil
|
72
|
+
@@list[@fiber] = self
|
73
|
+
@fiber.coprocess = self
|
74
|
+
@result = @block.call(self)
|
75
|
+
rescue Exceptions::MoveOn => e
|
76
|
+
@result = e.value
|
77
|
+
rescue Exception => e
|
78
|
+
uncaught_exception = true
|
79
|
+
@result = e
|
80
|
+
ensure
|
81
|
+
finish_execution(uncaught_exception)
|
51
82
|
end
|
52
83
|
|
53
|
-
def
|
54
|
-
|
55
|
-
@
|
56
|
-
@fiber
|
57
|
-
|
58
|
-
@
|
59
|
-
|
60
|
-
|
61
|
-
|
84
|
+
def finish_execution(uncaught_exception)
|
85
|
+
@@list.delete(@fiber)
|
86
|
+
@fiber.coprocess = nil
|
87
|
+
@fiber = nil
|
88
|
+
@awaiting_fiber&.schedule @result
|
89
|
+
@when_done&.()
|
90
|
+
|
91
|
+
return unless uncaught_exception && !@awaiting_fiber
|
92
|
+
|
93
|
+
# if no awaiting fiber, raise any uncaught error by passing it to the
|
94
|
+
# calling fiber, or to the root fiber if the calling fiber
|
95
|
+
calling_fiber = @calling_fiber || Fiber.root
|
96
|
+
calling_fiber.transfer @result
|
62
97
|
end
|
63
98
|
|
64
|
-
def
|
99
|
+
def alive?
|
65
100
|
@fiber
|
66
101
|
end
|
67
102
|
|
68
103
|
# Kernel.await expects the given argument / block to be a callable, so #call
|
69
104
|
# in fact waits for the coprocess to finish
|
70
105
|
def await
|
106
|
+
await_coprocess_result
|
107
|
+
ensure
|
108
|
+
# If the awaiting fiber has been transferred an exception, the awaited fiber
|
109
|
+
# might still be running, so we need to stop it
|
110
|
+
@fiber&.schedule(Exceptions::MoveOn.new)
|
111
|
+
end
|
112
|
+
alias_method :join, :await
|
113
|
+
|
114
|
+
def await_coprocess_result
|
71
115
|
run unless @ran
|
72
116
|
if @fiber
|
73
117
|
@awaiting_fiber = Fiber.current
|
@@ -75,30 +119,36 @@ class Coprocess
|
|
75
119
|
else
|
76
120
|
@result
|
77
121
|
end
|
78
|
-
ensure
|
79
|
-
# if awaiting was interrupted and the coprocess is still running, we need to stop it
|
80
|
-
if @fiber
|
81
|
-
@fiber&.schedule(Exceptions::MoveOn.new)
|
82
|
-
suspend
|
83
|
-
end
|
84
122
|
end
|
85
|
-
alias_method :join, :await
|
86
123
|
|
87
124
|
def when_done(&block)
|
88
125
|
@when_done = block
|
89
126
|
end
|
90
127
|
|
91
128
|
def resume(value = nil)
|
92
|
-
@fiber
|
129
|
+
return unless @fiber
|
130
|
+
|
131
|
+
@fiber.schedule(value)
|
132
|
+
snooze
|
93
133
|
end
|
94
134
|
|
95
135
|
def interrupt(value = nil)
|
96
|
-
@fiber
|
136
|
+
return unless @fiber
|
137
|
+
|
138
|
+
@fiber.schedule(Exceptions::MoveOn.new(nil, value))
|
139
|
+
snooze
|
97
140
|
end
|
98
141
|
alias_method :stop, :interrupt
|
99
142
|
|
143
|
+
def transfer(value = nil)
|
144
|
+
@fiber&.schedule(value)
|
145
|
+
end
|
146
|
+
|
100
147
|
def cancel!
|
101
|
-
@fiber
|
148
|
+
return unless @fiber
|
149
|
+
|
150
|
+
@fiber.schedule(Exceptions::Cancel.new)
|
151
|
+
snooze
|
102
152
|
end
|
103
153
|
|
104
154
|
def self.current
|