polyphony 0.19 → 0.20

Sign up to get free protection for your applications and to get access to all the features.
Files changed (186) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/.rubocop.yml +87 -1
  4. data/CHANGELOG.md +35 -0
  5. data/Gemfile.lock +17 -6
  6. data/README.md +200 -139
  7. data/Rakefile +4 -4
  8. data/TODO.md +35 -7
  9. data/bin/poly +11 -0
  10. data/docs/getting-started/getting-started.md +1 -1
  11. data/docs/summary.md +3 -0
  12. data/docs/technical-overview/exception-handling.md +94 -0
  13. data/docs/technical-overview/fiber-scheduling.md +99 -0
  14. data/examples/core/cancel.rb +8 -4
  15. data/examples/core/channel_echo.rb +18 -17
  16. data/examples/core/defer.rb +12 -0
  17. data/examples/core/enumerator.rb +4 -4
  18. data/examples/core/fiber_error.rb +9 -0
  19. data/examples/core/fiber_error_with_backtrace.rb +73 -0
  20. data/examples/core/fork.rb +6 -6
  21. data/examples/core/genserver.rb +16 -8
  22. data/examples/core/lock.rb +3 -3
  23. data/examples/core/move_on.rb +4 -3
  24. data/examples/core/move_on_twice.rb +5 -5
  25. data/examples/core/move_on_with_ensure.rb +8 -11
  26. data/examples/core/move_on_with_value.rb +14 -0
  27. data/examples/core/{multiple_spawn.rb → multiple_spin.rb} +5 -5
  28. data/examples/core/nested_cancel.rb +5 -5
  29. data/examples/core/{nested_multiple_spawn.rb → nested_multiple_spin.rb} +6 -6
  30. data/examples/core/nested_spin.rb +17 -0
  31. data/examples/core/pingpong.rb +21 -0
  32. data/examples/core/pulse.rb +4 -5
  33. data/examples/core/resource.rb +6 -4
  34. data/examples/core/resource_cancel.rb +6 -9
  35. data/examples/core/resource_delegate.rb +3 -3
  36. data/examples/core/sleep.rb +3 -3
  37. data/examples/core/sleep_spin.rb +19 -0
  38. data/examples/core/snooze.rb +32 -0
  39. data/examples/core/spin.rb +14 -0
  40. data/examples/core/{spawn_cancel.rb → spin_cancel.rb} +6 -7
  41. data/examples/core/spin_error.rb +17 -0
  42. data/examples/core/spin_error_backtrace.rb +30 -0
  43. data/examples/core/spin_uncaught_error.rb +15 -0
  44. data/examples/core/supervisor.rb +8 -8
  45. data/examples/core/supervisor_with_cancel_scope.rb +7 -7
  46. data/examples/core/supervisor_with_error.rb +8 -8
  47. data/examples/core/supervisor_with_manual_move_on.rb +6 -7
  48. data/examples/core/suspend.rb +13 -0
  49. data/examples/core/thread.rb +1 -1
  50. data/examples/core/thread_cancel.rb +9 -11
  51. data/examples/core/thread_pool.rb +18 -14
  52. data/examples/core/throttle.rb +7 -7
  53. data/examples/core/timeout.rb +3 -3
  54. data/examples/fs/read.rb +7 -9
  55. data/examples/http/config.ru +7 -3
  56. data/examples/http/cuba.ru +22 -0
  57. data/examples/http/happy_eyeballs.rb +6 -4
  58. data/examples/http/http_client.rb +1 -1
  59. data/examples/http/http_get.rb +1 -1
  60. data/examples/http/http_parse_experiment.rb +21 -16
  61. data/examples/http/http_proxy.rb +28 -26
  62. data/examples/http/http_server.rb +10 -10
  63. data/examples/http/http_server_forked.rb +6 -5
  64. data/examples/http/http_server_throttled.rb +3 -3
  65. data/examples/http/http_ws_server.rb +11 -11
  66. data/examples/http/https_raw_client.rb +1 -1
  67. data/examples/http/https_server.rb +8 -8
  68. data/examples/http/https_wss_server.rb +13 -11
  69. data/examples/http/rack_server.rb +2 -2
  70. data/examples/http/rack_server_https.rb +4 -4
  71. data/examples/http/rack_server_https_forked.rb +5 -5
  72. data/examples/http/websocket_secure_server.rb +6 -6
  73. data/examples/http/websocket_server.rb +5 -5
  74. data/examples/interfaces/pg_client.rb +4 -4
  75. data/examples/interfaces/pg_pool.rb +13 -6
  76. data/examples/interfaces/pg_transaction.rb +5 -4
  77. data/examples/interfaces/redis_channels.rb +15 -11
  78. data/examples/interfaces/redis_client.rb +2 -2
  79. data/examples/interfaces/redis_pubsub.rb +2 -1
  80. data/examples/interfaces/redis_pubsub_perf.rb +13 -9
  81. data/examples/io/backticks.rb +11 -0
  82. data/examples/io/cat.rb +4 -5
  83. data/examples/io/echo_client.rb +9 -4
  84. data/examples/io/echo_client_from_stdin.rb +20 -0
  85. data/examples/io/echo_pipe.rb +7 -8
  86. data/examples/io/echo_server.rb +8 -6
  87. data/examples/io/echo_server_with_timeout.rb +13 -10
  88. data/examples/io/echo_stdin.rb +3 -3
  89. data/examples/io/httparty.rb +2 -2
  90. data/examples/io/httparty_multi.rb +8 -4
  91. data/examples/io/httparty_threaded.rb +6 -2
  92. data/examples/io/io_read.rb +2 -2
  93. data/examples/io/irb.rb +16 -4
  94. data/examples/io/net-http.rb +3 -3
  95. data/examples/io/open.rb +17 -0
  96. data/examples/io/system.rb +3 -3
  97. data/examples/io/tcpserver.rb +15 -0
  98. data/examples/io/tcpsocket.rb +6 -5
  99. data/examples/performance/multi_snooze.rb +29 -0
  100. data/examples/performance/{perf_snooze.rb → snooze.rb} +7 -5
  101. data/examples/performance/snooze_raw.rb +39 -0
  102. data/ext/gyro/async.c +165 -0
  103. data/ext/gyro/child.c +167 -0
  104. data/ext/{ev → gyro}/extconf.rb +4 -3
  105. data/ext/gyro/gyro.c +316 -0
  106. data/ext/{ev/ev.h → gyro/gyro.h} +12 -7
  107. data/ext/gyro/gyro_ext.c +23 -0
  108. data/ext/{ev → gyro}/io.c +65 -57
  109. data/ext/{ev → gyro}/libev.h +0 -0
  110. data/ext/gyro/signal.c +117 -0
  111. data/ext/{ev → gyro}/socket.c +61 -6
  112. data/ext/gyro/timer.c +199 -0
  113. data/ext/libev/Changes +35 -0
  114. data/ext/libev/README +2 -1
  115. data/ext/libev/ev.c +213 -151
  116. data/ext/libev/ev.h +95 -88
  117. data/ext/libev/ev_epoll.c +26 -15
  118. data/ext/libev/ev_kqueue.c +11 -5
  119. data/ext/libev/ev_linuxaio.c +642 -0
  120. data/ext/libev/ev_poll.c +13 -8
  121. data/ext/libev/ev_port.c +5 -2
  122. data/ext/libev/ev_vars.h +14 -3
  123. data/ext/libev/ev_wrap.h +16 -0
  124. data/lib/ev_ext.bundle +0 -0
  125. data/lib/polyphony.rb +46 -50
  126. data/lib/polyphony/auto_run.rb +12 -0
  127. data/lib/polyphony/core/cancel_scope.rb +11 -7
  128. data/lib/polyphony/core/channel.rb +16 -9
  129. data/lib/polyphony/core/coprocess.rb +101 -51
  130. data/lib/polyphony/core/exceptions.rb +14 -12
  131. data/lib/polyphony/core/resource_pool.rb +21 -8
  132. data/lib/polyphony/core/supervisor.rb +10 -5
  133. data/lib/polyphony/core/sync.rb +7 -6
  134. data/lib/polyphony/core/thread.rb +4 -4
  135. data/lib/polyphony/core/thread_pool.rb +4 -4
  136. data/lib/polyphony/core/throttler.rb +6 -4
  137. data/lib/polyphony/extensions/core.rb +253 -0
  138. data/lib/polyphony/extensions/io.rb +28 -16
  139. data/lib/polyphony/extensions/openssl.rb +2 -1
  140. data/lib/polyphony/extensions/socket.rb +47 -52
  141. data/lib/polyphony/http.rb +4 -3
  142. data/lib/polyphony/http/agent.rb +68 -57
  143. data/lib/polyphony/http/server.rb +5 -5
  144. data/lib/polyphony/http/server/http1.rb +268 -0
  145. data/lib/polyphony/http/server/http2.rb +62 -0
  146. data/lib/polyphony/http/server/http2_stream.rb +104 -0
  147. data/lib/polyphony/http/server/rack.rb +64 -0
  148. data/lib/polyphony/http/server/request.rb +119 -0
  149. data/lib/polyphony/net.rb +26 -15
  150. data/lib/polyphony/postgres.rb +17 -13
  151. data/lib/polyphony/redis.rb +16 -15
  152. data/lib/polyphony/version.rb +1 -1
  153. data/lib/polyphony/websocket.rb +11 -4
  154. data/polyphony.gemspec +13 -9
  155. data/test/eg.rb +27 -0
  156. data/test/helper.rb +25 -0
  157. data/test/run.rb +5 -0
  158. data/test/test_async.rb +33 -0
  159. data/test/test_coprocess.rb +239 -77
  160. data/test/test_core.rb +95 -61
  161. data/test/test_gyro.rb +148 -0
  162. data/test/test_http_server.rb +313 -0
  163. data/test/test_io.rb +79 -27
  164. data/test/test_kernel.rb +22 -12
  165. data/test/test_signal.rb +36 -0
  166. data/test/test_timer.rb +24 -0
  167. metadata +89 -33
  168. data/examples/core/nested_async.rb +0 -17
  169. data/examples/core/next_tick.rb +0 -12
  170. data/examples/core/sleep_spawn.rb +0 -19
  171. data/examples/core/spawn.rb +0 -14
  172. data/examples/core/spawn_error.rb +0 -28
  173. data/examples/performance/perf_multi_snooze.rb +0 -21
  174. data/ext/ev/async.c +0 -168
  175. data/ext/ev/child.c +0 -169
  176. data/ext/ev/ev_ext.c +0 -23
  177. data/ext/ev/ev_module.c +0 -242
  178. data/ext/ev/signal.c +0 -119
  179. data/ext/ev/timer.c +0 -197
  180. data/lib/polyphony/core/fiber_pool.rb +0 -98
  181. data/lib/polyphony/extensions/kernel.rb +0 -169
  182. data/lib/polyphony/http/http1_adapter.rb +0 -254
  183. data/lib/polyphony/http/http2_adapter.rb +0 -157
  184. data/lib/polyphony/http/rack.rb +0 -25
  185. data/lib/polyphony/http/request.rb +0 -66
  186. 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
- pollidx_init (int *base, int count)
44
+ array_needsize_pollidx (int *base, int offset, int count)
45
45
  {
46
- /* consider using memset (.., -1, ...), which is practically guaranteed
47
- * to work on all systems implementing poll */
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, pollidx_init);
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, EMPTY2);
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() returned illegal result, broken BSD kernel?", p < polls + pollcnt));
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
- fd_kill (EV_A_ p->fd);
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
- fd_kill (EV_A_ fd);
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) EV_THROW)
199
- VAR (acquire_cb, void (*acquire_cb)(EV_P) EV_THROW)
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 './ev_ext'
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('./polyphony/core/exceptions')
15
+ exceptions = import './polyphony/core/exceptions'
14
16
  Cancel = exceptions::Cancel
15
17
  MoveOn = exceptions::MoveOn
16
18
 
17
- Coprocess = import('./polyphony/core/coprocess')
18
- FiberPool = import('./polyphony/core/fiber_pool')
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
- at_exit do
72
- # in most cases, by the main fiber is done there are still pending or other
73
- # or asynchronous operations going on. If the reactor loop is not done, we
74
- # suspend the root fiber until it is done
75
- suspend if $__reactor_fiber__&.alive?
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
- @error_class = @opts[:mode] == :cancel ? Exceptions::Cancel : Exceptions::MoveOn
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 @error_class.new(self, @opts[:value])
23
+ @fiber.transfer error_class.new(self, @opts[:value])
20
24
  end
21
25
 
22
26
  def start_timeout
23
- @timeout = EV::Timer.new(@opts[:timeout], 0)
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(&@when_cancelled) if @cancelled && @when_cancelled
48
+ protect(&@on_cancel) if @cancelled && @on_cancel
45
49
  end
46
50
 
47
- def when_cancelled(&block)
48
- @when_cancelled = block
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 <<(o)
20
+ def <<(value)
19
21
  if @waiting_queue.empty?
20
- @payload_queue << o
22
+ @payload_queue << value
21
23
  else
22
- @waiting_queue.shift&.schedule(o)
24
+ @waiting_queue.shift&.schedule(value)
23
25
  end
24
26
  snooze
25
27
  end
26
28
 
27
29
  def receive
28
- EV.ref
30
+ Gyro.ref
29
31
  if @payload_queue.empty?
30
32
  @waiting_queue << Fiber.current
33
+ suspend
31
34
  else
32
- payload = @payload_queue.shift
33
- Fiber.current.schedule(payload)
35
+ receive_from_queue
34
36
  end
35
- suspend
36
37
  ensure
37
- EV.unref
38
+ Gyro.unref
38
39
  end
39
- end
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('../extensions/kernel')
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
- attr_reader :result, :fiber
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(&block2)
21
- @caller = caller if Exceptions.debug
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
- @ran = true
64
+ @fiber = Fiber.new { execute }
42
65
  @fiber.schedule
66
+ @ran = true
43
67
  self
44
68
  end
45
69
 
46
- def <<(o)
47
- @mailbox ||= []
48
- @mailbox << o
49
- @fiber&.schedule if @receive_waiting
50
- snooze
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 receive
54
- EV.ref
55
- @receive_waiting = true
56
- @fiber&.schedule if @mailbox && @mailbox.size > 0
57
- suspend
58
- @mailbox.shift
59
- ensure
60
- EV.unref
61
- @receive_waiting = nil
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 running?
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&.schedule(value)
129
+ return unless @fiber
130
+
131
+ @fiber.schedule(value)
132
+ snooze
93
133
  end
94
134
 
95
135
  def interrupt(value = nil)
96
- @fiber&.schedule(Exceptions::MoveOn.new(nil, value))
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&.schedule(Exceptions::Cancel.new)
148
+ return unless @fiber
149
+
150
+ @fiber.schedule(Exceptions::Cancel.new)
151
+ snooze
102
152
  end
103
153
 
104
154
  def self.current