uringmachine 0.28.3 → 0.29.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.
@@ -3,21 +3,68 @@
3
3
  VALUE cStream;
4
4
  VALUE eStreamRESPError;
5
5
 
6
+ VALUE SYM_bp_read;
7
+ VALUE SYM_bp_recv;
8
+ VALUE SYM_ssl;
9
+
10
+ inline int stream_has_target_obj_p(struct um_stream *stream) {
11
+ switch (stream->mode) {
12
+ case STREAM_SSL:
13
+ case STREAM_STRING:
14
+ case STREAM_IO_BUFFER:
15
+ return true;
16
+ default:
17
+ return false;
18
+ }
19
+ }
20
+
21
+ inline void stream_mark_segments(struct um_stream *stream) {
22
+ struct um_segment *curr = stream->head;
23
+ while (curr) {
24
+ // rb_gc_mark_movable(curr->obj);
25
+ curr = curr->next;
26
+ }
27
+ }
28
+
29
+ inline void stream_compact_segments(struct um_stream *stream) {
30
+ struct um_segment *curr = stream->head;
31
+ while (curr) {
32
+ // curr->obj = rb_gc_location(curr->obj);
33
+ curr = curr->next;
34
+ }
35
+ }
36
+
6
37
  static void Stream_mark(void *ptr) {
7
38
  struct um_stream *stream = ptr;
8
- rb_gc_mark_movable(stream->buffer);
39
+ rb_gc_mark_movable(stream->self);
40
+ rb_gc_mark_movable(stream->machine->self);
41
+
42
+ if (stream_has_target_obj_p(stream)) {
43
+ rb_gc_mark_movable(stream->target);
44
+ stream_mark_segments(stream);
45
+ }
9
46
  }
10
47
 
11
48
  static void Stream_compact(void *ptr) {
12
49
  struct um_stream *stream = ptr;
13
- stream->buffer = rb_gc_location(stream->buffer);
50
+ stream->self = rb_gc_location(stream->self);
51
+
52
+ if (stream_has_target_obj_p(stream)) {
53
+ stream->target = rb_gc_location(stream->target);
54
+ stream_compact_segments(stream);
55
+ }
56
+ }
57
+
58
+ static void Stream_free(void *ptr) {
59
+ struct um_stream *stream = ptr;
60
+ stream_clear(stream);
14
61
  }
15
62
 
16
63
  static const rb_data_type_t Stream_type = {
17
64
  .wrap_struct_name = "UringMachine::Stream",
18
65
  .function = {
19
66
  .dmark = Stream_mark,
20
- .dfree = RUBY_TYPED_DEFAULT_FREE,
67
+ .dfree = Stream_free,
21
68
  .dsize = NULL,
22
69
  .dcompact = Stream_compact
23
70
  },
@@ -26,67 +73,74 @@ static const rb_data_type_t Stream_type = {
26
73
 
27
74
  static VALUE Stream_allocate(VALUE klass) {
28
75
  struct um_stream *stream;
29
- return TypedData_Make_Struct(klass, struct um_stream, &Stream_type, stream);
76
+ VALUE self = TypedData_Make_Struct(klass, struct um_stream, &Stream_type, stream);
77
+ return self;
30
78
  }
31
79
 
32
- static inline struct um_stream *Stream_data(VALUE self) {
80
+ static inline struct um_stream *um_get_stream(VALUE self) {
33
81
  struct um_stream *stream;
34
82
  TypedData_Get_Struct(self, struct um_stream, &Stream_type, stream);
35
83
  return stream;
36
84
  }
37
85
 
38
- VALUE Stream_initialize(VALUE self, VALUE machine, VALUE fd) {
39
- struct um_stream *stream = Stream_data(self);
40
-
41
- stream->machine = um_get_machine(machine);
42
- stream->fd = NUM2ULONG(fd);
43
- stream->buffer = rb_utf8_str_new_literal("");
44
- rb_str_resize(stream->buffer, 1 << 16); // 64KB
45
- rb_str_set_len(stream->buffer, 0);
46
-
47
- stream->len = 0;
48
- stream->pos = 0;
49
- stream->eof = 0;
50
-
51
- return self;
86
+ static inline void stream_setup(struct um_stream *stream, VALUE target, VALUE mode) {
87
+ stream->working_buffer = NULL;
88
+ if (mode == SYM_bp_read || mode == Qnil) {
89
+ stream->mode = STREAM_BP_READ;
90
+ stream->fd = NUM2INT(target);
91
+ }
92
+ else if (mode == SYM_bp_recv) {
93
+ stream->mode = STREAM_BP_RECV;
94
+ stream->fd = NUM2INT(target);
95
+ }
96
+ else if (mode == SYM_ssl) {
97
+ stream->mode = STREAM_SSL;
98
+ stream->target = target;
99
+ um_ssl_set_bio(stream->machine, target);
100
+ }
101
+ else
102
+ rb_raise(eUMError, "Invalid stream mode");
52
103
  }
53
104
 
54
- VALUE Stream_machine(VALUE self) {
55
- struct um_stream *stream = Stream_data(self);
56
- return stream->machine->self;
57
- }
105
+ VALUE Stream_initialize(int argc, VALUE *argv, VALUE self) {
106
+ VALUE machine;
107
+ VALUE target;
108
+ VALUE mode;
109
+ rb_scan_args(argc, argv, "21", &machine, &target, &mode);
58
110
 
59
- VALUE Stream_fd(VALUE self) {
60
- struct um_stream *stream = Stream_data(self);
61
- return ULONG2NUM(stream->fd);
62
- }
111
+ struct um_stream *stream = um_get_stream(self);
112
+ memset(stream, 0, sizeof(struct um_stream));
63
113
 
64
- VALUE Stream_get_line(VALUE self, VALUE buf, VALUE limit) {
65
- struct um_stream *stream = Stream_data(self);
66
- if (unlikely(stream->eof)) return Qnil;
114
+ RB_OBJ_WRITE(self, &stream->self, self);
115
+ stream->machine = um_get_machine(machine);
116
+ stream_setup(stream, target, mode);
67
117
 
68
- return stream_get_line(stream, buf, NUM2LONG(limit));
118
+ return self;
69
119
  }
70
120
 
71
- VALUE Stream_get_string(VALUE self, VALUE buf, VALUE len) {
72
- struct um_stream *stream = Stream_data(self);
73
- if (unlikely(stream->eof)) return Qnil;
74
-
75
- return stream_get_string(stream, buf, NUM2LONG(len));
121
+ VALUE Stream_mode(VALUE self) {
122
+ struct um_stream *stream = um_get_stream(self);
123
+ switch (stream->mode) {
124
+ case STREAM_BP_READ: return SYM_bp_read;
125
+ case STREAM_BP_RECV: return SYM_bp_recv;
126
+ case STREAM_SSL: return SYM_ssl;
127
+ default: return Qnil;
128
+ }
129
+ return Qnil;
76
130
  }
77
131
 
78
- // skips `len` bytes in the stream. This function returns nil if eof is
79
- // encountered.
80
- VALUE Stream_skip(VALUE self, VALUE len) {
81
- struct um_stream *stream = Stream_data(self);
132
+ VALUE Stream_get_line(VALUE self, VALUE limit) {
133
+ struct um_stream *stream = um_get_stream(self);
134
+ return stream_get_line(stream, Qnil, NUM2ULONG(limit));
135
+ }
82
136
 
83
- return stream_skip(stream, NUM2LONG(len));
137
+ VALUE Stream_get_string(VALUE self, VALUE len) {
138
+ struct um_stream *stream = um_get_stream(self);
139
+ return stream_get_string(stream, Qnil, NUM2LONG(len), 0, false);
84
140
  }
85
141
 
86
142
  VALUE Stream_resp_decode(VALUE self) {
87
- struct um_stream *stream = Stream_data(self);
88
- if (unlikely(stream->eof)) return Qnil;
89
-
143
+ struct um_stream *stream = um_get_stream(self);
90
144
  VALUE out_buffer = rb_utf8_str_new_literal("");
91
145
  VALUE obj = resp_decode(stream, out_buffer);
92
146
  RB_GC_GUARD(out_buffer);
@@ -102,34 +156,36 @@ VALUE Stream_resp_encode(VALUE self, VALUE str, VALUE obj) {
102
156
  return str;
103
157
  }
104
158
 
105
- VALUE Stream_resp_encode_cmd(int argc, VALUE *argv, VALUE self) {
106
- struct um_write_buffer buf;
107
- VALUE str;
108
- rb_check_arity(argc, 2, UNLIMITED_ARGUMENTS);
109
- str = argv[0];
110
- write_buffer_init(&buf, str);
111
- rb_str_modify(str);
112
- resp_encode_cmd(&buf, argc - 1, argv + 1);
113
- write_buffer_update_len(&buf);
114
- return str;
159
+ VALUE Stream_eof_p(VALUE self) {
160
+ struct um_stream *stream = um_get_stream(self);
161
+ return stream->eof ? Qtrue : Qfalse;
162
+ }
163
+
164
+ VALUE Stream_clear(VALUE self) {
165
+ struct um_stream *stream = um_get_stream(self);
166
+ stream_clear(stream);
167
+ return self;
115
168
  }
116
169
 
117
170
  void Init_Stream(void) {
118
- VALUE cStream = rb_define_class_under(cUM, "Stream", rb_cObject);
171
+ cStream = rb_define_class_under(cUM, "Stream", rb_cObject);
119
172
  rb_define_alloc_func(cStream, Stream_allocate);
120
173
 
121
- rb_define_method(cStream, "initialize", Stream_initialize, 2);
122
- rb_define_method(cStream, "machine", Stream_machine, 0);
123
- rb_define_method(cStream, "fd", Stream_fd, 0);
174
+ rb_define_method(cStream, "initialize", Stream_initialize, -1);
175
+ rb_define_method(cStream, "mode", Stream_mode, 0);
124
176
 
125
- rb_define_method(cStream, "get_line", Stream_get_line, 2);
126
- rb_define_method(cStream, "get_string", Stream_get_string, 2);
127
- rb_define_method(cStream, "skip", Stream_skip, 1);
177
+ rb_define_method(cStream, "get_line", Stream_get_line, 1);
178
+ rb_define_method(cStream, "get_string", Stream_get_string, 1);
128
179
 
129
180
  rb_define_method(cStream, "resp_decode", Stream_resp_decode, 0);
130
-
131
181
  rb_define_singleton_method(cStream, "resp_encode", Stream_resp_encode, 2);
132
- rb_define_singleton_method(cStream, "resp_encode_cmd", Stream_resp_encode_cmd, -1);
182
+
183
+ rb_define_method(cStream, "eof?", Stream_eof_p, 0);
184
+ rb_define_method(cStream, "clear", Stream_clear, 0);
133
185
 
134
186
  eStreamRESPError = rb_define_class_under(cStream, "RESPError", rb_eStandardError);
187
+
188
+ SYM_bp_read = ID2SYM(rb_intern("bp_read"));
189
+ SYM_bp_recv = ID2SYM(rb_intern("bp_recv"));
190
+ SYM_ssl = ID2SYM(rb_intern("ssl"));
135
191
  }
data/ext/um/um_utils.c CHANGED
@@ -62,16 +62,16 @@ inline void * um_prepare_read_buffer(VALUE buffer, ssize_t len, ssize_t ofs) {
62
62
  return RSTRING_PTR(buffer) + ofs;
63
63
  }
64
64
  else if (IO_BUFFER_P(buffer)) {
65
- void *base;
65
+ char *base;
66
66
  size_t size;
67
- rb_io_buffer_get_bytes_for_writing(buffer, &base, &size); // writing *to* buffer
67
+ rb_io_buffer_get_bytes_for_writing(buffer, (void *)&base, &size); // writing *to* buffer
68
68
  if (len == -1) len = size;
69
69
  if (ofs < 0) ofs = size + ofs + 1;
70
70
  size_t new_size = len + (size_t)ofs;
71
71
 
72
72
  if (size < new_size) {
73
73
  rb_io_buffer_resize(buffer, new_size);
74
- rb_io_buffer_get_bytes_for_writing(buffer, &base, &size);
74
+ rb_io_buffer_get_bytes_for_writing(buffer, (void *)&base, &size);
75
75
  }
76
76
  return base + ofs;
77
77
  }
@@ -148,7 +148,7 @@ int um_setup_buffer_ring(struct um *machine, unsigned size, unsigned count) {
148
148
  um_raise_internal_error("Failed to allocate buffers");
149
149
  }
150
150
 
151
- void *ptr = desc->buf_base;
151
+ char *ptr = desc->buf_base;
152
152
  for (unsigned i = 0; i < desc->buf_count; i++) {
153
153
  io_uring_buf_ring_add(desc->br, ptr, desc->buf_size, i, desc->buf_mask, i);
154
154
  ptr += desc->buf_size;
@@ -164,7 +164,7 @@ inline VALUE um_get_string_from_buffer_ring(struct um *machine, int bgid, __s32
164
164
 
165
165
  unsigned buf_idx = flags >> IORING_CQE_BUFFER_SHIFT;
166
166
  struct buf_ring_descriptor *desc = machine->buffer_rings + bgid;
167
- char *src = desc->buf_base + desc->buf_size * buf_idx;
167
+ char *src = (char *)desc->buf_base + desc->buf_size * buf_idx;
168
168
  // TODO: add support for UTF8
169
169
  // buf = rd->utf8_encoding ? rb_utf8_str_new(src, cqe->res) : rb_str_new(src, cqe->res);
170
170
  VALUE buf = rb_str_new(src, result);
@@ -224,7 +224,7 @@ inline struct iovec *um_alloc_iovecs_for_writing(int argc, VALUE *argv, size_t *
224
224
  inline void um_advance_iovecs_for_writing(struct iovec **ptr, int *len, size_t adv) {
225
225
  while (adv) {
226
226
  if (adv < (*ptr)->iov_len) {
227
- (*ptr)->iov_base += adv;
227
+ (*ptr)->iov_base = (char *)(*ptr)->iov_base + adv;
228
228
  (*ptr)->iov_len -= adv;
229
229
  return;
230
230
  }
data/grant-2025/tasks.md CHANGED
@@ -24,15 +24,19 @@
24
24
 
25
25
  https://www.man7.org/linux/man-pages/man7/inotify.7.html
26
26
 
27
- - [ ] Better buffer management
27
+ - [v] Better buffer management
28
28
  - [v] Add `UM#sendv` method (see below)
29
29
  - [v] Benchmark `#sendv` vs `#send_bundle` (in concurrent situation)
30
30
  - [v] Support for `IO::Buffer`?
31
31
  - [ ] Benchmark `#read_each` vs `#read` (in concurrent situation)
32
- - [ ] Implement automatic buffer pool:
33
- - [ ] Automatic buffer allocation,registration and management.
34
- - [ ] Support for partial buffer consumption.
35
- - [ ] Data processing through a rewritten stream implementation.
32
+ - [v] Implement automatic buffer pool:
33
+ - [v] Automatic buffer allocation,registration and management.
34
+ - [v] Support for partial buffer consumption.
35
+ - [v] Data processing through a rewritten stream implementation.
36
+
37
+ - [ ] More benchmarks
38
+ - Different reading methods in concurrent setting
39
+ - SSL vs SSL-custom-BIO vs SSL-stream
36
40
 
37
41
  - [v] Sidecar mode
38
42
  - [v] Convert `UM#initialize` to take kwargs
@@ -147,9 +151,10 @@
147
151
  - [ ] docs (similar to papercraft docs)
148
152
 
149
153
  - [ ] Uma - web server
150
- - [ ] Rack 3.0-compatible
154
+ - [v] Rack 3.0-compatible
151
155
  see: https://github.com/socketry/protocol-rack
152
156
  - [ ] Rails integration (Railtie)
153
157
  see: https://github.com/socketry/falcon
158
+ - [ ] Hanami?
154
159
  - [ ] Benchmarks
155
- - [ ] Add to the TechEmpower benchmarks
160
+ - [ ] Add to the TechEmpower benchmarks?
@@ -77,9 +77,11 @@ class UringMachine
77
77
  # concurrent applications in Ruby, in tight integration with the standard Ruby
78
78
  # I/O and locking APIs.
79
79
  class FiberScheduler
80
+ BLOCKING_OP_SUPPORT = ENV['BLOCKING_OP_SUPPORT']
81
+
80
82
 
81
83
  # The blocking operation thread pool is shared by all fiber schedulers.
82
- DEFAULT_THREAD_POOL = BlockingOperationThreadPool.new
84
+ DEFAULT_THREAD_POOL = BLOCKING_OP_SUPPORT && BlockingOperationThreadPool.new
83
85
 
84
86
  # UringMachine instance associated with scheduler.
85
87
  attr_reader :machine
@@ -114,6 +116,7 @@ class UringMachine
114
116
  # @param block [Proc] fiber block
115
117
  # @return [Fiber]
116
118
  def fiber(&block)
119
+ # p fiber: [block]
117
120
  fiber = Fiber.new(blocking: false) { @machine.run(fiber, &block) }
118
121
 
119
122
  @fiber_map[fiber] = true
@@ -141,16 +144,21 @@ class UringMachine
141
144
  @fiber_map = ObjectSpace::WeakMap.new
142
145
  end
143
146
 
144
- @machine.await_fibers(fibers)
147
+ @machine.await(fibers)
145
148
  end
146
149
 
147
- # Runs the given operation in a separate thread, so as not to block other
148
- # fibers.
149
- #
150
- # @param op [callable] blocking operation
151
- # @return [void]
152
- def blocking_operation_wait(op)
153
- @thread_pool.process(@machine, op)
150
+ if BLOCKING_OP_SUPPORT
151
+
152
+ # Runs the given operation in a separate thread, so as not to block other
153
+ # fibers.
154
+ #
155
+ # @param op [callable] blocking operation
156
+ # @return [void]
157
+ def blocking_operation_wait(op)
158
+ # p blocking_operation_wait: [op]
159
+ @thread_pool.process(@machine, op)
160
+ end
161
+
154
162
  end
155
163
 
156
164
  # Blocks the current fiber by yielding to the machine. This hook is called
@@ -160,6 +168,7 @@ class UringMachine
160
168
  # @param timeout [Number, nil] optional timeout
161
169
  # @return [bool] was the operation successful
162
170
  def block(blocker, timeout = nil)
171
+ # p block: [blocker, timeout]
163
172
  if timeout
164
173
  @machine.timeout(timeout, Timeout::Error) { @machine.yield }
165
174
  else
@@ -178,6 +187,7 @@ class UringMachine
178
187
  # @param fiber [Fiber] fiber to resume
179
188
  # @return [void]
180
189
  def unblock(blocker, fiber)
190
+ # p unblock: [blocker, fiber]
181
191
  @machine.schedule(fiber, nil)
182
192
  @machine.wakeup if Thread.current != @thread
183
193
  end
@@ -187,11 +197,13 @@ class UringMachine
187
197
  # @param duration [Number, nil] sleep duration
188
198
  # @return [void]
189
199
  def kernel_sleep(duration = nil)
200
+ # p kernel_sleep: duration
190
201
  duration ? @machine.sleep(duration) : @machine.yield
191
202
  end
192
203
 
193
204
  # Yields to the next runnable fiber.
194
205
  def yield
206
+ # p yield: []
195
207
  @machine.snooze
196
208
  end
197
209
 
@@ -202,6 +214,7 @@ class UringMachine
202
214
  # @param timeout [Number, nil] optional timeout
203
215
  # @return [void]
204
216
  def io_wait(io, events, timeout = nil)
217
+ # p io_wait: [io, events, timeout]
205
218
  timeout ||= io.timeout
206
219
  if timeout
207
220
  @machine.timeout(timeout, Timeout::Error) {
@@ -219,6 +232,7 @@ class UringMachine
219
232
  # @param eios [Array<IO>] exceptable IOs
220
233
  # @param timeout [Number, nil] optional timeout
221
234
  def io_select(rios, wios, eios, timeout = nil)
235
+ # p io_select: [rios, wios, eios, timeout]
222
236
  map_r = map_fds(rios)
223
237
  map_w = map_fds(wios)
224
238
  map_e = map_fds(eios)
@@ -243,6 +257,7 @@ class UringMachine
243
257
  # @param offset [Integer] buffer offset
244
258
  # @return [Integer] bytes read
245
259
  def io_read(io, buffer, length, offset)
260
+ # p io_read: [io, buffer, length, offset]
246
261
  length = buffer.size if length == 0
247
262
 
248
263
  if (timeout = io.timeout)
@@ -269,6 +284,7 @@ class UringMachine
269
284
  # @param offset [Integer] buffer offset
270
285
  # @return [Integer] bytes read
271
286
  def io_pread(io, buffer, from, length, offset)
287
+ # p io_pread: [io, buffer, from, length, offset]
272
288
  length = buffer.size if length == 0
273
289
 
274
290
  if (timeout = io.timeout)
@@ -294,6 +310,7 @@ class UringMachine
294
310
  # @param offset [Integer] write offset
295
311
  # @return [Integer] bytes written
296
312
  def io_write(io, buffer, length, offset)
313
+ # p io_write: [io, buffer, length, offset]
297
314
  length = buffer.size if length == 0
298
315
  buffer = buffer.slice(offset) if offset > 0
299
316
 
@@ -321,6 +338,7 @@ class UringMachine
321
338
  # @param offset [Integer] buffer offset
322
339
  # @return [Integer] bytes written
323
340
  def io_pwrite(io, buffer, from, length, offset)
341
+ # p io_pwrite: [io, buffer, from, length, offset]
324
342
  length = buffer.size if length == 0
325
343
  buffer = buffer.slice(offset) if offset > 0
326
344
 
@@ -344,6 +362,7 @@ class UringMachine
344
362
  # @param fd [Integer] file descriptor
345
363
  # @return [Integer] file descriptor
346
364
  def io_close(fd)
365
+ # p io_close: [fd]
347
366
  @machine.close_async(fd)
348
367
  rescue Errno => e
349
368
  -e.errno
@@ -368,6 +387,7 @@ class UringMachine
368
387
  # @param exception [Exception] Exception
369
388
  # @return [void]
370
389
  def fiber_interrupt(fiber, exception)
390
+ # p fiber_interrupt: [fiber, exception]
371
391
  @machine.schedule(fiber, exception)
372
392
  @machine.wakeup
373
393
  end
@@ -377,6 +397,7 @@ class UringMachine
377
397
  # @param hostname [String] hostname to resolve
378
398
  # @return [Array<Addrinfo>] array of resolved addresses
379
399
  def address_resolve(hostname)
400
+ # p address_resolve: [hostname]
380
401
  Resolv.getaddresses(hostname)
381
402
  end
382
403
 
@@ -388,6 +409,7 @@ class UringMachine
388
409
  # @param block [Proc] block to run
389
410
  # @return [any] block return value
390
411
  def timeout_after(duration, exception, message, &block)
412
+ # p timeout_after: [duration, exception, message, block]
391
413
  @machine.timeout(duration, exception, &block)
392
414
  end
393
415
 
@@ -397,7 +419,11 @@ class UringMachine
397
419
  #
398
420
  # @param o [any]
399
421
  # @return [void]
400
- def p(o) = UM.debug(o.inspect)
422
+ def p(o)
423
+ UM.debug(o.inspect)
424
+ UM.debug(caller[2..12].inspect)
425
+ UM.debug("")
426
+ end
401
427
 
402
428
  # Maps the given ios to fds.
403
429
  #
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class UringMachine
4
- VERSION = '0.28.3'
4
+ VERSION = '0.29.0'
5
5
  end
data/lib/uringmachine.rb CHANGED
@@ -46,7 +46,7 @@ class UringMachine
46
46
  def terminate(*fibers)
47
47
  fibers = fibers.first if fibers.size == 1 && fibers.first.is_a?(Enumerable)
48
48
 
49
- fibers.each { schedule(it, TERMINATE_EXCEPTION) }
49
+ fibers.each { schedule(it, TERMINATE_EXCEPTION) unless it.done? }
50
50
  end
51
51
 
52
52
  # Runs the given block in the given fiber. This method is used to run fibers
@@ -62,24 +62,49 @@ class UringMachine
62
62
  fiber
63
63
  end
64
64
 
65
- # Waits for the given fibers to terminate.
65
+ # Waits for the given fibers to terminate, returning the return value for each
66
+ # given fiber. This method also accepts procs instead of fibers. When a proc
67
+ # is given, it is ran in a separate fiber which will be joined.
66
68
  #
67
- # @return [Array<any>] return values of the given fibers
69
+ # machine.join(
70
+ # -> { machine.sleep(0.01); :f1 },
71
+ # -> { machine.sleep(0.02); :f2 },
72
+ # -> { machine.sleep(0.03); :f3 }
73
+ # ) #=> [:f1, :f2, :f3]
74
+ #
75
+ # @param *fibers [Array<Fiber, Proc>] fibers @return [Array<any>] return
76
+ # values of the given fibers
68
77
  def join(*fibers)
69
- fibers = fibers.first if fibers.size == 1 && fibers.first.is_a?(Enumerable)
78
+ queue = Fiber.current.mailbox
79
+
80
+ if fibers.size == 1
81
+ first = fibers.first
82
+ case first
83
+ when Enumerable
84
+ fibers = first
85
+ when Fiber
86
+ first = proc_spin(first) if first.is_a?(Proc)
87
+ if !first.done?
88
+ first.add_done_listener(queue)
89
+ self.shift(queue)
90
+ end
91
+ return first.result
92
+ end
93
+ end
70
94
 
71
- results = fibers.inject({}) { |h, f| h[f] = nil; h }
72
- queue = nil
95
+ results = {}
73
96
  pending = nil
74
97
  fibers.each do |f|
98
+ f = proc_spin(f) if f.is_a?(Proc)
75
99
  if f.done?
76
100
  results[f] = f.result
77
101
  else
102
+ results[f] = nil
78
103
  (pending ||= []) << f
79
- queue ||= Fiber.current.mailbox
80
104
  f.add_done_listener(queue)
81
105
  end
82
106
  end
107
+
83
108
  if pending
84
109
  while !pending.empty?
85
110
  f = self.shift(queue)
@@ -96,23 +121,29 @@ class UringMachine
96
121
  #
97
122
  # @param fibers [Fiber, Array<Fiber>] fibers to wait for
98
123
  # @return [Integer] number of fibers
99
- def await_fibers(fibers)
100
- if fibers.is_a?(Fiber)
101
- f = fibers
102
- if !f.done?
103
- queue = Fiber.current.mailbox
104
- f.add_done_listener(queue)
105
- self.shift(queue)
124
+ def await(*fibers)
125
+ queue = Fiber.current.mailbox
126
+
127
+ if fibers.size == 1
128
+ first = fibers.first
129
+ case first
130
+ when Enumerable
131
+ fibers = first
132
+ when Fiber
133
+ first = proc_spin(first) if first.is_a?(Proc)
134
+ if !first.done?
135
+ first.add_done_listener(queue)
136
+ self.shift(queue)
137
+ end
138
+ return 1
106
139
  end
107
- return 1
108
140
  end
109
141
 
110
- queue = nil
111
142
  pending = nil
112
143
  fibers.each do |f|
144
+ f = proc_spin(f) if f.is_a?(Proc)
113
145
  if !f.done?
114
146
  (pending ||= []) << f
115
- queue ||= Fiber.current.mailbox
116
147
  f.add_done_listener(queue)
117
148
  end
118
149
  end
@@ -176,11 +207,11 @@ class UringMachine
176
207
  fiber.set_result(e)
177
208
  ensure
178
209
  fiber.mark_as_done
179
- # cleanup
180
210
  fiber_set.delete(fiber)
181
211
  self.notify_done_listeners(fiber)
182
212
 
183
- # switch away to a different fiber
213
+ # finally switch away to a different fiber, the current fiber should not be
214
+ # resumed after this.
184
215
  self.switch
185
216
  end
186
217
 
@@ -215,6 +246,16 @@ class UringMachine
215
246
  }
216
247
  end
217
248
 
249
+ # @param proc [Proc]
250
+ # @return [Fiber]
251
+ def proc_spin(proc)
252
+ if proc.arity == 0
253
+ spin { proc.call }
254
+ else
255
+ spin(&proc)
256
+ end
257
+ end
258
+
218
259
  # Fiber extensions
219
260
  module FiberExtensions
220
261
  attr_reader :result, :done, :done_listeners
data/test/helper.rb CHANGED
@@ -68,6 +68,8 @@ class UMBaseTest < Minitest::Test
68
68
  def teardown
69
69
  return if !@machine
70
70
 
71
+ @machine = nil
72
+
71
73
  # pending_fibers = @machine.pending_fibers
72
74
  # raise "leaked fibers: #{pending_fibers}" if pending_fibers.size > 0
73
75