polyphony 0.47.2 → 0.48.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +317 -293
  3. data/Gemfile.lock +1 -1
  4. data/TODO.md +46 -3
  5. data/examples/core/supervisor.rb +3 -3
  6. data/examples/core/worker-thread.rb +3 -4
  7. data/examples/io/tcp_proxy.rb +32 -0
  8. data/examples/io/unix_socket.rb +26 -0
  9. data/examples/performance/line_splitting.rb +34 -0
  10. data/examples/performance/loop.rb +32 -0
  11. data/examples/performance/thread-vs-fiber/polyphony_server.rb +6 -0
  12. data/ext/polyphony/backend_common.h +2 -2
  13. data/ext/polyphony/backend_io_uring.c +42 -83
  14. data/ext/polyphony/backend_libev.c +32 -77
  15. data/ext/polyphony/event.c +1 -1
  16. data/ext/polyphony/polyphony.c +0 -2
  17. data/ext/polyphony/polyphony.h +5 -4
  18. data/ext/polyphony/queue.c +2 -2
  19. data/ext/polyphony/thread.c +9 -28
  20. data/lib/polyphony/adapters/postgres.rb +3 -3
  21. data/lib/polyphony/core/global_api.rb +19 -16
  22. data/lib/polyphony/core/resource_pool.rb +1 -12
  23. data/lib/polyphony/core/thread_pool.rb +3 -1
  24. data/lib/polyphony/core/throttler.rb +1 -1
  25. data/lib/polyphony/extensions/fiber.rb +34 -8
  26. data/lib/polyphony/extensions/io.rb +8 -14
  27. data/lib/polyphony/extensions/openssl.rb +4 -4
  28. data/lib/polyphony/extensions/socket.rb +68 -16
  29. data/lib/polyphony/version.rb +1 -1
  30. data/polyphony.gemspec +1 -1
  31. data/test/helper.rb +1 -0
  32. data/test/test_backend.rb +1 -1
  33. data/test/test_fiber.rb +64 -0
  34. data/test/test_global_api.rb +41 -1
  35. data/test/test_io.rb +26 -0
  36. data/test/test_resource_pool.rb +0 -21
  37. data/test/test_signal.rb +18 -0
  38. data/test/test_socket.rb +27 -2
  39. data/test/test_supervise.rb +2 -1
  40. metadata +7 -4
  41. data/ext/polyphony/backend.h +0 -26
@@ -13,7 +13,6 @@
13
13
  #include "../libev/ev.h"
14
14
  #include "ruby/io.h"
15
15
 
16
- VALUE cTCPSocket;
17
16
  VALUE SYM_libev;
18
17
 
19
18
  ID ID_ivar_is_nonblocking;
@@ -41,11 +40,14 @@ inline void io_set_nonblock(rb_io_t *fptr, VALUE io) {
41
40
  }
42
41
 
43
42
  typedef struct Backend_t {
43
+ // common fields
44
+ unsigned int currently_polling;
45
+ unsigned int pending_count;
46
+ unsigned int poll_no_wait_count;
47
+
48
+ // implementation-specific fields
44
49
  struct ev_loop *ev_loop;
45
50
  struct ev_async break_async;
46
- int running;
47
- int ref_count;
48
- int run_no_wait_count;
49
51
  } Backend_t;
50
52
 
51
53
  static size_t Backend_size(const void *ptr) {
@@ -84,9 +86,9 @@ static VALUE Backend_initialize(VALUE self) {
84
86
  ev_async_start(backend->ev_loop, &backend->break_async);
85
87
  ev_unref(backend->ev_loop); // don't count the break_async watcher
86
88
 
87
- backend->running = 0;
88
- backend->ref_count = 0;
89
- backend->run_no_wait_count = 0;
89
+ backend->currently_polling = 0;
90
+ backend->pending_count = 0;
91
+ backend->poll_no_wait_count = 0;
90
92
 
91
93
  return Qnil;
92
94
  }
@@ -117,42 +119,11 @@ VALUE Backend_post_fork(VALUE self) {
117
119
  return self;
118
120
  }
119
121
 
120
- VALUE Backend_ref(VALUE self) {
122
+ unsigned int Backend_pending_count(VALUE self) {
121
123
  Backend_t *backend;
122
124
  GetBackend(self, backend);
123
125
 
124
- backend->ref_count++;
125
- return self;
126
- }
127
-
128
- VALUE Backend_unref(VALUE self) {
129
- Backend_t *backend;
130
- GetBackend(self, backend);
131
-
132
- backend->ref_count--;
133
- return self;
134
- }
135
-
136
- int Backend_ref_count(VALUE self) {
137
- Backend_t *backend;
138
- GetBackend(self, backend);
139
-
140
- return backend->ref_count;
141
- }
142
-
143
- void Backend_reset_ref_count(VALUE self) {
144
- Backend_t *backend;
145
- GetBackend(self, backend);
146
-
147
- backend->ref_count = 0;
148
- }
149
-
150
- VALUE Backend_pending_count(VALUE self) {
151
- int count;
152
- Backend_t *backend;
153
- GetBackend(self, backend);
154
- count = ev_pending_count(backend->ev_loop);
155
- return INT2NUM(count);
126
+ return backend->pending_count;
156
127
  }
157
128
 
158
129
  VALUE Backend_poll(VALUE self, VALUE nowait, VALUE current_fiber, VALUE runqueue) {
@@ -161,19 +132,19 @@ VALUE Backend_poll(VALUE self, VALUE nowait, VALUE current_fiber, VALUE runqueue
161
132
  GetBackend(self, backend);
162
133
 
163
134
  if (is_nowait) {
164
- backend->run_no_wait_count++;
165
- if (backend->run_no_wait_count < 10) return self;
135
+ backend->poll_no_wait_count++;
136
+ if (backend->poll_no_wait_count < 10) return self;
166
137
 
167
138
  long runnable_count = Runqueue_len(runqueue);
168
- if (backend->run_no_wait_count < runnable_count) return self;
139
+ if (backend->poll_no_wait_count < runnable_count) return self;
169
140
  }
170
141
 
171
- backend->run_no_wait_count = 0;
142
+ backend->poll_no_wait_count = 0;
172
143
 
173
144
  COND_TRACE(2, SYM_fiber_event_poll_enter, current_fiber);
174
- backend->running = 1;
145
+ backend->currently_polling = 1;
175
146
  ev_run(backend->ev_loop, is_nowait ? EVRUN_NOWAIT : EVRUN_ONCE);
176
- backend->running = 0;
147
+ backend->currently_polling = 0;
177
148
  COND_TRACE(2, SYM_fiber_event_poll_leave, current_fiber);
178
149
 
179
150
  return self;
@@ -183,7 +154,7 @@ VALUE Backend_wakeup(VALUE self) {
183
154
  Backend_t *backend;
184
155
  GetBackend(self, backend);
185
156
 
186
- if (backend->running) {
157
+ if (backend->currently_polling) {
187
158
  // Since the loop will run until at least one event has occurred, we signal
188
159
  // the selector's associated async watcher, which will cause the ev loop to
189
160
  // return. In contrast to using `ev_break` to break out of the loop, which
@@ -494,7 +465,7 @@ VALUE Backend_write_m(int argc, VALUE *argv, VALUE self) {
494
465
  Backend_writev(self, argv[0], argc - 1, argv + 1);
495
466
  }
496
467
 
497
- VALUE Backend_accept(VALUE self, VALUE sock) {
468
+ VALUE Backend_accept(VALUE self, VALUE server_socket, VALUE socket_class) {
498
469
  Backend_t *backend;
499
470
  struct libev_io watcher;
500
471
  rb_io_t *fptr;
@@ -502,12 +473,12 @@ VALUE Backend_accept(VALUE self, VALUE sock) {
502
473
  struct sockaddr addr;
503
474
  socklen_t len = (socklen_t)sizeof addr;
504
475
  VALUE switchpoint_result = Qnil;
505
- VALUE underlying_sock = rb_ivar_get(sock, ID_ivar_io);
506
- if (underlying_sock != Qnil) sock = underlying_sock;
476
+ VALUE underlying_sock = rb_ivar_get(server_socket, ID_ivar_io);
477
+ if (underlying_sock != Qnil) server_socket = underlying_sock;
507
478
 
508
479
  GetBackend(self, backend);
509
- GetOpenFile(sock, fptr);
510
- io_set_nonblock(fptr, sock);
480
+ GetOpenFile(server_socket, fptr);
481
+ io_set_nonblock(fptr, server_socket);
511
482
  watcher.fiber = Qnil;
512
483
  while (1) {
513
484
  fd = accept(fptr->fd, &addr, &len);
@@ -529,7 +500,7 @@ VALUE Backend_accept(VALUE self, VALUE sock) {
529
500
  goto error;
530
501
  }
531
502
 
532
- socket = rb_obj_alloc(cTCPSocket);
503
+ socket = rb_obj_alloc(socket_class);
533
504
  MakeOpenFile(socket, fp);
534
505
  rb_update_max_fd(fd);
535
506
  fp->fd = fd;
@@ -550,7 +521,7 @@ error:
550
521
  return RAISE_EXCEPTION(switchpoint_result);
551
522
  }
552
523
 
553
- VALUE Backend_accept_loop(VALUE self, VALUE sock) {
524
+ VALUE Backend_accept_loop(VALUE self, VALUE server_socket, VALUE socket_class) {
554
525
  Backend_t *backend;
555
526
  struct libev_io watcher;
556
527
  rb_io_t *fptr;
@@ -559,12 +530,12 @@ VALUE Backend_accept_loop(VALUE self, VALUE sock) {
559
530
  socklen_t len = (socklen_t)sizeof addr;
560
531
  VALUE switchpoint_result = Qnil;
561
532
  VALUE socket = Qnil;
562
- VALUE underlying_sock = rb_ivar_get(sock, ID_ivar_io);
563
- if (underlying_sock != Qnil) sock = underlying_sock;
533
+ VALUE underlying_sock = rb_ivar_get(server_socket, ID_ivar_io);
534
+ if (underlying_sock != Qnil) server_socket = underlying_sock;
564
535
 
565
536
  GetBackend(self, backend);
566
- GetOpenFile(sock, fptr);
567
- io_set_nonblock(fptr, sock);
537
+ GetOpenFile(server_socket, fptr);
538
+ io_set_nonblock(fptr, server_socket);
568
539
  watcher.fiber = Qnil;
569
540
 
570
541
  while (1) {
@@ -586,7 +557,7 @@ VALUE Backend_accept_loop(VALUE self, VALUE sock) {
586
557
  goto error;
587
558
  }
588
559
 
589
- socket = rb_obj_alloc(cTCPSocket);
560
+ socket = rb_obj_alloc(socket_class);
590
561
  MakeOpenFile(socket, fp);
591
562
  rb_update_max_fd(fd);
592
563
  fp->fd = fd;
@@ -849,19 +820,12 @@ VALUE Backend_kind(VALUE self) {
849
820
  void Init_Backend() {
850
821
  ev_set_allocator(xrealloc);
851
822
 
852
- rb_require("socket");
853
- cTCPSocket = rb_const_get(rb_cObject, rb_intern("TCPSocket"));
854
-
855
823
  VALUE cBackend = rb_define_class_under(mPolyphony, "Backend", rb_cData);
856
824
  rb_define_alloc_func(cBackend, Backend_allocate);
857
825
 
858
826
  rb_define_method(cBackend, "initialize", Backend_initialize, 0);
859
827
  rb_define_method(cBackend, "finalize", Backend_finalize, 0);
860
828
  rb_define_method(cBackend, "post_fork", Backend_post_fork, 0);
861
- rb_define_method(cBackend, "pending_count", Backend_pending_count, 0);
862
-
863
- rb_define_method(cBackend, "ref", Backend_ref, 0);
864
- rb_define_method(cBackend, "unref", Backend_unref, 0);
865
829
 
866
830
  rb_define_method(cBackend, "poll", Backend_poll, 3);
867
831
  rb_define_method(cBackend, "break", Backend_wakeup, 0);
@@ -869,8 +833,8 @@ void Init_Backend() {
869
833
  rb_define_method(cBackend, "read", Backend_read, 4);
870
834
  rb_define_method(cBackend, "read_loop", Backend_read_loop, 1);
871
835
  rb_define_method(cBackend, "write", Backend_write_m, -1);
872
- rb_define_method(cBackend, "accept", Backend_accept, 1);
873
- rb_define_method(cBackend, "accept_loop", Backend_accept_loop, 1);
836
+ rb_define_method(cBackend, "accept", Backend_accept, 2);
837
+ rb_define_method(cBackend, "accept_loop", Backend_accept_loop, 2);
874
838
  rb_define_method(cBackend, "connect", Backend_connect, 3);
875
839
  rb_define_method(cBackend, "recv", Backend_recv, 3);
876
840
  rb_define_method(cBackend, "recv_loop", Backend_read_loop, 1);
@@ -886,15 +850,6 @@ void Init_Backend() {
886
850
 
887
851
  ID_ivar_is_nonblocking = rb_intern("@is_nonblocking");
888
852
  SYM_libev = ID2SYM(rb_intern("libev"));
889
-
890
- __BACKEND__.pending_count = Backend_pending_count;
891
- __BACKEND__.poll = Backend_poll;
892
- __BACKEND__.ref = Backend_ref;
893
- __BACKEND__.ref_count = Backend_ref_count;
894
- __BACKEND__.reset_ref_count = Backend_reset_ref_count;
895
- __BACKEND__.unref = Backend_unref;
896
- __BACKEND__.wait_event = Backend_wait_event;
897
- __BACKEND__.wakeup = Backend_wakeup;
898
853
  }
899
854
 
900
855
  #endif // POLYPHONY_BACKEND_LIBEV
@@ -66,7 +66,7 @@ VALUE Event_await(VALUE self) {
66
66
 
67
67
  VALUE backend = rb_ivar_get(rb_thread_current(), ID_ivar_backend);
68
68
  event->waiting_fiber = rb_fiber_current();
69
- VALUE switchpoint_result = __BACKEND__.wait_event(backend, Qnil);
69
+ VALUE switchpoint_result = Backend_wait_event(backend, Qnil);
70
70
  event->waiting_fiber = Qnil;
71
71
 
72
72
  RAISE_IF_EXCEPTION(switchpoint_result);
@@ -22,8 +22,6 @@ ID ID_R;
22
22
  ID ID_W;
23
23
  ID ID_RW;
24
24
 
25
- backend_interface_t backend_interface;
26
-
27
25
  VALUE Polyphony_snooze(VALUE self) {
28
26
  VALUE ret;
29
27
  VALUE fiber = rb_fiber_current();
@@ -4,7 +4,6 @@
4
4
  #include <execinfo.h>
5
5
 
6
6
  #include "ruby.h"
7
- #include "backend.h"
8
7
  #include "runqueue_ring_buffer.h"
9
8
 
10
9
  // debugging
@@ -32,9 +31,6 @@
32
31
  // Fiber#transfer
33
32
  #define FIBER_TRANSFER(fiber, value) rb_funcall(fiber, ID_transfer, 1, value)
34
33
 
35
- extern backend_interface_t backend_interface;
36
- #define __BACKEND__ (backend_interface)
37
-
38
34
  extern VALUE mPolyphony;
39
35
  extern VALUE cQueue;
40
36
  extern VALUE cEvent;
@@ -92,6 +88,11 @@ void Runqueue_clear(VALUE self);
92
88
  long Runqueue_len(VALUE self);
93
89
  int Runqueue_empty_p(VALUE self);
94
90
 
91
+ unsigned int Backend_pending_count(VALUE self);
92
+ VALUE Backend_poll(VALUE self, VALUE nowait, VALUE current_fiber, VALUE runqueue);
93
+ VALUE Backend_wait_event(VALUE self, VALUE raise_on_exception);
94
+ VALUE Backend_wakeup(VALUE self);
95
+
95
96
  VALUE Thread_schedule_fiber(VALUE thread, VALUE fiber, VALUE value);
96
97
  VALUE Thread_schedule_fiber_with_priority(VALUE thread, VALUE fiber, VALUE value);
97
98
  VALUE Thread_switch_fiber(VALUE thread);
@@ -86,7 +86,7 @@ inline void capped_queue_block_push(Queue_t *queue) {
86
86
  if (queue->capacity > queue->values.count) Fiber_make_runnable(fiber, Qnil);
87
87
 
88
88
  ring_buffer_push(&queue->push_queue, fiber);
89
- switchpoint_result = __BACKEND__.wait_event(backend, Qnil);
89
+ switchpoint_result = Backend_wait_event(backend, Qnil);
90
90
  ring_buffer_delete(&queue->push_queue, fiber);
91
91
 
92
92
  RAISE_IF_EXCEPTION(switchpoint_result);
@@ -131,7 +131,7 @@ VALUE Queue_shift(VALUE self) {
131
131
  if (queue->values.count) Fiber_make_runnable(fiber, Qnil);
132
132
 
133
133
  ring_buffer_push(&queue->shift_queue, fiber);
134
- VALUE switchpoint_result = __BACKEND__.wait_event(backend, Qnil);
134
+ VALUE switchpoint_result = Backend_wait_event(backend, Qnil);
135
135
  ring_buffer_delete(&queue->shift_queue, fiber);
136
136
 
137
137
  RAISE_IF_EXCEPTION(switchpoint_result);
@@ -17,16 +17,6 @@ static VALUE Thread_setup_fiber_scheduling(VALUE self) {
17
17
  return self;
18
18
  }
19
19
 
20
- int Thread_fiber_ref_count(VALUE self) {
21
- VALUE backend = rb_ivar_get(self, ID_ivar_backend);
22
- return NUM2INT(__BACKEND__.ref_count(backend));
23
- }
24
-
25
- inline void Thread_fiber_reset_ref_count(VALUE self) {
26
- VALUE backend = rb_ivar_get(self, ID_ivar_backend);
27
- __BACKEND__.reset_ref_count(backend);
28
- }
29
-
30
20
  static VALUE SYM_scheduled_fibers;
31
21
  static VALUE SYM_pending_watchers;
32
22
 
@@ -39,7 +29,7 @@ static VALUE Thread_fiber_scheduling_stats(VALUE self) {
39
29
  long scheduled_count = Runqueue_len(runqueue);
40
30
  rb_hash_aset(stats, SYM_scheduled_fibers, INT2NUM(scheduled_count));
41
31
 
42
- pending_count = __BACKEND__.pending_count(backend);
32
+ pending_count = Backend_pending_count(backend);
43
33
  rb_hash_aset(stats, SYM_pending_watchers, INT2NUM(pending_count));
44
34
 
45
35
  return stats;
@@ -64,7 +54,7 @@ void schedule_fiber(VALUE self, VALUE fiber, VALUE value, int prioritize) {
64
54
  // happen, not knowing that it there's already a fiber ready to run in its
65
55
  // run queue.
66
56
  VALUE backend = rb_ivar_get(self,ID_ivar_backend);
67
- __BACKEND__.wakeup(backend);
57
+ Backend_wakeup(backend);
68
58
  }
69
59
  }
70
60
  }
@@ -84,25 +74,24 @@ VALUE Thread_switch_fiber(VALUE self) {
84
74
  VALUE runqueue = rb_ivar_get(self, ID_ivar_runqueue);
85
75
  runqueue_entry next;
86
76
  VALUE backend = rb_ivar_get(self, ID_ivar_backend);
87
- int ref_count;
88
- int backend_was_polled = 0;
77
+ unsigned int pending_count = Backend_pending_count(backend);
78
+ unsigned int backend_was_polled = 0;
89
79
 
90
80
  if (__tracing_enabled__ && (rb_ivar_get(current_fiber, ID_ivar_running) != Qfalse))
91
81
  TRACE(2, SYM_fiber_switchpoint, current_fiber);
92
82
 
93
- ref_count = __BACKEND__.ref_count(backend);
94
83
  while (1) {
95
84
  next = Runqueue_shift(runqueue);
96
85
  if (next.fiber != Qnil) {
97
- if (backend_was_polled == 0 && ref_count > 0) {
86
+ if (!backend_was_polled && pending_count) {
98
87
  // this prevents event starvation in case the run queue never empties
99
- __BACKEND__.poll(backend, Qtrue, current_fiber, runqueue);
88
+ Backend_poll(backend, Qtrue, current_fiber, runqueue);
100
89
  }
101
90
  break;
102
91
  }
103
- if (ref_count == 0) break;
92
+ if (pending_count == 0) break;
104
93
 
105
- __BACKEND__.poll(backend, Qnil, current_fiber, runqueue);
94
+ Backend_poll(backend, Qnil, current_fiber, runqueue);
106
95
  backend_was_polled = 1;
107
96
  }
108
97
 
@@ -118,20 +107,13 @@ VALUE Thread_switch_fiber(VALUE self) {
118
107
  next.value : FIBER_TRANSFER(next.fiber, next.value);
119
108
  }
120
109
 
121
- VALUE Thread_reset_fiber_scheduling(VALUE self) {
122
- VALUE queue = rb_ivar_get(self, ID_ivar_runqueue);
123
- Runqueue_clear(queue);
124
- Thread_fiber_reset_ref_count(self);
125
- return self;
126
- }
127
-
128
110
  VALUE Thread_fiber_schedule_and_wakeup(VALUE self, VALUE fiber, VALUE resume_obj) {
129
111
  VALUE backend = rb_ivar_get(self, ID_ivar_backend);
130
112
  if (fiber != Qnil) {
131
113
  Thread_schedule_fiber_with_priority(self, fiber, resume_obj);
132
114
  }
133
115
 
134
- if (__BACKEND__.wakeup(backend) == Qnil) {
116
+ if (Backend_wakeup(backend) == Qnil) {
135
117
  // we're not inside the ev_loop, so we just do a switchpoint
136
118
  Thread_switch_fiber(self);
137
119
  }
@@ -146,7 +128,6 @@ VALUE Thread_debug(VALUE self) {
146
128
 
147
129
  void Init_Thread() {
148
130
  rb_define_method(rb_cThread, "setup_fiber_scheduling", Thread_setup_fiber_scheduling, 0);
149
- rb_define_method(rb_cThread, "reset_fiber_scheduling", Thread_reset_fiber_scheduling, 0);
150
131
  rb_define_method(rb_cThread, "fiber_scheduling_stats", Thread_fiber_scheduling_stats, 0);
151
132
  rb_define_method(rb_cThread, "schedule_and_wakeup", Thread_fiber_schedule_and_wakeup, 2);
152
133
 
@@ -11,7 +11,7 @@ module ::PG
11
11
 
12
12
  def self.connect_async(conn)
13
13
  socket_io = conn.socket_io
14
- loop do
14
+ while true
15
15
  res = conn.connect_poll
16
16
  case res
17
17
  when PGRES_POLLING_FAILED then raise Error, conn.error_message
@@ -23,7 +23,7 @@ module ::PG
23
23
  end
24
24
 
25
25
  def self.connect_sync(conn)
26
- loop do
26
+ while true
27
27
  res = conn.connect_poll
28
28
  case res
29
29
  when PGRES_POLLING_FAILED
@@ -96,7 +96,7 @@ class ::PG::Connection
96
96
  def wait_for_notify(timeout = nil, &block)
97
97
  return move_on_after(timeout) { wait_for_notify(&block) } if timeout
98
98
 
99
- loop do
99
+ while true
100
100
  Thread.current.backend.wait_io(socket_io, false)
101
101
  consume_input
102
102
  notice = notifies
@@ -53,13 +53,21 @@ module Polyphony
53
53
  Fiber.current.spin(tag, caller, &block)
54
54
  end
55
55
 
56
- def spin_loop(tag = nil, rate: nil, &block)
57
- if rate
56
+ def spin_loop(tag = nil, rate: nil, interval: nil, &block)
57
+ if rate || interval
58
58
  Fiber.current.spin(tag, caller) do
59
- throttled_loop(rate, &block)
59
+ throttled_loop(rate: rate, interval: interval, &block)
60
60
  end
61
61
  else
62
- Fiber.current.spin(tag, caller) { loop(&block) }
62
+ spin_looped_block(tag, caller, block)
63
+ end
64
+ end
65
+
66
+ def spin_looped_block(tag, caller, block)
67
+ Fiber.current.spin(tag, caller) do
68
+ block.call while true
69
+ rescue LocalJumpError, StopIteration
70
+ # break called or StopIteration raised
63
71
  end
64
72
  end
65
73
 
@@ -73,17 +81,8 @@ module Polyphony
73
81
  end.await
74
82
  end
75
83
 
76
- def every(interval)
77
- next_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + interval
78
- loop do
79
- now = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
80
- Thread.current.backend.sleep(next_time - now)
81
- yield
82
- loop do
83
- next_time += interval
84
- break if next_time > now
85
- end
86
- end
84
+ def every(interval, &block)
85
+ Thread.current.backend.timer_loop(interval, &block)
87
86
  end
88
87
 
89
88
  def move_on_after(interval, with_value: nil, &block)
@@ -142,8 +141,12 @@ module Polyphony
142
141
  if opts[:count]
143
142
  opts[:count].times { |_i| throttler.(&block) }
144
143
  else
145
- loop { throttler.(&block) }
144
+ while true
145
+ throttler.(&block)
146
+ end
146
147
  end
148
+ rescue LocalJumpError, StopIteration
149
+ # break called or StopIteration raised
147
150
  ensure
148
151
  throttler&.stop
149
152
  end