polyphony 0.59.1 → 0.60

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.
@@ -22,7 +22,7 @@ typedef struct op_context {
22
22
  struct op_context *prev;
23
23
  struct op_context *next;
24
24
  enum op_type type: 16;
25
- unsigned int ref_count : 16;
25
+ unsigned int ref_count : 16;
26
26
  int id;
27
27
  int result;
28
28
  VALUE fiber;
@@ -31,8 +31,10 @@ typedef struct op_context {
31
31
 
32
32
  typedef struct op_context_store {
33
33
  int last_id;
34
- op_context_t *available;
35
- op_context_t *taken;
34
+ op_context_t *available;
35
+ op_context_t *taken;
36
+ int available_count;
37
+ int taken_count;
36
38
  } op_context_store_t;
37
39
 
38
40
  const char *op_type_to_str(enum op_type type);
@@ -217,7 +217,6 @@ inline struct backend_stats Backend_stats(VALUE self) {
217
217
 
218
218
  return (struct backend_stats){
219
219
  .scheduled_fibers = runqueue_len(&backend->base.runqueue),
220
- .waiting_fibers = 0,
221
220
  .pending_ops = backend->base.pending_count
222
221
  };
223
222
  }
@@ -261,14 +260,20 @@ VALUE libev_wait_fd(Backend_t *backend, int fd, int events, int raise_exception)
261
260
  return switchpoint_result;
262
261
  }
263
262
 
264
- VALUE Backend_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof) {
263
+ VALUE Backend_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof, VALUE pos) {
265
264
  Backend_t *backend;
266
265
  struct libev_io watcher;
267
266
  rb_io_t *fptr;
268
267
  long dynamic_len = length == Qnil;
269
268
  long len = dynamic_len ? 4096 : NUM2INT(length);
270
- int shrinkable = io_setstrbuf(&str, len);
271
- char *buf = RSTRING_PTR(str);
269
+ long buf_pos = NUM2INT(pos);
270
+ if (str != Qnil) {
271
+ int current_len = RSTRING_LEN(str);
272
+ if (buf_pos < 0 || buf_pos > current_len) buf_pos = current_len;
273
+ }
274
+ else buf_pos = 0;
275
+ int shrinkable = io_setstrbuf(&str, buf_pos + len);
276
+ char *buf = RSTRING_PTR(str) + buf_pos;
272
277
  long total = 0;
273
278
  VALUE switchpoint_result = Qnil;
274
279
  int read_to_eof = RTEST(to_eof);
@@ -304,9 +309,9 @@ VALUE Backend_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof)
304
309
  if (total == len) {
305
310
  if (!dynamic_len) break;
306
311
 
307
- rb_str_resize(str, total);
312
+ rb_str_resize(str, buf_pos + total);
308
313
  rb_str_modify_expand(str, len);
309
- buf = RSTRING_PTR(str) + total;
314
+ buf = RSTRING_PTR(str) + buf_pos + total;
310
315
  shrinkable = 0;
311
316
  len += len;
312
317
  }
@@ -314,7 +319,7 @@ VALUE Backend_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof)
314
319
  }
315
320
  }
316
321
 
317
- io_set_read_length(str, total, shrinkable);
322
+ io_set_read_length(str, buf_pos + total, shrinkable);
318
323
  io_enc_str(str, fptr);
319
324
 
320
325
  if (total == 0) return Qnil;
@@ -327,17 +332,17 @@ error:
327
332
  return RAISE_EXCEPTION(switchpoint_result);
328
333
  }
329
334
 
330
- VALUE Backend_recv(VALUE self, VALUE io, VALUE str, VALUE length) {
331
- return Backend_read(self, io, str, length, Qnil);
335
+ VALUE Backend_recv(VALUE self, VALUE io, VALUE str, VALUE length, VALUE pos) {
336
+ return Backend_read(self, io, str, length, Qnil, pos);
332
337
  }
333
338
 
334
- VALUE Backend_read_loop(VALUE self, VALUE io) {
339
+ VALUE Backend_read_loop(VALUE self, VALUE io, VALUE maxlen) {
335
340
  Backend_t *backend;
336
341
  struct libev_io watcher;
337
342
  rb_io_t *fptr;
338
343
  VALUE str;
339
344
  long total;
340
- long len = 8192;
345
+ long len = NUM2INT(maxlen);
341
346
  int shrinkable;
342
347
  char *buf;
343
348
  VALUE switchpoint_result = Qnil;
@@ -1511,10 +1516,10 @@ void Init_Backend() {
1511
1516
  rb_define_method(cBackend, "accept_loop", Backend_accept_loop, 2);
1512
1517
  rb_define_method(cBackend, "connect", Backend_connect, 3);
1513
1518
  rb_define_method(cBackend, "feed_loop", Backend_feed_loop, 3);
1514
- rb_define_method(cBackend, "read", Backend_read, 4);
1515
- rb_define_method(cBackend, "read_loop", Backend_read_loop, 1);
1516
- rb_define_method(cBackend, "recv", Backend_recv, 3);
1517
- rb_define_method(cBackend, "recv_loop", Backend_read_loop, 1);
1519
+ rb_define_method(cBackend, "read", Backend_read, 5);
1520
+ rb_define_method(cBackend, "read_loop", Backend_read_loop, 2);
1521
+ rb_define_method(cBackend, "recv", Backend_recv, 4);
1522
+ rb_define_method(cBackend, "recv_loop", Backend_read_loop, 2);
1518
1523
  rb_define_method(cBackend, "recv_feed_loop", Backend_feed_loop, 3);
1519
1524
  rb_define_method(cBackend, "send", Backend_send, 3);
1520
1525
  rb_define_method(cBackend, "sendv", Backend_sendv, 3);
@@ -8,8 +8,8 @@ use_pidfd_open = false
8
8
  force_use_libev = ENV['POLYPHONY_USE_LIBEV'] != nil
9
9
  linux = RUBY_PLATFORM =~ /linux/
10
10
 
11
- if linux && `uname -sr` =~ /Linux 5\.([\d+])/
12
- kernel_minor_version = $1.gsub('.', '').to_i
11
+ if linux && `uname -sr` =~ /Linux 5\.(\d+)/
12
+ kernel_minor_version = $1.to_i
13
13
  use_liburing = !force_use_libev && kernel_minor_version >= 6
14
14
  use_pidfd_open = kernel_minor_version >= 3
15
15
  end
@@ -81,34 +81,6 @@ static VALUE Fiber_state(VALUE self) {
81
81
  return SYM_waiting;
82
82
  }
83
83
 
84
- VALUE Fiber_await(VALUE self) {
85
- VALUE result;
86
-
87
- // we compare with false, since a fiber that has not yet started will have
88
- // @running set to nil
89
- if (rb_ivar_get(self, ID_ivar_running) == Qfalse) {
90
- result = rb_ivar_get(self, ID_ivar_result);
91
- RAISE_IF_EXCEPTION(result);
92
- return result;
93
- }
94
-
95
- VALUE fiber = rb_fiber_current();
96
- VALUE waiting_fibers = rb_ivar_get(self, ID_ivar_waiting_fibers);
97
- if (waiting_fibers == Qnil) {
98
- waiting_fibers = rb_hash_new();
99
- rb_ivar_set(self, ID_ivar_waiting_fibers, waiting_fibers);
100
- }
101
- rb_hash_aset(waiting_fibers, fiber, Qtrue);
102
-
103
- VALUE backend = rb_ivar_get(rb_thread_current(), ID_ivar_backend);
104
- result = Backend_wait_event(backend, Qnil);
105
-
106
- rb_hash_delete(waiting_fibers, fiber);
107
- RAISE_IF_EXCEPTION(result);
108
- RB_GC_GUARD(result);
109
- return result;
110
- }
111
-
112
84
  VALUE Fiber_send(VALUE self, VALUE value) {
113
85
  VALUE mailbox = rb_ivar_get(self, ID_ivar_mailbox);
114
86
  if (mailbox == Qnil) {
@@ -125,7 +97,7 @@ VALUE Fiber_receive(VALUE self) {
125
97
  mailbox = rb_funcall(cQueue, ID_new, 0);
126
98
  rb_ivar_set(self, ID_ivar_mailbox, mailbox);
127
99
  }
128
- return Queue_shift(mailbox);
100
+ return Queue_shift(mailbox);
129
101
  }
130
102
 
131
103
  VALUE Fiber_mailbox(VALUE self) {
@@ -150,9 +122,6 @@ void Init_Fiber() {
150
122
  rb_define_method(cFiber, "state", Fiber_state, 0);
151
123
  rb_define_method(cFiber, "auto_watcher", Fiber_auto_watcher, 0);
152
124
 
153
- rb_define_method(cFiber, "await", Fiber_await, 0);
154
- rb_define_method(cFiber, "join", Fiber_await, 0);
155
-
156
125
  rb_define_method(cFiber, "<<", Fiber_send, 1);
157
126
  rb_define_method(cFiber, "send", Fiber_send, 1);
158
127
  rb_define_method(cFiber, "receive", Fiber_receive, 0);
@@ -58,20 +58,20 @@ VALUE Polyphony_backend_feed_loop(VALUE self, VALUE io, VALUE receiver, VALUE me
58
58
  return Backend_feed_loop(BACKEND(), io, receiver, method);
59
59
  }
60
60
 
61
- VALUE Polyphony_backend_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof) {
62
- return Backend_read(BACKEND(), io, str, length, to_eof);
61
+ VALUE Polyphony_backend_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof, VALUE pos) {
62
+ return Backend_read(BACKEND(), io, str, length, to_eof, pos);
63
63
  }
64
64
 
65
- VALUE Polyphony_backend_read_loop(VALUE self, VALUE io) {
66
- return Backend_read_loop(BACKEND(), io);
65
+ VALUE Polyphony_backend_read_loop(VALUE self, VALUE io, VALUE maxlen) {
66
+ return Backend_read_loop(BACKEND(), io, maxlen);
67
67
  }
68
68
 
69
- VALUE Polyphony_backend_recv(VALUE self, VALUE io, VALUE str, VALUE length) {
70
- return Backend_recv(BACKEND(), io, str, length);
69
+ VALUE Polyphony_backend_recv(VALUE self, VALUE io, VALUE str, VALUE length, VALUE pos) {
70
+ return Backend_recv(BACKEND(), io, str, length, pos);
71
71
  }
72
72
 
73
- VALUE Polyphony_backend_recv_loop(VALUE self, VALUE io) {
74
- return Backend_recv_loop(BACKEND(), io);
73
+ VALUE Polyphony_backend_recv_loop(VALUE self, VALUE io, VALUE maxlen) {
74
+ return Backend_recv_loop(BACKEND(), io, maxlen);
75
75
  }
76
76
 
77
77
  VALUE Polyphony_backend_recv_feed_loop(VALUE self, VALUE io, VALUE receiver, VALUE method) {
@@ -130,10 +130,10 @@ void Init_Polyphony() {
130
130
  rb_define_singleton_method(mPolyphony, "backend_accept_loop", Polyphony_backend_accept_loop, 2);
131
131
  rb_define_singleton_method(mPolyphony, "backend_connect", Polyphony_backend_connect, 3);
132
132
  rb_define_singleton_method(mPolyphony, "backend_feed_loop", Polyphony_backend_feed_loop, 3);
133
- rb_define_singleton_method(mPolyphony, "backend_read", Polyphony_backend_read, 4);
134
- rb_define_singleton_method(mPolyphony, "backend_read_loop", Polyphony_backend_read_loop, 1);
135
- rb_define_singleton_method(mPolyphony, "backend_recv", Polyphony_backend_recv, 3);
136
- rb_define_singleton_method(mPolyphony, "backend_recv_loop", Polyphony_backend_recv_loop, 1);
133
+ rb_define_singleton_method(mPolyphony, "backend_read", Polyphony_backend_read, 5);
134
+ rb_define_singleton_method(mPolyphony, "backend_read_loop", Polyphony_backend_read_loop, 2);
135
+ rb_define_singleton_method(mPolyphony, "backend_recv", Polyphony_backend_recv, 4);
136
+ rb_define_singleton_method(mPolyphony, "backend_recv_loop", Polyphony_backend_recv_loop, 2);
137
137
  rb_define_singleton_method(mPolyphony, "backend_recv_feed_loop", Polyphony_backend_recv_feed_loop, 3);
138
138
  rb_define_singleton_method(mPolyphony, "backend_send", Polyphony_backend_send, 3);
139
139
  rb_define_singleton_method(mPolyphony, "backend_sendv", Polyphony_backend_sendv, 3);
@@ -91,10 +91,10 @@ VALUE Backend_accept(VALUE self, VALUE server_socket, VALUE socket_class);
91
91
  VALUE Backend_accept_loop(VALUE self, VALUE server_socket, VALUE socket_class);
92
92
  VALUE Backend_connect(VALUE self, VALUE io, VALUE addr, VALUE port);
93
93
  VALUE Backend_feed_loop(VALUE self, VALUE io, VALUE receiver, VALUE method);
94
- VALUE Backend_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof);
95
- VALUE Backend_read_loop(VALUE self, VALUE io);
96
- VALUE Backend_recv(VALUE self, VALUE io, VALUE str, VALUE length);
97
- VALUE Backend_recv_loop(VALUE self, VALUE io);
94
+ VALUE Backend_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof, VALUE pos);
95
+ VALUE Backend_read_loop(VALUE self, VALUE io, VALUE maxlen);
96
+ VALUE Backend_recv(VALUE self, VALUE io, VALUE str, VALUE length, VALUE pos);
97
+ VALUE Backend_recv_loop(VALUE self, VALUE io, VALUE maxlen);
98
98
  VALUE Backend_recv_feed_loop(VALUE self, VALUE io, VALUE receiver, VALUE method);
99
99
  VALUE Backend_send(VALUE self, VALUE io, VALUE msg, VALUE flags);
100
100
  VALUE Backend_sendv(VALUE self, VALUE io, VALUE ary, VALUE flags);
@@ -57,21 +57,21 @@ static VALUE Queue_initialize(int argc, VALUE *argv, VALUE self) {
57
57
  return self;
58
58
  }
59
59
 
60
- inline void queue_resume_first_blocked_fiber(ring_buffer *queue) {
60
+ inline void queue_schedule_first_blocked_fiber(ring_buffer *queue) {
61
61
  if (queue->count) {
62
62
  VALUE fiber = ring_buffer_shift(queue);
63
63
  if (fiber != Qnil) Fiber_make_runnable(fiber, Qnil);
64
64
  }
65
65
  }
66
66
 
67
- inline void queue_resume_all_blocked_fibers(ring_buffer *queue) {
67
+ inline void queue_schedule_all_blocked_fibers(ring_buffer *queue) {
68
68
  while (queue->count) {
69
69
  VALUE fiber = ring_buffer_shift(queue);
70
70
  if (fiber != Qnil) Fiber_make_runnable(fiber, Qnil);
71
71
  }
72
72
  }
73
73
 
74
- inline void queue_resume_blocked_fibers_to_capacity(Queue_t *queue) {
74
+ inline void queue_schedule_blocked_fibers_to_capacity(Queue_t *queue) {
75
75
  for (unsigned int i = queue->values.count; (i < queue->capacity) && queue->push_queue.count; i++) {
76
76
  VALUE fiber = ring_buffer_shift(&queue->push_queue);
77
77
  if (fiber != Qnil) Fiber_make_runnable(fiber, Qnil);
@@ -101,7 +101,7 @@ VALUE Queue_push(VALUE self, VALUE value) {
101
101
 
102
102
  if (queue->capacity) capped_queue_block_push(queue);
103
103
 
104
- queue_resume_first_blocked_fiber(&queue->shift_queue);
104
+ queue_schedule_first_blocked_fiber(&queue->shift_queue);
105
105
  ring_buffer_push(&queue->values, value);
106
106
 
107
107
  return self;
@@ -113,7 +113,7 @@ VALUE Queue_unshift(VALUE self, VALUE value) {
113
113
 
114
114
  if (queue->capacity) capped_queue_block_push(queue);
115
115
 
116
- queue_resume_first_blocked_fiber(&queue->shift_queue);
116
+ queue_schedule_first_blocked_fiber(&queue->shift_queue);
117
117
  ring_buffer_unshift(&queue->values, value);
118
118
 
119
119
  return self;
@@ -140,7 +140,7 @@ VALUE Queue_shift(VALUE self) {
140
140
  }
141
141
  VALUE value = ring_buffer_shift(&queue->values);
142
142
  if ((queue->capacity) && (queue->capacity > queue->values.count))
143
- queue_resume_first_blocked_fiber(&queue->push_queue);
143
+ queue_schedule_first_blocked_fiber(&queue->push_queue);
144
144
  RB_GC_GUARD(value);
145
145
  return value;
146
146
  }
@@ -152,7 +152,7 @@ VALUE Queue_delete(VALUE self, VALUE value) {
152
152
  ring_buffer_delete(&queue->values, value);
153
153
 
154
154
  if (queue->capacity && (queue->capacity > queue->values.count))
155
- queue_resume_first_blocked_fiber(&queue->push_queue);
155
+ queue_schedule_first_blocked_fiber(&queue->push_queue);
156
156
 
157
157
  return self;
158
158
  }
@@ -164,9 +164,9 @@ VALUE Queue_cap(VALUE self, VALUE cap) {
164
164
  queue->capacity = new_capacity;
165
165
 
166
166
  if (queue->capacity)
167
- queue_resume_blocked_fibers_to_capacity(queue);
167
+ queue_schedule_blocked_fibers_to_capacity(queue);
168
168
  else
169
- queue_resume_all_blocked_fibers(&queue->push_queue);
169
+ queue_schedule_all_blocked_fibers(&queue->push_queue);
170
170
 
171
171
  return self;
172
172
  }
@@ -183,7 +183,7 @@ VALUE Queue_clear(VALUE self) {
183
183
  GetQueue(self, queue);
184
184
 
185
185
  ring_buffer_clear(&queue->values);
186
- if (queue->capacity) queue_resume_blocked_fibers_to_capacity(queue);
186
+ if (queue->capacity) queue_schedule_blocked_fibers_to_capacity(queue);
187
187
 
188
188
  return self;
189
189
  }
@@ -200,7 +200,7 @@ VALUE Queue_shift_each(VALUE self) {
200
200
  GetQueue(self, queue);
201
201
 
202
202
  ring_buffer_shift_each(&queue->values);
203
- if (queue->capacity) queue_resume_blocked_fibers_to_capacity(queue);
203
+ if (queue->capacity) queue_schedule_blocked_fibers_to_capacity(queue);
204
204
  return self;
205
205
  }
206
206
 
@@ -209,7 +209,7 @@ VALUE Queue_shift_all(VALUE self) {
209
209
  GetQueue(self, queue);
210
210
 
211
211
  VALUE result = ring_buffer_shift_all(&queue->values);
212
- if (queue->capacity) queue_resume_blocked_fibers_to_capacity(queue);
212
+ if (queue->capacity) queue_schedule_blocked_fibers_to_capacity(queue);
213
213
  return result;
214
214
  }
215
215
 
@@ -58,14 +58,11 @@ inline int runqueue_empty_p(runqueue_t *runqueue) {
58
58
  return (runqueue->entries.count == 0);
59
59
  }
60
60
 
61
- static const unsigned int ANTI_STARVE_HIGH_WATERMARK_THRESHOLD = 128;
62
61
  static const unsigned int ANTI_STARVE_SWITCH_COUNT_THRESHOLD = 64;
63
62
 
64
63
  inline int runqueue_should_poll_nonblocking(runqueue_t *runqueue) {
65
- if (runqueue->high_watermark < ANTI_STARVE_HIGH_WATERMARK_THRESHOLD) return 0;
66
64
  if (runqueue->switch_count < ANTI_STARVE_SWITCH_COUNT_THRESHOLD) return 0;
67
65
 
68
- // the
69
66
  runqueue->switch_count = 0;
70
67
  return 1;
71
68
  }
@@ -22,9 +22,9 @@ module Polyphony
22
22
  return self
23
23
  end
24
24
 
25
- parent.spin(@tag, @caller, &@block).tap do |f|
26
- f.schedule(value) unless value.nil?
27
- end
25
+ fiber = parent.spin(@tag, @caller, &@block)
26
+ fiber.schedule(value) unless value.nil?
27
+ fiber
28
28
  end
29
29
  alias_method :reset, :restart
30
30
 
@@ -66,6 +66,11 @@ module Polyphony
66
66
  def interject(&block)
67
67
  raise Polyphony::Interjection.new(block)
68
68
  end
69
+
70
+ def await
71
+ Fiber.await(self).first
72
+ end
73
+ alias_method :join, :await
69
74
  end
70
75
 
71
76
  # Fiber supervision
@@ -78,7 +83,7 @@ module Polyphony
78
83
  while true
79
84
  supervise_perform(opts)
80
85
  end
81
- rescue Polyphony::MoveOn
86
+ rescue Polyphony::MoveOn
82
87
  # generated in #supervise_perform to stop supervisor
83
88
  ensure
84
89
  @on_child_done = nil
@@ -119,72 +124,62 @@ module Polyphony
119
124
  def await(*fibers)
120
125
  return [] if fibers.empty?
121
126
 
122
- state = setup_await_select_state(fibers)
123
- await_setup_monitoring(fibers, state)
124
- suspend
125
- fibers.map(&:result)
126
- ensure
127
- await_select_cleanup(state) if state
128
- end
129
- alias_method :join, :await
130
-
131
- def setup_await_select_state(fibers)
132
- {
133
- awaiter: Fiber.current,
134
- pending: fibers.each_with_object({}) { |f, h| h[f] = true }
135
- }
136
- end
137
-
138
- def await_setup_monitoring(fibers, state)
127
+ Fiber.current.message_on_child_termination = true
128
+ results = {}
139
129
  fibers.each do |f|
140
- f.when_done { |r| await_fiber_done(f, r, state) }
130
+ results[f] = nil
131
+ if f.dead?
132
+ # fiber already terminated, so queue message
133
+ Fiber.current.send [f, f.result]
134
+ else
135
+ f.monitor
136
+ end
141
137
  end
142
- end
143
-
144
- def await_fiber_done(fiber, result, state)
145
- state[:pending].delete(fiber)
146
-
147
- if state[:cleanup]
148
- state[:awaiter].schedule if state[:pending].empty?
149
- elsif !state[:done] && (result.is_a?(Exception) || state[:pending].empty?)
150
- state[:awaiter].schedule(result)
151
- state[:done] = true
138
+ exception = nil
139
+ while !fibers.empty?
140
+ (fiber, result) = receive
141
+ next unless fibers.include?(fiber)
142
+
143
+ fibers.delete(fiber)
144
+ if result.is_a?(Exception)
145
+ exception ||= result
146
+ fibers.each { |f| f.terminate }
147
+ else
148
+ results[fiber] = result
149
+ end
152
150
  end
153
- end
154
-
155
- def await_select_cleanup(state)
156
- return if state[:pending].empty?
157
-
158
- terminate = Polyphony::Terminate.new
159
- state[:cleanup] = true
160
- state[:pending].each_key { |f| f.schedule(terminate) }
161
- suspend
162
- end
163
-
164
- def select(*fibers)
165
- state = setup_await_select_state(fibers)
166
- select_setup_monitoring(fibers, state)
167
- suspend
151
+ results.values
168
152
  ensure
169
- await_select_cleanup(state)
153
+ Fiber.current.message_on_child_termination = false
154
+ raise exception if exception
170
155
  end
156
+ alias_method :join, :await
171
157
 
172
- def select_setup_monitoring(fibers, state)
158
+ def select(*fibers)
159
+ return nil if fibers.empty?
160
+
173
161
  fibers.each do |f|
174
- f.when_done { |r| select_fiber_done(f, r, state) }
162
+ if f.dead?
163
+ result = f.result
164
+ result.is_a?(Exception) ? (raise result) : (return [f, result])
165
+ end
175
166
  end
176
- end
177
167
 
178
- def select_fiber_done(fiber, result, state)
179
- state[:pending].delete(fiber)
180
- if state[:cleanup]
181
- # in cleanup mode the selector is resumed if no more pending fibers
182
- state[:awaiter].schedule if state[:pending].empty?
183
- elsif !state[:selected]
184
- # first fiber to complete, we schedule the result
185
- state[:awaiter].schedule([fiber, result])
186
- state[:selected] = true
168
+ Fiber.current.message_on_child_termination = true
169
+ fibers.each { |f| f.monitor }
170
+ while true
171
+ (fiber, result) = receive
172
+ next unless fibers.include?(fiber)
173
+
174
+ fibers.each { |f| f.unmonitor }
175
+ if result.is_a?(Exception)
176
+ raise result
177
+ else
178
+ return [fiber, result]
179
+ end
187
180
  end
181
+ ensure
182
+ Fiber.current.message_on_child_termination = false
188
183
  end
189
184
 
190
185
  # Creates and schedules with priority an out-of-band fiber that runs the
@@ -209,6 +204,14 @@ module Polyphony
209
204
  (@children ||= {}).keys
210
205
  end
211
206
 
207
+ def add_child(child_fiber)
208
+ (@children ||= {})[child_fiber] = true
209
+ end
210
+
211
+ def remove_child(child_fiber)
212
+ @children.delete(child_fiber) if @children
213
+ end
214
+
212
215
  def spin(tag = nil, orig_caller = Kernel.caller, &block)
213
216
  f = Fiber.new { |v| f.run(v) }
214
217
  f.prepare(tag, block, orig_caller, self)
@@ -218,7 +221,10 @@ module Polyphony
218
221
 
219
222
  def child_done(child_fiber, result)
220
223
  @children.delete(child_fiber)
221
- @on_child_done&.(child_fiber, result)
224
+
225
+ if result.is_a?(Exception) && !@message_on_child_termination
226
+ schedule_with_priority(result)
227
+ end
222
228
  end
223
229
 
224
230
  def terminate_all_children(graceful = false)
@@ -234,14 +240,7 @@ module Polyphony
234
240
  def await_all_children
235
241
  return unless @children && !@children.empty?
236
242
 
237
- results = @children.dup
238
- @on_child_done = proc do |c, r|
239
- results[c] = r
240
- schedule if @children.empty?
241
- end
242
- suspend
243
- @on_child_done = nil
244
- results.values
243
+ Fiber.await(*@children.keys)
245
244
  end
246
245
 
247
246
  def shutdown_all_children(graceful = false)
@@ -252,6 +251,18 @@ module Polyphony
252
251
  c.await
253
252
  end
254
253
  end
254
+
255
+ def detach
256
+ @parent.remove_child(self)
257
+ @parent = @thread.main_fiber
258
+ @parent.add_child(self)
259
+ end
260
+
261
+ def attach(parent)
262
+ @parent.remove_child(self)
263
+ @parent = parent
264
+ @parent.add_child(self)
265
+ end
255
266
  end
256
267
 
257
268
  # Fiber life cycle methods
@@ -304,8 +315,6 @@ module Polyphony
304
315
 
305
316
  def restart_self(first_value)
306
317
  @mailbox = nil
307
- @when_done_procs = nil
308
- @waiting_fibers = nil
309
318
  run(first_value)
310
319
  end
311
320
 
@@ -313,8 +322,9 @@ module Polyphony
313
322
  result, uncaught_exception = finalize_children(result, uncaught_exception)
314
323
  Thread.backend.trace(:fiber_terminate, self, result)
315
324
  @result = result
316
- @running = false
325
+
317
326
  inform_dependants(result, uncaught_exception)
327
+ @running = false
318
328
  ensure
319
329
  # Prevent fiber from being resumed after terminating
320
330
  @thread.fiber_unschedule(self)
@@ -332,17 +342,27 @@ module Polyphony
332
342
  end
333
343
 
334
344
  def inform_dependants(result, uncaught_exception)
345
+ if @monitors
346
+ msg = [self, result]
347
+ @monitors.each { |f| f << msg }
348
+ end
349
+
335
350
  @parent&.child_done(self, result)
336
- @when_done_procs&.each { |p| p.(result) }
337
- @waiting_fibers&.each_key { |f| f.schedule(result) }
338
-
339
- # propagate unaught exception to parent
340
- @parent&.schedule_with_priority(result) if uncaught_exception && !@waiting_fibers
341
351
  end
342
352
 
343
- def when_done(&block)
344
- @when_done_procs ||= []
345
- @when_done_procs << block
353
+ attr_accessor :message_on_child_termination
354
+
355
+ def monitor
356
+ @monitors ||= []
357
+ @monitors << Fiber.current
358
+ end
359
+
360
+ def unmonitor
361
+ @monitors.delete(Fiber.current) if @monitors
362
+ end
363
+
364
+ def dead?
365
+ state == :dead
346
366
  end
347
367
  end
348
368
  end