polyphony 0.47.5.1 → 0.50.0

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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +29 -0
  3. data/Gemfile.lock +1 -1
  4. data/LICENSE +1 -1
  5. data/TODO.md +37 -17
  6. data/examples/core/nested.rb +21 -0
  7. data/examples/core/suspend.rb +13 -0
  8. data/examples/core/terminate_main_fiber.rb +12 -0
  9. data/examples/io/tcp_proxy.rb +32 -0
  10. data/examples/performance/line_splitting.rb +34 -0
  11. data/examples/performance/loop.rb +32 -0
  12. data/examples/performance/thread-vs-fiber/polyphony_server.rb +6 -2
  13. data/ext/polyphony/backend_common.h +3 -3
  14. data/ext/polyphony/backend_io_uring.c +29 -68
  15. data/ext/polyphony/backend_libev.c +18 -59
  16. data/ext/polyphony/event.c +1 -1
  17. data/ext/polyphony/fiber.c +2 -1
  18. data/ext/polyphony/polyphony.c +0 -2
  19. data/ext/polyphony/polyphony.h +6 -4
  20. data/ext/polyphony/queue.c +2 -2
  21. data/ext/polyphony/runqueue.c +6 -0
  22. data/ext/polyphony/runqueue_ring_buffer.c +9 -0
  23. data/ext/polyphony/runqueue_ring_buffer.h +1 -0
  24. data/ext/polyphony/thread.c +23 -28
  25. data/lib/polyphony.rb +2 -1
  26. data/lib/polyphony/adapters/postgres.rb +3 -3
  27. data/lib/polyphony/adapters/process.rb +2 -0
  28. data/lib/polyphony/core/exceptions.rb +1 -0
  29. data/lib/polyphony/core/global_api.rb +15 -3
  30. data/lib/polyphony/core/thread_pool.rb +3 -1
  31. data/lib/polyphony/core/throttler.rb +1 -1
  32. data/lib/polyphony/core/timer.rb +115 -0
  33. data/lib/polyphony/extensions/core.rb +4 -4
  34. data/lib/polyphony/extensions/fiber.rb +30 -13
  35. data/lib/polyphony/extensions/io.rb +8 -14
  36. data/lib/polyphony/extensions/openssl.rb +4 -4
  37. data/lib/polyphony/extensions/socket.rb +1 -1
  38. data/lib/polyphony/extensions/thread.rb +1 -2
  39. data/lib/polyphony/net.rb +3 -6
  40. data/lib/polyphony/version.rb +1 -1
  41. data/polyphony.gemspec +1 -1
  42. data/test/helper.rb +2 -2
  43. data/test/test_backend.rb +26 -1
  44. data/test/test_fiber.rb +95 -1
  45. data/test/test_global_api.rb +30 -0
  46. data/test/test_io.rb +26 -0
  47. data/test/test_signal.rb +1 -2
  48. data/test/test_socket.rb +5 -5
  49. data/test/test_supervise.rb +1 -1
  50. data/test/test_timer.rb +157 -0
  51. metadata +11 -4
  52. data/ext/polyphony/backend.h +0 -26
@@ -40,11 +40,14 @@ inline void io_set_nonblock(rb_io_t *fptr, VALUE io) {
40
40
  }
41
41
 
42
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
43
49
  struct ev_loop *ev_loop;
44
50
  struct ev_async break_async;
45
- int running;
46
- int ref_count;
47
- int run_no_wait_count;
48
51
  } Backend_t;
49
52
 
50
53
  static size_t Backend_size(const void *ptr) {
@@ -83,9 +86,9 @@ static VALUE Backend_initialize(VALUE self) {
83
86
  ev_async_start(backend->ev_loop, &backend->break_async);
84
87
  ev_unref(backend->ev_loop); // don't count the break_async watcher
85
88
 
86
- backend->running = 0;
87
- backend->ref_count = 0;
88
- backend->run_no_wait_count = 0;
89
+ backend->currently_polling = 0;
90
+ backend->pending_count = 0;
91
+ backend->poll_no_wait_count = 0;
89
92
 
90
93
  return Qnil;
91
94
  }
@@ -116,42 +119,11 @@ VALUE Backend_post_fork(VALUE self) {
116
119
  return self;
117
120
  }
118
121
 
119
- VALUE Backend_ref(VALUE self) {
120
- Backend_t *backend;
121
- GetBackend(self, backend);
122
-
123
- backend->ref_count++;
124
- return self;
125
- }
126
-
127
- VALUE Backend_unref(VALUE self) {
128
- Backend_t *backend;
129
- GetBackend(self, backend);
130
-
131
- backend->ref_count--;
132
- return self;
133
- }
134
-
135
- int Backend_ref_count(VALUE self) {
136
- Backend_t *backend;
137
- GetBackend(self, backend);
138
-
139
- return backend->ref_count;
140
- }
141
-
142
- void Backend_reset_ref_count(VALUE self) {
122
+ unsigned int Backend_pending_count(VALUE self) {
143
123
  Backend_t *backend;
144
124
  GetBackend(self, backend);
145
125
 
146
- backend->ref_count = 0;
147
- }
148
-
149
- VALUE Backend_pending_count(VALUE self) {
150
- int count;
151
- Backend_t *backend;
152
- GetBackend(self, backend);
153
- count = ev_pending_count(backend->ev_loop);
154
- return INT2NUM(count);
126
+ return backend->pending_count;
155
127
  }
156
128
 
157
129
  VALUE Backend_poll(VALUE self, VALUE nowait, VALUE current_fiber, VALUE runqueue) {
@@ -160,19 +132,19 @@ VALUE Backend_poll(VALUE self, VALUE nowait, VALUE current_fiber, VALUE runqueue
160
132
  GetBackend(self, backend);
161
133
 
162
134
  if (is_nowait) {
163
- backend->run_no_wait_count++;
164
- 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;
165
137
 
166
138
  long runnable_count = Runqueue_len(runqueue);
167
- if (backend->run_no_wait_count < runnable_count) return self;
139
+ if (backend->poll_no_wait_count < runnable_count) return self;
168
140
  }
169
141
 
170
- backend->run_no_wait_count = 0;
142
+ backend->poll_no_wait_count = 0;
171
143
 
172
144
  COND_TRACE(2, SYM_fiber_event_poll_enter, current_fiber);
173
- backend->running = 1;
145
+ backend->currently_polling = 1;
174
146
  ev_run(backend->ev_loop, is_nowait ? EVRUN_NOWAIT : EVRUN_ONCE);
175
- backend->running = 0;
147
+ backend->currently_polling = 0;
176
148
  COND_TRACE(2, SYM_fiber_event_poll_leave, current_fiber);
177
149
 
178
150
  return self;
@@ -182,7 +154,7 @@ VALUE Backend_wakeup(VALUE self) {
182
154
  Backend_t *backend;
183
155
  GetBackend(self, backend);
184
156
 
185
- if (backend->running) {
157
+ if (backend->currently_polling) {
186
158
  // Since the loop will run until at least one event has occurred, we signal
187
159
  // the selector's associated async watcher, which will cause the ev loop to
188
160
  // return. In contrast to using `ev_break` to break out of the loop, which
@@ -854,10 +826,6 @@ void Init_Backend() {
854
826
  rb_define_method(cBackend, "initialize", Backend_initialize, 0);
855
827
  rb_define_method(cBackend, "finalize", Backend_finalize, 0);
856
828
  rb_define_method(cBackend, "post_fork", Backend_post_fork, 0);
857
- rb_define_method(cBackend, "pending_count", Backend_pending_count, 0);
858
-
859
- rb_define_method(cBackend, "ref", Backend_ref, 0);
860
- rb_define_method(cBackend, "unref", Backend_unref, 0);
861
829
 
862
830
  rb_define_method(cBackend, "poll", Backend_poll, 3);
863
831
  rb_define_method(cBackend, "break", Backend_wakeup, 0);
@@ -882,15 +850,6 @@ void Init_Backend() {
882
850
 
883
851
  ID_ivar_is_nonblocking = rb_intern("@is_nonblocking");
884
852
  SYM_libev = ID2SYM(rb_intern("libev"));
885
-
886
- __BACKEND__.pending_count = Backend_pending_count;
887
- __BACKEND__.poll = Backend_poll;
888
- __BACKEND__.ref = Backend_ref;
889
- __BACKEND__.ref_count = Backend_ref_count;
890
- __BACKEND__.reset_ref_count = Backend_reset_ref_count;
891
- __BACKEND__.unref = Backend_unref;
892
- __BACKEND__.wait_event = Backend_wait_event;
893
- __BACKEND__.wakeup = Backend_wakeup;
894
853
  }
895
854
 
896
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);
@@ -101,7 +101,8 @@ VALUE Fiber_await(VALUE self) {
101
101
  }
102
102
  rb_hash_aset(waiting_fibers, fiber, Qtrue);
103
103
 
104
- result = Thread_switch_fiber(rb_thread_current());
104
+ VALUE backend = rb_ivar_get(rb_thread_current(), ID_ivar_backend);
105
+ result = Backend_wait_event(backend, Qnil);
105
106
 
106
107
  rb_hash_delete(waiting_fibers, fiber);
107
108
  RAISE_IF_EXCEPTION(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;
@@ -88,10 +84,16 @@ void Runqueue_push(VALUE self, VALUE fiber, VALUE value, int reschedule);
88
84
  void Runqueue_unshift(VALUE self, VALUE fiber, VALUE value, int reschedule);
89
85
  runqueue_entry Runqueue_shift(VALUE self);
90
86
  void Runqueue_delete(VALUE self, VALUE fiber);
87
+ int Runqueue_index_of(VALUE self, VALUE fiber);
91
88
  void Runqueue_clear(VALUE self);
92
89
  long Runqueue_len(VALUE self);
93
90
  int Runqueue_empty_p(VALUE self);
94
91
 
92
+ unsigned int Backend_pending_count(VALUE self);
93
+ VALUE Backend_poll(VALUE self, VALUE nowait, VALUE current_fiber, VALUE runqueue);
94
+ VALUE Backend_wait_event(VALUE self, VALUE raise_on_exception);
95
+ VALUE Backend_wakeup(VALUE self);
96
+
95
97
  VALUE Thread_schedule_fiber(VALUE thread, VALUE fiber, VALUE value);
96
98
  VALUE Thread_schedule_fiber_with_priority(VALUE thread, VALUE fiber, VALUE value);
97
99
  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);
@@ -74,6 +74,12 @@ void Runqueue_delete(VALUE self, VALUE fiber) {
74
74
  runqueue_ring_buffer_delete(&runqueue->entries, fiber);
75
75
  }
76
76
 
77
+ int Runqueue_index_of(VALUE self, VALUE fiber) {
78
+ Runqueue_t *runqueue;
79
+ GetRunqueue(self, runqueue);
80
+ return runqueue_ring_buffer_index_of(&runqueue->entries, fiber);
81
+ }
82
+
77
83
  void Runqueue_clear(VALUE self) {
78
84
  Runqueue_t *runqueue;
79
85
  GetRunqueue(self, runqueue);
@@ -80,6 +80,15 @@ void runqueue_ring_buffer_delete(runqueue_ring_buffer *buffer, VALUE fiber) {
80
80
  }
81
81
  }
82
82
 
83
+ int runqueue_ring_buffer_index_of(runqueue_ring_buffer *buffer, VALUE fiber) {
84
+ for (unsigned int i = 0; i < buffer->count; i++) {
85
+ unsigned int idx = (buffer->head + i) % buffer->size;
86
+ if (buffer->entries[idx].fiber == fiber)
87
+ return i;
88
+ }
89
+ return -1;
90
+ }
91
+
83
92
  void runqueue_ring_buffer_clear(runqueue_ring_buffer *buffer) {
84
93
  buffer->count = buffer->head = buffer->tail = 0;
85
94
  }
@@ -27,5 +27,6 @@ void runqueue_ring_buffer_unshift(runqueue_ring_buffer *buffer, VALUE fiber, VAL
27
27
  void runqueue_ring_buffer_push(runqueue_ring_buffer *buffer, VALUE fiber, VALUE value);
28
28
 
29
29
  void runqueue_ring_buffer_delete(runqueue_ring_buffer *buffer, VALUE fiber);
30
+ int runqueue_ring_buffer_index_of(runqueue_ring_buffer *buffer, VALUE fiber);
30
31
 
31
32
  #endif /* RUNQUEUE_RING_BUFFER_H */
@@ -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,11 +54,23 @@ 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
  }
71
61
 
62
+ VALUE Thread_fiber_scheduling_index(VALUE self, VALUE fiber) {
63
+ VALUE runqueue = rb_ivar_get(self, ID_ivar_runqueue);
64
+
65
+ return INT2NUM(Runqueue_index_of(runqueue, fiber));
66
+ }
67
+
68
+ VALUE Thread_fiber_unschedule(VALUE self, VALUE fiber) {
69
+ VALUE runqueue = rb_ivar_get(self, ID_ivar_runqueue);
70
+ Runqueue_delete(runqueue, fiber);
71
+ return self;
72
+ }
73
+
72
74
  VALUE Thread_schedule_fiber(VALUE self, VALUE fiber, VALUE value) {
73
75
  schedule_fiber(self, fiber, value, 0);
74
76
  return self;
@@ -84,25 +86,24 @@ VALUE Thread_switch_fiber(VALUE self) {
84
86
  VALUE runqueue = rb_ivar_get(self, ID_ivar_runqueue);
85
87
  runqueue_entry next;
86
88
  VALUE backend = rb_ivar_get(self, ID_ivar_backend);
87
- int ref_count;
88
- int backend_was_polled = 0;
89
+ unsigned int pending_count = Backend_pending_count(backend);
90
+ unsigned int backend_was_polled = 0;
89
91
 
90
92
  if (__tracing_enabled__ && (rb_ivar_get(current_fiber, ID_ivar_running) != Qfalse))
91
93
  TRACE(2, SYM_fiber_switchpoint, current_fiber);
92
94
 
93
- ref_count = __BACKEND__.ref_count(backend);
94
95
  while (1) {
95
96
  next = Runqueue_shift(runqueue);
96
97
  if (next.fiber != Qnil) {
97
- if (backend_was_polled == 0 && ref_count > 0) {
98
+ if (!backend_was_polled && pending_count) {
98
99
  // this prevents event starvation in case the run queue never empties
99
- __BACKEND__.poll(backend, Qtrue, current_fiber, runqueue);
100
+ Backend_poll(backend, Qtrue, current_fiber, runqueue);
100
101
  }
101
102
  break;
102
103
  }
103
- if (ref_count == 0) break;
104
+ if (pending_count == 0) break;
104
105
 
105
- __BACKEND__.poll(backend, Qnil, current_fiber, runqueue);
106
+ Backend_poll(backend, Qnil, current_fiber, runqueue);
106
107
  backend_was_polled = 1;
107
108
  }
108
109
 
@@ -118,20 +119,13 @@ VALUE Thread_switch_fiber(VALUE self) {
118
119
  next.value : FIBER_TRANSFER(next.fiber, next.value);
119
120
  }
120
121
 
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
122
  VALUE Thread_fiber_schedule_and_wakeup(VALUE self, VALUE fiber, VALUE resume_obj) {
129
123
  VALUE backend = rb_ivar_get(self, ID_ivar_backend);
130
124
  if (fiber != Qnil) {
131
125
  Thread_schedule_fiber_with_priority(self, fiber, resume_obj);
132
126
  }
133
127
 
134
- if (__BACKEND__.wakeup(backend) == Qnil) {
128
+ if (Backend_wakeup(backend) == Qnil) {
135
129
  // we're not inside the ev_loop, so we just do a switchpoint
136
130
  Thread_switch_fiber(self);
137
131
  }
@@ -146,7 +140,6 @@ VALUE Thread_debug(VALUE self) {
146
140
 
147
141
  void Init_Thread() {
148
142
  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
143
  rb_define_method(rb_cThread, "fiber_scheduling_stats", Thread_fiber_scheduling_stats, 0);
151
144
  rb_define_method(rb_cThread, "schedule_and_wakeup", Thread_fiber_schedule_and_wakeup, 2);
152
145
 
@@ -154,6 +147,8 @@ void Init_Thread() {
154
147
  rb_define_method(rb_cThread, "schedule_fiber_with_priority",
155
148
  Thread_schedule_fiber_with_priority, 2);
156
149
  rb_define_method(rb_cThread, "switch_fiber", Thread_switch_fiber, 0);
150
+ rb_define_method(rb_cThread, "fiber_scheduling_index", Thread_fiber_scheduling_index, 1);
151
+ rb_define_method(rb_cThread, "fiber_unschedule", Thread_fiber_unschedule, 1);
157
152
 
158
153
  rb_define_method(rb_cThread, "debug!", Thread_debug, 0);
159
154
 
@@ -14,6 +14,7 @@ Thread.current.backend = Polyphony::Backend.new
14
14
  require_relative './polyphony/core/global_api'
15
15
  require_relative './polyphony/core/resource_pool'
16
16
  require_relative './polyphony/core/sync'
17
+ require_relative './polyphony/core/timer'
17
18
  require_relative './polyphony/net'
18
19
  require_relative './polyphony/adapters/process'
19
20
 
@@ -43,7 +44,7 @@ module Polyphony
43
44
  rescue SystemExit
44
45
  # fall through to ensure
45
46
  rescue Exception => e
46
- warn e.full_message
47
+ STDERR << e.full_message
47
48
  exit!
48
49
  ensure
49
50
  exit_forked_process
@@ -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
@@ -24,6 +24,8 @@ module Polyphony
24
24
  def kill_and_await(sig, pid)
25
25
  ::Process.kill(sig, pid)
26
26
  Thread.current.backend.waitpid(pid)
27
+ rescue Errno::ERSCH
28
+ # ignore
27
29
  end
28
30
  end
29
31
  end
@@ -13,6 +13,7 @@ module Polyphony
13
13
  def initialize(value = nil)
14
14
  @caller_backtrace = caller
15
15
  @value = value
16
+ super
16
17
  end
17
18
  end
18
19
 
@@ -29,7 +29,7 @@ module Polyphony
29
29
  spin do
30
30
  sleep interval
31
31
  exception = cancel_exception(with_exception)
32
- exception.__raising_fiber__ = nil
32
+ exception.raising_fiber = nil
33
33
  fiber.schedule exception
34
34
  end
35
35
  end
@@ -59,7 +59,15 @@ module Polyphony
59
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
 
@@ -133,8 +141,12 @@ module Polyphony
133
141
  if opts[:count]
134
142
  opts[:count].times { |_i| throttler.(&block) }
135
143
  else
136
- loop { throttler.(&block) }
144
+ while true
145
+ throttler.(&block)
146
+ end
137
147
  end
148
+ rescue LocalJumpError, StopIteration
149
+ # break called or StopIteration raised
138
150
  ensure
139
151
  throttler&.stop
140
152
  end