polyphony 0.19 → 0.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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