polyphony 0.17 → 0.19

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 (86) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +15 -0
  3. data/Gemfile.lock +11 -3
  4. data/README.md +18 -18
  5. data/TODO.md +5 -21
  6. data/examples/core/channel_echo.rb +3 -3
  7. data/examples/core/enumerator.rb +1 -1
  8. data/examples/core/fork.rb +1 -1
  9. data/examples/core/genserver.rb +1 -1
  10. data/examples/core/lock.rb +3 -3
  11. data/examples/core/multiple_spawn.rb +2 -2
  12. data/examples/core/nested_async.rb +1 -1
  13. data/examples/core/nested_multiple_spawn.rb +3 -3
  14. data/examples/core/resource_cancel.rb +1 -1
  15. data/examples/core/sleep_spawn.rb +2 -2
  16. data/examples/core/spawn.rb +1 -1
  17. data/examples/core/spawn_cancel.rb +1 -1
  18. data/examples/core/spawn_error.rb +4 -4
  19. data/examples/core/supervisor.rb +1 -1
  20. data/examples/core/supervisor_with_error.rb +1 -1
  21. data/examples/core/supervisor_with_manual_move_on.rb +1 -1
  22. data/examples/core/thread.rb +2 -2
  23. data/examples/core/thread_cancel.rb +2 -2
  24. data/examples/core/thread_pool.rb +1 -1
  25. data/examples/core/throttle.rb +3 -3
  26. data/examples/core/timeout.rb +10 -0
  27. data/examples/fs/read.rb +1 -1
  28. data/examples/http/http_client.rb +1 -1
  29. data/examples/http/http_get.rb +7 -0
  30. data/examples/http/http_parse_experiment.rb +118 -0
  31. data/examples/http/http_proxy.rb +81 -0
  32. data/examples/http/http_server.rb +15 -4
  33. data/examples/http/http_server_forked.rb +2 -2
  34. data/examples/http/http_server_throttled.rb +1 -1
  35. data/examples/http/http_ws_server.rb +2 -2
  36. data/examples/http/https_server.rb +5 -1
  37. data/examples/http/https_wss_server.rb +1 -1
  38. data/examples/http/rack_server_https_forked.rb +1 -1
  39. data/examples/interfaces/pg_client.rb +1 -1
  40. data/examples/interfaces/pg_pool.rb +1 -1
  41. data/examples/interfaces/redis_channels.rb +5 -5
  42. data/examples/interfaces/redis_pubsub.rb +2 -2
  43. data/examples/interfaces/redis_pubsub_perf.rb +3 -3
  44. data/examples/io/echo_client.rb +2 -2
  45. data/examples/io/echo_pipe.rb +17 -0
  46. data/examples/io/echo_server.rb +1 -1
  47. data/examples/io/echo_server_with_timeout.rb +1 -1
  48. data/examples/io/httparty.rb +10 -0
  49. data/examples/io/httparty_multi.rb +29 -0
  50. data/examples/io/httparty_threaded.rb +25 -0
  51. data/examples/io/irb.rb +15 -0
  52. data/examples/io/net-http.rb +15 -0
  53. data/examples/io/system.rb +1 -1
  54. data/examples/io/tcpsocket.rb +18 -0
  55. data/examples/performance/perf_multi_snooze.rb +2 -2
  56. data/examples/performance/perf_snooze.rb +17 -20
  57. data/examples/performance/thread-vs-fiber/polyphony_server.rb +1 -1
  58. data/ext/ev/ev.h +9 -1
  59. data/ext/ev/ev_ext.c +4 -1
  60. data/ext/ev/ev_module.c +36 -22
  61. data/ext/ev/extconf.rb +1 -1
  62. data/ext/ev/io.c +23 -23
  63. data/ext/ev/signal.c +1 -1
  64. data/ext/ev/socket.c +161 -0
  65. data/lib/polyphony/core/coprocess.rb +1 -1
  66. data/lib/polyphony/core/fiber_pool.rb +2 -2
  67. data/lib/polyphony/core/supervisor.rb +2 -18
  68. data/lib/polyphony/extensions/io.rb +19 -6
  69. data/lib/polyphony/extensions/kernel.rb +17 -5
  70. data/lib/polyphony/extensions/socket.rb +40 -1
  71. data/lib/polyphony/http/agent.rb +56 -25
  72. data/lib/polyphony/http/http1_adapter.rb +254 -0
  73. data/lib/polyphony/http/http2_adapter.rb +157 -0
  74. data/lib/polyphony/http/{http2_request.rb → request.rb} +25 -22
  75. data/lib/polyphony/http/server.rb +19 -11
  76. data/lib/polyphony/net.rb +10 -6
  77. data/lib/polyphony/version.rb +1 -1
  78. data/polyphony.gemspec +6 -5
  79. data/test/test_coprocess.rb +9 -9
  80. data/test/test_core.rb +14 -14
  81. data/test/test_io.rb +4 -4
  82. data/test/test_kernel.rb +1 -1
  83. metadata +48 -23
  84. data/lib/polyphony/http/http1.rb +0 -124
  85. data/lib/polyphony/http/http1_request.rb +0 -83
  86. data/lib/polyphony/http/http2.rb +0 -65
@@ -16,4 +16,4 @@ $defs << "-DHAVE_SYS_RESOURCE_H" if have_header("sys/resource.h")
16
16
  CONFIG["optflags"] << " -fno-strict-aliasing"
17
17
 
18
18
  dir_config "ev_ext"
19
- create_makefile "ev_ext"
19
+ create_makefile "ev_ext"
@@ -26,7 +26,6 @@ static VALUE EV_IO_initialize(VALUE self, VALUE io, VALUE event_mask);
26
26
 
27
27
  static VALUE EV_IO_start(VALUE self);
28
28
  static VALUE EV_IO_stop(VALUE self);
29
- static VALUE EV_IO_await(VALUE self);
30
29
 
31
30
  void EV_IO_callback(ev_loop *ev_loop, struct ev_io *io, int revents);
32
31
 
@@ -38,9 +37,6 @@ static VALUE IO_readpartial(int argc, VALUE *argv, VALUE io);
38
37
  static VALUE IO_write(int argc, VALUE *argv, VALUE io);
39
38
  static VALUE IO_write_chevron(VALUE io, VALUE str);
40
39
 
41
- static VALUE IO_read_watcher(VALUE self);
42
- static VALUE IO_write_watcher(VALUE self);
43
-
44
40
  void Init_EV_IO() {
45
41
  mEV = rb_define_module("EV");
46
42
  cEV_IO = rb_define_class_under(mEV, "IO", rb_cData);
@@ -164,7 +160,7 @@ static VALUE EV_IO_stop(VALUE self) {
164
160
  return self;
165
161
  }
166
162
 
167
- static VALUE EV_IO_await(VALUE self) {
163
+ VALUE EV_IO_await(VALUE self) {
168
164
  struct EV_IO *io;
169
165
  VALUE ret;
170
166
 
@@ -221,7 +217,7 @@ struct io_internal_read_struct {
221
217
  size_t capa;
222
218
  };
223
219
 
224
- static int io_setstrbuf(VALUE *str, long len) {
220
+ int io_setstrbuf(VALUE *str, long len) {
225
221
  #ifdef _WIN32
226
222
  len = (len + 1) & ~1L; /* round up for wide char */
227
223
  #endif
@@ -249,7 +245,7 @@ static void io_shrink_read_string(VALUE str, long n) {
249
245
  }
250
246
  }
251
247
 
252
- static void io_set_read_length(VALUE str, long n, int shrinkable) {
248
+ void io_set_read_length(VALUE str, long n, int shrinkable) {
253
249
  if (RSTRING_LEN(str) != n) {
254
250
  rb_str_modify(str);
255
251
  rb_str_set_len(str, n);
@@ -257,17 +253,14 @@ static void io_set_read_length(VALUE str, long n, int shrinkable) {
257
253
  }
258
254
  }
259
255
 
260
- static rb_encoding*
261
- io_read_encoding(rb_io_t *fptr)
262
- {
256
+ static rb_encoding* io_read_encoding(rb_io_t *fptr) {
263
257
  if (fptr->encs.enc) {
264
258
  return fptr->encs.enc;
265
259
  }
266
260
  return rb_default_external_encoding();
267
261
  }
268
262
 
269
- static VALUE io_enc_str(VALUE str, rb_io_t *fptr)
270
- {
263
+ VALUE io_enc_str(VALUE str, rb_io_t *fptr) {
271
264
  OBJ_TAINT(str);
272
265
  rb_enc_associate(str, io_read_encoding(fptr));
273
266
  return str;
@@ -277,6 +270,9 @@ static VALUE io_enc_str(VALUE str, rb_io_t *fptr)
277
270
  //////////////////////////////////////////////////////////////////////
278
271
 
279
272
  static VALUE IO_read(int argc, VALUE *argv, VALUE io) {
273
+ VALUE underlying_io = rb_iv_get(io, "@io");
274
+ if (!NIL_P(underlying_io)) io = underlying_io;
275
+
280
276
  long len = argc == 1 ? NUM2LONG(argv[0]) : 8192;
281
277
 
282
278
  rb_io_t *fptr;
@@ -291,7 +287,7 @@ static VALUE IO_read(int argc, VALUE *argv, VALUE io) {
291
287
  VALUE str = argc >= 2 ? argv[1] : Qnil;
292
288
 
293
289
  shrinkable = io_setstrbuf(&str, len);
294
- // OBJ_TAINT(str);
290
+ OBJ_TAINT(str);
295
291
  GetOpenFile(io, fptr);
296
292
  rb_io_check_byte_readable(fptr);
297
293
  rb_io_set_nonblock(fptr);
@@ -308,8 +304,7 @@ static VALUE IO_read(int argc, VALUE *argv, VALUE io) {
308
304
  int e = errno;
309
305
  if ((e == EWOULDBLOCK || e == EAGAIN)) {
310
306
  if (read_watcher == Qnil)
311
- // read_watcher = IO_read_watcher(io);
312
- read_watcher = rb_funcall(io, ID_read_watcher, 0);
307
+ read_watcher = IO_read_watcher(io);
313
308
  EV_IO_await(read_watcher);
314
309
  }
315
310
  else
@@ -337,6 +332,9 @@ static VALUE IO_read(int argc, VALUE *argv, VALUE io) {
337
332
  }
338
333
 
339
334
  static VALUE IO_readpartial(int argc, VALUE *argv, VALUE io) {
335
+ VALUE underlying_io = rb_iv_get(io, "@io");
336
+ if (!NIL_P(underlying_io)) io = underlying_io;
337
+
340
338
  long len = argc == 1 ? NUM2LONG(argv[0]) : 8192;
341
339
 
342
340
  rb_io_t *fptr;
@@ -363,10 +361,9 @@ static VALUE IO_readpartial(int argc, VALUE *argv, VALUE io) {
363
361
  n = read(fptr->fd, RSTRING_PTR(str), len);
364
362
  if (n < 0) {
365
363
  int e = errno;
366
- if ((e == EWOULDBLOCK || e == EAGAIN)) {
364
+ if (e == EWOULDBLOCK || e == EAGAIN) {
367
365
  if (read_watcher == Qnil)
368
- // read_watcher = IO_read_watcher(io);
369
- read_watcher = rb_funcall(io, ID_read_watcher, 0);
366
+ read_watcher = IO_read_watcher(io);
370
367
  EV_IO_await(read_watcher);
371
368
  }
372
369
  else
@@ -387,6 +384,9 @@ static VALUE IO_readpartial(int argc, VALUE *argv, VALUE io) {
387
384
  }
388
385
 
389
386
  static VALUE IO_write(int argc, VALUE *argv, VALUE io) {
387
+ VALUE underlying_io = rb_iv_get(io, "@io");
388
+ if (!NIL_P(underlying_io)) io = underlying_io;
389
+
390
390
  long i;
391
391
  long n;
392
392
  long total = 0;
@@ -413,8 +413,8 @@ static VALUE IO_write(int argc, VALUE *argv, VALUE io) {
413
413
  int e = errno;
414
414
  if (e == EWOULDBLOCK || e == EAGAIN) {
415
415
  if (write_watcher == Qnil)
416
- // write_watcher = IO_write_watcher(io);
417
- write_watcher = rb_funcall(io, ID_write_watcher, 0);
416
+ write_watcher = IO_write_watcher(io);
417
+ // write_watcher = rb_funcall(io, ID_write_watcher, 0);
418
418
  EV_IO_await(write_watcher);
419
419
  }
420
420
  else {
@@ -441,7 +441,7 @@ static VALUE IO_write_chevron(VALUE io, VALUE str) {
441
441
  return io;
442
442
  }
443
443
 
444
- static VALUE IO_read_watcher(VALUE self) {
444
+ VALUE IO_read_watcher(VALUE self) {
445
445
  VALUE watcher = rb_iv_get(self, "@read_watcher");
446
446
  if (watcher == Qnil) {
447
447
  watcher = rb_funcall(cEV_IO, rb_intern("new"), 2, self, ID2SYM(rb_intern("r")));
@@ -450,11 +450,11 @@ static VALUE IO_read_watcher(VALUE self) {
450
450
  return watcher;
451
451
  }
452
452
 
453
- static VALUE IO_write_watcher(VALUE self) {
453
+ VALUE IO_write_watcher(VALUE self) {
454
454
  VALUE watcher = rb_iv_get(self, "@write_watcher");
455
455
  if (watcher == Qnil) {
456
456
  watcher = rb_funcall(cEV_IO, rb_intern("new"), 2, self, ID2SYM(rb_intern("w")));
457
457
  rb_iv_set(self, "@write_watcher", watcher);
458
458
  }
459
459
  return watcher;
460
- }
460
+ }
@@ -116,4 +116,4 @@ static VALUE EV_Signal_stop(VALUE self) {
116
116
  }
117
117
 
118
118
  return self;
119
- }
119
+ }
@@ -0,0 +1,161 @@
1
+ #include "ev.h"
2
+ #include <sys/socket.h>
3
+
4
+ static VALUE BasicSocket_send(int argc, VALUE *argv, VALUE io);
5
+ static VALUE BasicSocket_recv(int argc, VALUE *argv, VALUE io);
6
+
7
+ void Init_Socket() {
8
+ rb_require("socket");
9
+ VALUE cBasicSocket = rb_const_get(rb_cObject, rb_intern("BasicSocket"));
10
+
11
+ rb_define_method(cBasicSocket, "send", BasicSocket_send, -1);
12
+ rb_define_method(cBasicSocket, "recv", BasicSocket_recv, -1);
13
+ }
14
+
15
+ ///////////////////////////////////////////////////////////////////////////
16
+
17
+ struct rsock_send_arg {
18
+ int fd, flags;
19
+ VALUE mesg;
20
+ struct sockaddr *to;
21
+ socklen_t tolen;
22
+ };
23
+
24
+ #define StringValue(v) rb_string_value(&(v))
25
+ #define IS_ADDRINFO(obj) rb_typeddata_is_kind_of((obj), &addrinfo_type)
26
+
27
+ VALUE
28
+ rsock_sockaddr_string_value(volatile VALUE *v)
29
+ {
30
+ // VALUE val = *v;
31
+ // if (IS_ADDRINFO(val)) {
32
+ // *v = addrinfo_to_sockaddr(val);
33
+ // }
34
+ StringValue(*v);
35
+ return *v;
36
+ }
37
+
38
+ #define SockAddrStringValue(v) rsock_sockaddr_string_value(&(v))
39
+ #define RSTRING_LENINT(str) rb_long2int(RSTRING_LEN(str))
40
+ #ifndef RSTRING_SOCKLEN
41
+ # define RSTRING_SOCKLEN (socklen_t)RSTRING_LENINT
42
+ #endif
43
+
44
+ #if defined __APPLE__
45
+ # define do_write_retry(code) do {ret = code;} while (ret == -1 && errno == EPROTOTYPE)
46
+ #else
47
+ # define do_write_retry(code) ret = code
48
+ #endif
49
+
50
+ VALUE
51
+ rsock_sendto_blocking(void *data)
52
+ {
53
+ struct rsock_send_arg *arg = data;
54
+ VALUE mesg = arg->mesg;
55
+ ssize_t ret;
56
+ do_write_retry(sendto(arg->fd, RSTRING_PTR(mesg), RSTRING_LEN(mesg),
57
+ arg->flags, arg->to, arg->tolen));
58
+ return (VALUE)ret;
59
+ }
60
+
61
+ VALUE
62
+ rsock_send_blocking(void *data)
63
+ {
64
+ struct rsock_send_arg *arg = data;
65
+ VALUE mesg = arg->mesg;
66
+ ssize_t ret;
67
+ do_write_retry(send(arg->fd, RSTRING_PTR(mesg), RSTRING_LEN(mesg),
68
+ arg->flags));
69
+ return (VALUE)ret;
70
+ }
71
+
72
+ ///////////////////////////////////////////////////////////////////////////
73
+
74
+ static VALUE BasicSocket_send(int argc, VALUE *argv, VALUE sock) {
75
+ VALUE underlying_socket = rb_iv_get(sock, "@socket");
76
+ if (!NIL_P(underlying_socket)) sock = underlying_socket;
77
+ struct rsock_send_arg arg;
78
+ VALUE flags, to;
79
+ rb_io_t *fptr;
80
+ ssize_t n;
81
+ rb_blocking_function_t *func;
82
+ const char *funcname;
83
+ VALUE write_watcher = Qnil;
84
+
85
+ rb_scan_args(argc, argv, "21", &arg.mesg, &flags, &to);
86
+
87
+ StringValue(arg.mesg);
88
+ if (!NIL_P(to)) {
89
+ SockAddrStringValue(to);
90
+ to = rb_str_new4(to);
91
+ arg.to = (struct sockaddr *)RSTRING_PTR(to);
92
+ arg.tolen = RSTRING_SOCKLEN(to);
93
+ func = rsock_sendto_blocking;
94
+ funcname = "sendto(2)";
95
+ }
96
+ else {
97
+ func = rsock_send_blocking;
98
+ funcname = "send(2)";
99
+ }
100
+ GetOpenFile(sock, fptr);
101
+ rb_io_set_nonblock(fptr);
102
+ arg.fd = fptr->fd;
103
+ arg.flags = NUM2INT(flags);
104
+ while ((n = (ssize_t)func(&arg)) < 0) {
105
+ if (write_watcher == Qnil)
106
+ write_watcher = IO_write_watcher(sock);
107
+ EV_IO_await(write_watcher);
108
+ }
109
+ return SSIZET2NUM(n);
110
+ }
111
+
112
+ static VALUE BasicSocket_recv(int argc, VALUE *argv, VALUE sock) {
113
+ VALUE underlying_socket = rb_iv_get(sock, "@socket");
114
+ if (!NIL_P(underlying_socket)) sock = underlying_socket;
115
+ long len = argc >= 1 ? NUM2LONG(argv[0]) : 8192;
116
+ if (len < 0) {
117
+ rb_raise(rb_eArgError, "negative length %ld given", len);
118
+ }
119
+
120
+ rb_io_t *fptr;
121
+ long n;
122
+ int shrinkable;
123
+ VALUE read_watcher = Qnil;
124
+
125
+
126
+ VALUE str = argc >= 3 ? argv[2] : Qnil;
127
+
128
+ shrinkable = io_setstrbuf(&str, len);
129
+ OBJ_TAINT(str);
130
+ GetOpenFile(sock, fptr);
131
+ // rb_io_set_nonblock(fptr);
132
+ rb_io_check_byte_readable(fptr);
133
+
134
+ if (len == 0)
135
+ return str;
136
+
137
+ while (1) {
138
+ n = recv(fptr->fd, RSTRING_PTR(str), len, MSG_DONTWAIT);
139
+ if (n < 0) {
140
+ int e = errno;
141
+ if (e == EWOULDBLOCK || e == EAGAIN) {
142
+ if (read_watcher == Qnil)
143
+ read_watcher = IO_read_watcher(sock);
144
+ EV_IO_await(read_watcher);
145
+ }
146
+ else
147
+ rb_syserr_fail(e, strerror(e));
148
+ // rb_syserr_fail_path(e, fptr->pathv);
149
+ }
150
+ else
151
+ break;
152
+ }
153
+
154
+ io_set_read_length(str, n, shrinkable);
155
+ io_enc_str(str, fptr);
156
+
157
+ if (n == 0)
158
+ return Qnil;
159
+
160
+ return str;
161
+ }
@@ -20,7 +20,7 @@ class Coprocess
20
20
  def run(&block2)
21
21
  @caller = caller if Exceptions.debug
22
22
 
23
- @fiber = FiberPool.spawn do
23
+ @fiber = FiberPool.run do
24
24
  @fiber.coprocess = self
25
25
  @result = (@block || block2).call(self)
26
26
  rescue Exceptions::MoveOn, Exceptions::Stop => e
@@ -4,7 +4,7 @@ export :available,
4
4
  :checked_out,
5
5
  :reset!,
6
6
  :size,
7
- :spawn
7
+ :run
8
8
 
9
9
  require 'fiber'
10
10
 
@@ -48,7 +48,7 @@ EV.unref
48
48
  # Invokes the given block using a fiber taken from the fiber pool. If the pool
49
49
  # is exhausted, a new fiber will be created.
50
50
  # @return [Fiber]
51
- def spawn(&block)
51
+ def run(&block)
52
52
  fiber = @pool.empty? ? new_fiber : @pool.shift
53
53
  fiber.next_job = block
54
54
  fiber
@@ -25,30 +25,14 @@ class Supervisor
25
25
  end
26
26
  end
27
27
 
28
- def spawn(proc = nil, &block)
29
- if proc.is_a?(Coprocess)
30
- spawn_coprocess(proc)
31
- else
32
- spawn_proc(block || proc)
33
- end
34
- end
35
-
36
- def spawn_coprocess(proc)
28
+ def spin(proc = nil, &block)
29
+ proc = Coprocess.new(&(proc || block)) unless proc.is_a?(Coprocess)
37
30
  @coprocesses << proc
38
31
  proc.when_done { task_completed(proc) }
39
32
  proc.run unless proc.running?
40
33
  proc
41
34
  end
42
35
 
43
- def spawn_proc(proc)
44
- @coprocesses << coproc do |coprocess|
45
- proc.call(coprocess)
46
- task_completed(coprocess)
47
- rescue Exception => e
48
- task_completed(coprocess)
49
- end
50
- end
51
-
52
36
  def still_running?
53
37
  !@coprocesses.empty?
54
38
  end
@@ -30,18 +30,20 @@ class ::IO
30
30
 
31
31
  alias_method :orig_read, :read
32
32
  def read(name, length = nil, offset = nil, opt = EMPTY_HASH)
33
+ opt, length = length, nil if length.is_a?(Hash)
33
34
  File.open(name, opt[:mode] || 'r') do |f|
34
35
  f.seek(offset) if offset
35
36
  length ? f.read(length) : f.read
36
37
  end
37
38
  end
38
39
 
39
- alias_method :orig_readlines, :readlines
40
- def readlines(name, sep = $/, limit = nil, getline_args = EMPTY_HASH)
41
- File.open(name, 'r') do |f|
42
- f.readlines(sep, limit, getline_args)
43
- end
44
- end
40
+ # alias_method :orig_readlines, :readlines
41
+ # def readlines(name, sep = $/, limit = nil, getline_args = EMPTY_HASH)
42
+ # File.open(name, 'r') do |f|
43
+ # puts "readlines(#{sep.inspect}, #{limit.inspect}, #{getline_args.inspect}"
44
+ # f.readlines(sep, limit, getline_args)
45
+ # end
46
+ # end
45
47
 
46
48
  alias_method :orig_write, :write
47
49
  def write(name, string, offset = nil, opt = EMPTY_HASH)
@@ -141,4 +143,15 @@ class ::IO
141
143
 
142
144
  # def readlines(sep = $/, limit = nil, chomp: nil)
143
145
  # end
146
+
147
+ def write_nonblock(string, options = {})
148
+ # STDOUT << '>'
149
+ write(string, 0)
150
+ end
151
+
152
+ def read_nonblock(maxlen, buf = nil, options = nil)
153
+ # STDOUT << '<'
154
+ buf ? readpartial(maxlen, buf) : readpartial(maxlen)
155
+ end
156
+
144
157
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'fiber'
4
+ require 'timeout'
4
5
 
5
6
  CancelScope = import('../core/cancel_scope')
6
7
  Coprocess = import('../core/coprocess')
@@ -56,7 +57,7 @@ end
56
57
 
57
58
  module ::Process
58
59
  def self.detach(pid)
59
- coproc {
60
+ spin {
60
61
  EV::Child.new(pid).await
61
62
  }
62
63
  end
@@ -93,7 +94,7 @@ module ::Kernel
93
94
  CancelScope.new(timeout: duration, mode: :cancel).(&block)
94
95
  end
95
96
 
96
- def coproc(proc = nil, &block)
97
+ def spin(proc = nil, &block)
97
98
  if proc.is_a?(Coprocess)
98
99
  proc.run
99
100
  else
@@ -139,10 +140,12 @@ module ::Kernel
139
140
  nil
140
141
  end
141
142
 
142
- def throttled_loop(rate, &block)
143
+ def throttled_loop(rate, count: nil, &block)
143
144
  throttler = Throttler.new(rate)
144
- loop do
145
- throttler.(&block)
145
+ if count
146
+ count.times { throttler.(&block) }
147
+ else
148
+ loop { throttler.(&block) }
146
149
  end
147
150
  end
148
151
 
@@ -155,3 +158,12 @@ module ::Kernel
155
158
  end
156
159
  end
157
160
 
161
+ module ::Timeout
162
+ def self.timeout(sec, klass = nil, message = nil, &block)
163
+ cancel_after(sec, &block)
164
+ rescue Exceptions::Cancel => e
165
+ error = klass ? klass.new(message) : ::Timeout::Error.new
166
+ error.set_backtrace(e.backtrace)
167
+ raise error
168
+ end
169
+ end