polyphony 0.17 → 0.19

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