uringmachine 0.21.0 → 0.22.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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +2 -0
  3. data/CHANGELOG.md +14 -0
  4. data/TODO.md +144 -0
  5. data/benchmark/README.md +173 -0
  6. data/benchmark/bm_io_pipe.rb +70 -0
  7. data/benchmark/bm_io_socketpair.rb +71 -0
  8. data/benchmark/bm_mutex_cpu.rb +57 -0
  9. data/benchmark/bm_mutex_io.rb +64 -0
  10. data/benchmark/bm_pg_client.rb +109 -0
  11. data/benchmark/bm_queue.rb +76 -0
  12. data/benchmark/chart.png +0 -0
  13. data/benchmark/common.rb +135 -0
  14. data/benchmark/dns_client.rb +47 -0
  15. data/{examples/bm_http_parse.rb → benchmark/http_parse.rb} +1 -1
  16. data/benchmark/run_bm.rb +8 -0
  17. data/benchmark/sqlite.rb +108 -0
  18. data/{examples/bm_write.rb → benchmark/write.rb} +4 -4
  19. data/ext/um/um.c +189 -100
  20. data/ext/um/um.h +36 -10
  21. data/ext/um/um_async_op.c +1 -1
  22. data/ext/um/um_class.c +87 -13
  23. data/ext/um/um_op.c +6 -0
  24. data/ext/um/um_sync.c +2 -2
  25. data/ext/um/um_utils.c +16 -0
  26. data/grant-2025/journal.md +118 -1
  27. data/grant-2025/tasks.md +48 -22
  28. data/lib/uringmachine/actor.rb +8 -0
  29. data/lib/uringmachine/dns_resolver.rb +1 -2
  30. data/lib/uringmachine/fiber_scheduler.rb +127 -81
  31. data/lib/uringmachine/version.rb +1 -1
  32. data/lib/uringmachine.rb +32 -3
  33. data/test/helper.rb +7 -18
  34. data/test/test_actor.rb +12 -3
  35. data/test/test_async_op.rb +10 -10
  36. data/test/test_fiber.rb +84 -1
  37. data/test/test_fiber_scheduler.rb +950 -47
  38. data/test/test_um.rb +297 -120
  39. data/uringmachine.gemspec +2 -1
  40. metadata +38 -16
  41. data/examples/bm_fileno.rb +0 -33
  42. data/examples/bm_queue.rb +0 -111
  43. data/examples/bm_side_running.rb +0 -83
  44. data/examples/bm_sqlite.rb +0 -89
  45. data/examples/dns_client.rb +0 -12
  46. /data/{examples/bm_mutex.rb → benchmark/mutex.rb} +0 -0
  47. /data/{examples/bm_mutex_single.rb → benchmark/mutex_single.rb} +0 -0
  48. /data/{examples/bm_send.rb → benchmark/send.rb} +0 -0
  49. /data/{examples/bm_snooze.rb → benchmark/snooze.rb} +0 -0
data/ext/um/um.c CHANGED
@@ -4,7 +4,7 @@
4
4
  #include <assert.h>
5
5
  #include <poll.h>
6
6
 
7
- #define DEFAULT_ENTRIES 4096
7
+ #define DEFAULT_SIZE 4096
8
8
 
9
9
  inline void prepare_io_uring_params(struct io_uring_params *params, uint sqpoll_timeout_msec) {
10
10
  memset(params, 0, sizeof(struct io_uring_params));
@@ -17,17 +17,18 @@ inline void prepare_io_uring_params(struct io_uring_params *params, uint sqpoll_
17
17
  params->flags |= IORING_SETUP_COOP_TASKRUN;
18
18
  }
19
19
 
20
- void um_setup(VALUE self, struct um *machine, uint entries, uint sqpoll_timeout_msec) {
20
+ void um_setup(VALUE self, struct um *machine, uint size, uint sqpoll_timeout_msec) {
21
21
  memset(machine, 0, sizeof(struct um));
22
22
 
23
23
  RB_OBJ_WRITE(self, &machine->self, self);
24
+ RB_OBJ_WRITE(self, &machine->pending_fibers, rb_hash_new());
24
25
 
25
- machine->entries = (entries > 0) ? entries : DEFAULT_ENTRIES;
26
+ machine->size = (size > 0) ? size : DEFAULT_SIZE;
26
27
  machine->sqpoll_mode = !!sqpoll_timeout_msec;
27
28
 
28
29
  struct io_uring_params params;
29
30
  prepare_io_uring_params(&params, sqpoll_timeout_msec);
30
- int ret = io_uring_queue_init_params(machine->entries, &machine->ring, &params);
31
+ int ret = io_uring_queue_init_params(machine->size, &machine->ring, &params);
31
32
  if (ret) rb_syserr_fail(-ret, strerror(-ret));
32
33
  machine->ring_initialized = 1;
33
34
  }
@@ -48,15 +49,16 @@ inline void um_teardown(struct um *machine) {
48
49
  }
49
50
 
50
51
  inline struct io_uring_sqe *um_get_sqe(struct um *machine, struct um_op *op) {
51
- if (DEBUG) fprintf(stderr, "-> %p um_get_sqe: op->kind=%s unsubmitted=%d pending=%d total=%lu\n",
52
- &machine->ring, op ? um_op_kind_name(op->kind) : "NULL", machine->unsubmitted_count,
53
- machine->pending_count, machine->total_op_count
52
+ DEBUG_PRINTF("-> %p um_get_sqe: op %p kind=%s unsubmitted=%d pending=%d total=%lu\n",
53
+ &machine->ring, op, um_op_kind_name(op ? op->kind : OP_UNDEFINED),
54
+ machine->metrics.ops_unsubmitted, machine->metrics.ops_pending, machine->metrics.total_ops
54
55
  );
55
56
 
56
57
  struct io_uring_sqe *sqe;
57
58
  sqe = io_uring_get_sqe(&machine->ring);
58
59
  if (likely(sqe)) goto done;
59
60
 
61
+ fprintf(stderr, "!!!Failed to get SQE\n");
60
62
  um_raise_internal_error("Failed to get SQE");
61
63
 
62
64
  // TODO: retry getting SQE?
@@ -70,10 +72,10 @@ inline struct io_uring_sqe *um_get_sqe(struct um *machine, struct um_op *op) {
70
72
  done:
71
73
  sqe->user_data = (long long)op;
72
74
  sqe->flags = 0;
73
- machine->unsubmitted_count++;
75
+ machine->metrics.ops_unsubmitted++;
74
76
  if (op) {
75
- machine->pending_count++;
76
- machine->total_op_count++;
77
+ machine->metrics.ops_pending++;
78
+ machine->metrics.total_ops++;
77
79
  }
78
80
  return sqe;
79
81
  }
@@ -100,48 +102,48 @@ void *um_submit_without_gvl(void *ptr) {
100
102
  }
101
103
 
102
104
  inline uint um_submit(struct um *machine) {
103
- if (DEBUG) fprintf(stderr, "-> %p um_submit: unsubmitted=%d pending=%d total=%lu\n",
104
- &machine->ring, machine->unsubmitted_count, machine->pending_count, machine->total_op_count
105
+ DEBUG_PRINTF("-> %p um_submit: unsubmitted=%d pending=%d total=%lu\n",
106
+ &machine->ring, machine->metrics.ops_unsubmitted, machine->metrics.ops_pending,
107
+ machine->metrics.total_ops
105
108
  );
106
- if (!machine->unsubmitted_count) {
107
- if (DEBUG) fprintf(stderr, "<- %p um_submit: no unsubmitted SQEs, early return\n",
108
- &machine->ring
109
- );
109
+ if (!machine->metrics.ops_unsubmitted) {
110
+ DEBUG_PRINTF("<- %p um_submit: no unsubmitted SQEs, early return\n", &machine->ring);
110
111
  return 0;
111
112
  }
112
-
113
+
113
114
  struct um_submit_ctx ctx = { .machine = machine };
114
115
  if (sq_ring_needs_enter(machine))
115
116
  rb_thread_call_without_gvl(um_submit_without_gvl, (void *)&ctx, RUBY_UBF_IO, 0);
116
- else
117
+ else
117
118
  ctx.result = io_uring_submit(&machine->ring);
118
119
 
119
- if (DEBUG) fprintf(stderr, "<- %p um_submit: result=%d\n",
120
- &machine->ring, ctx.result
121
- );
120
+ DEBUG_PRINTF("<- %p um_submit: result=%d\n", &machine->ring, ctx.result);
122
121
 
123
122
  if (ctx.result < 0)
124
123
  rb_syserr_fail(-ctx.result, strerror(-ctx.result));
125
124
 
126
- machine->unsubmitted_count = 0;
125
+ machine->metrics.ops_unsubmitted = 0;
127
126
  return ctx.result;
128
127
  }
129
128
 
130
129
  static inline void um_process_cqe(struct um *machine, struct io_uring_cqe *cqe) {
131
130
  struct um_op *op = (struct um_op *)cqe->user_data;
132
131
  if (DEBUG) {
133
- if (op) fprintf(stderr, "<- %p um_process_cqe: op %p kind %s flags %d cqe_res %d cqe_flags %d pending %d\n",
134
- &machine->ring, op, um_op_kind_name(op->kind), op->flags, cqe->res, cqe->flags, machine->pending_count
135
- );
136
- else fprintf(stderr, "<- %p um_process_cqe: op NULL cqe_res %d cqe_flags %d pending %d\n",
137
- &machine->ring, cqe->res, cqe->flags, machine->pending_count
138
- );
132
+ if (op) {
133
+ DEBUG_PRINTF("<- %p um_process_cqe: op %p kind %s flags %d cqe_res %d cqe_flags %d pending %d\n",
134
+ &machine->ring, op, um_op_kind_name(op->kind), op->flags, cqe->res, cqe->flags, machine->metrics.ops_pending
135
+ );
136
+ }
137
+ else {
138
+ DEBUG_PRINTF("<- %p um_process_cqe: op NULL cqe_res %d cqe_flags %d pending %d\n",
139
+ &machine->ring, cqe->res, cqe->flags, machine->metrics.ops_pending
140
+ );
141
+ }
139
142
  }
140
143
  if (unlikely(!op)) return;
141
144
 
142
-
143
145
  if (!(cqe->flags & IORING_CQE_F_MORE))
144
- machine->pending_count--;
146
+ machine->metrics.ops_pending--;
145
147
 
146
148
  if (op->flags & OP_F_FREE_ON_COMPLETE) {
147
149
  if (op->flags & OP_F_TRANSIENT)
@@ -179,8 +181,8 @@ static inline int cq_ring_needs_flush(struct io_uring *ring) {
179
181
  }
180
182
 
181
183
  static inline int um_process_ready_cqes(struct um *machine) {
182
- if (DEBUG) fprintf(stderr, "-> %p um_process_ready_cqes: unsubmitted=%d pending=%d total=%lu\n",
183
- &machine->ring, machine->unsubmitted_count, machine->pending_count, machine->total_op_count
184
+ DEBUG_PRINTF("-> %p um_process_ready_cqes: unsubmitted=%d pending=%d total=%lu\n",
185
+ &machine->ring, machine->metrics.ops_unsubmitted, machine->metrics.ops_pending, machine->metrics.total_ops
184
186
  );
185
187
 
186
188
  unsigned total_count = 0;
@@ -199,9 +201,9 @@ iterate:
199
201
  if (overflow_checked) goto done;
200
202
 
201
203
  if (cq_ring_needs_flush(&machine->ring)) {
202
- if (DEBUG) fprintf(stderr, "-> %p io_uring_enter\n", &machine->ring);
204
+ DEBUG_PRINTF("-> %p io_uring_enter\n", &machine->ring);
203
205
  int ret = io_uring_enter(machine->ring.ring_fd, 0, 0, IORING_ENTER_GETEVENTS, NULL);
204
- if (DEBUG) fprintf(stderr, "<- %p io_uring_enter: result=%d\n", &machine->ring, ret);
206
+ DEBUG_PRINTF("<- %p io_uring_enter: result=%d\n", &machine->ring, ret);
205
207
  if (ret < 0)
206
208
  rb_syserr_fail(-ret, strerror(-ret));
207
209
 
@@ -210,9 +212,7 @@ iterate:
210
212
  }
211
213
 
212
214
  done:
213
- if (DEBUG) fprintf(stderr, "<- %p um_process_ready_cqes: total_processed=%u\n",
214
- &machine->ring, total_count
215
- );
215
+ DEBUG_PRINTF("<- %p um_process_ready_cqes: total_processed=%u\n", &machine->ring, total_count);
216
216
 
217
217
  return total_count;
218
218
  }
@@ -226,10 +226,10 @@ struct wait_for_cqe_ctx {
226
226
 
227
227
  void *um_wait_for_cqe_without_gvl(void *ptr) {
228
228
  struct wait_for_cqe_ctx *ctx = ptr;
229
- if (ctx->machine->unsubmitted_count) {
230
- if (DEBUG) fprintf(stderr, "-> %p io_uring_submit_and_wait_timeout: unsubmitted=%d pending=%d total=%lu\n",
231
- &ctx->machine->ring, ctx->machine->unsubmitted_count, ctx->machine->pending_count,
232
- ctx->machine->total_op_count
229
+ if (ctx->machine->metrics.ops_unsubmitted) {
230
+ DEBUG_PRINTF("-> %p io_uring_submit_and_wait_timeout: unsubmitted=%d pending=%d total=%lu\n",
231
+ &ctx->machine->ring, ctx->machine->metrics.ops_unsubmitted, ctx->machine->metrics.ops_pending,
232
+ ctx->machine->metrics.total_ops
233
233
  );
234
234
 
235
235
  // Attn: The io_uring_submit_and_wait_timeout will not return -EINTR if
@@ -238,31 +238,49 @@ void *um_wait_for_cqe_without_gvl(void *ptr) {
238
238
  //
239
239
  // https://github.com/axboe/liburing/issues/1280
240
240
  int ret = io_uring_submit_and_wait_timeout(&ctx->machine->ring, &ctx->cqe, ctx->wait_nr, NULL, NULL);
241
- ctx->machine->unsubmitted_count = 0;
242
- if (DEBUG) fprintf(stderr, "<- %p io_uring_submit_and_wait_timeout: result=%d\n",
243
- &ctx->machine->ring, ret
244
- );
241
+ ctx->machine->metrics.ops_unsubmitted = 0;
242
+ DEBUG_PRINTF("<- %p io_uring_submit_and_wait_timeout: result=%d\n", &ctx->machine->ring, ret);
245
243
  ctx->result = (ret > 0 && !ctx->cqe) ? -EINTR : ret;
246
244
  }
247
245
  else {
248
- if (DEBUG) fprintf(stderr, "-> %p io_uring_wait_cqes: unsubmitted=%d pending=%d total=%lu\n",
249
- &ctx->machine->ring, ctx->machine->unsubmitted_count, ctx->machine->pending_count,
250
- ctx->machine->total_op_count
246
+ DEBUG_PRINTF("-> %p io_uring_wait_cqes: unsubmitted=%d pending=%d total=%lu\n",
247
+ &ctx->machine->ring, ctx->machine->metrics.ops_unsubmitted, ctx->machine->metrics.ops_pending,
248
+ ctx->machine->metrics.total_ops
251
249
  );
252
250
  ctx->result = io_uring_wait_cqes(&ctx->machine->ring, &ctx->cqe, ctx->wait_nr, NULL, NULL);
253
- if (DEBUG) fprintf(stderr, "<- %p io_uring_wait_cqes: result=%d\n",
254
- &ctx->machine->ring, ctx->result
255
- );
251
+ DEBUG_PRINTF("<- %p io_uring_wait_cqes: result=%d\n", &ctx->machine->ring, ctx->result);
256
252
  }
257
253
  return NULL;
258
254
  }
259
255
 
256
+ inline void um_profile_wait_cqe_pre(struct um *machine, double *time_monotonic0, VALUE *fiber) {
257
+ // *fiber = rb_fiber_current();
258
+ *time_monotonic0 = um_get_time_monotonic();
259
+ // double time_cpu = um_get_time_cpu();
260
+ // double elapsed = time_cpu - machine->metrics.time_last_cpu;
261
+ // um_update_fiber_time_run(fiber, time_monotonic0, elapsed);
262
+ // machine->metrics.time_last_cpu = time_cpu;
263
+ }
264
+
265
+ inline void um_profile_wait_cqe_post(struct um *machine, double time_monotonic0, VALUE fiber) {
266
+ // double time_cpu = um_get_time_cpu();
267
+ double elapsed = um_get_time_monotonic() - time_monotonic0;
268
+ // um_update_fiber_last_time(fiber, cpu_time1);
269
+ machine->metrics.time_total_wait += elapsed;
270
+ // machine->metrics.time_last_cpu = time_cpu;
271
+ }
272
+
260
273
  // Waits for the given minimum number of completion entries. The wait_nr is
261
274
  // either 1 - where we wait for at least one CQE to be ready, or 0, where we
262
275
  // don't wait, and just process any CQEs that already ready.
263
276
  static inline void um_wait_for_and_process_ready_cqes(struct um *machine, int wait_nr) {
264
277
  struct wait_for_cqe_ctx ctx = { .machine = machine, .cqe = NULL, .wait_nr = wait_nr };
278
+ machine->metrics.total_waits++;
279
+ double time_monotonic0 = 0.0;
280
+ VALUE fiber;
281
+ if (machine->profile_mode) um_profile_wait_cqe_pre(machine, &time_monotonic0, &fiber);
265
282
  rb_thread_call_without_gvl(um_wait_for_cqe_without_gvl, (void *)&ctx, RUBY_UBF_IO, 0);
283
+ if (machine->profile_mode) um_profile_wait_cqe_post(machine, time_monotonic0, fiber);
266
284
 
267
285
  if (unlikely(ctx.result < 0)) {
268
286
  // the internal calls to (maybe submit) and wait for cqes may fail with:
@@ -286,22 +304,36 @@ static inline void um_wait_for_and_process_ready_cqes(struct um *machine, int wa
286
304
  }
287
305
  }
288
306
 
307
+ inline void um_profile_switch(struct um *machine, VALUE next_fiber) {
308
+ // *current_fiber = rb_fiber_current();
309
+ // double time_cpu = um_get_time_cpu();
310
+ // double elapsed = time_cpu - machine->metrics.time_last_cpu;
311
+ // um_update_fiber_time_run(cur_fiber, time_cpu, elapsed);
312
+ // um_update_fiber_time_wait(next_fiber, time_cpu);
313
+ // machine->metrics.time_last_cpu = time_cpu;
314
+ }
315
+
289
316
  inline VALUE process_runqueue_op(struct um *machine, struct um_op *op) {
317
+ DEBUG_PRINTF("-> %p process_runqueue_op: op %p\n", &machine->ring, op);
318
+
319
+ machine->metrics.total_switches++;
290
320
  VALUE fiber = op->fiber;
291
321
  VALUE value = op->value;
292
322
 
293
323
  if (unlikely(op->flags & OP_F_TRANSIENT))
294
324
  um_op_free(machine, op);
295
325
 
326
+ if (machine->profile_mode) um_profile_switch(machine, fiber);
296
327
  VALUE ret = rb_fiber_transfer(fiber, 1, &value);
297
328
  RB_GC_GUARD(value);
298
329
  RB_GC_GUARD(ret);
299
330
  return ret;
300
331
  }
301
332
 
302
- inline VALUE um_fiber_switch(struct um *machine) {
303
- if (DEBUG) fprintf(stderr, "-> %p um_fiber_switch: unsubmitted=%d pending=%d total=%lu\n",
304
- &machine->ring, machine->unsubmitted_count, machine->pending_count, machine->total_op_count
333
+ inline VALUE um_switch(struct um *machine) {
334
+ DEBUG_PRINTF("-> %p um_switch: unsubmitted=%d pending=%d total=%lu\n",
335
+ &machine->ring, machine->metrics.ops_unsubmitted, machine->metrics.ops_pending,
336
+ machine->metrics.total_ops
305
337
  );
306
338
  while (true) {
307
339
  struct um_op *op = um_runqueue_shift(machine);
@@ -316,7 +348,7 @@ inline VALUE um_fiber_switch(struct um *machine) {
316
348
  // is the op a snooze op and is this the same fiber as the current one?
317
349
  if (unlikely(op->kind == OP_SCHEDULE && op->fiber == rb_fiber_current())) {
318
350
  // are there any pending ops (i.e. waiting for completion)?
319
- if (machine->pending_count > 0) {
351
+ if (machine->metrics.ops_pending > 0) {
320
352
  // if yes, process completions, get runqueue head, put original op
321
353
  // back on runqueue.
322
354
  // um_process_ready_cqes(machine);
@@ -335,6 +367,14 @@ inline VALUE um_fiber_switch(struct um *machine) {
335
367
  }
336
368
  }
337
369
 
370
+ inline VALUE um_yield(struct um *machine) {
371
+ VALUE fiber = rb_fiber_current();
372
+ rb_hash_aset(machine->pending_fibers, fiber, Qtrue);
373
+ VALUE ret = um_switch(machine);
374
+ rb_hash_delete(machine->pending_fibers, fiber);
375
+ return ret;
376
+ }
377
+
338
378
  void um_cancel_op(struct um *machine, struct um_op *op) {
339
379
  struct io_uring_sqe *sqe = um_get_sqe(machine, NULL);
340
380
  io_uring_prep_cancel64(sqe, (long long)op, 0);
@@ -342,9 +382,13 @@ void um_cancel_op(struct um *machine, struct um_op *op) {
342
382
 
343
383
  inline void um_cancel_and_wait(struct um *machine, struct um_op *op) {
344
384
  um_cancel_op(machine, op);
385
+
386
+ VALUE fiber = rb_fiber_current();
387
+ rb_hash_aset(machine->pending_fibers, fiber, Qtrue);
345
388
  while (!um_op_completed_p(op)) {
346
- um_fiber_switch(machine);
389
+ um_switch(machine);
347
390
  }
391
+ rb_hash_delete(machine->pending_fibers, fiber);
348
392
  }
349
393
 
350
394
  inline int um_check_completion(struct um *machine, struct um_op *op) {
@@ -357,13 +401,6 @@ inline int um_check_completion(struct um *machine, struct um_op *op) {
357
401
  return 1;
358
402
  }
359
403
 
360
- inline VALUE um_await(struct um *machine) {
361
- VALUE ret = um_fiber_switch(machine);
362
- RAISE_IF_EXCEPTION(ret);
363
- RB_GC_GUARD(ret);
364
- return ret;
365
- }
366
-
367
404
  VALUE um_wakeup(struct um *machine) {
368
405
  struct io_uring_sqe *sqe = um_get_sqe(machine, NULL);
369
406
  io_uring_prep_nop(sqe);
@@ -449,7 +486,8 @@ VALUE um_sleep(struct um *machine, double duration) {
449
486
  op.ts = um_double_to_timespec(duration);
450
487
  struct io_uring_sqe *sqe = um_get_sqe(machine, &op);
451
488
  io_uring_prep_timeout(sqe, &op.ts, 0, 0);
452
- VALUE ret = um_fiber_switch(machine);
489
+
490
+ VALUE ret = um_yield(machine);
453
491
 
454
492
  if (!um_op_completed_p(&op))
455
493
  um_cancel_and_wait(machine, &op);
@@ -470,7 +508,8 @@ VALUE um_read(struct um *machine, int fd, VALUE buffer, size_t maxlen, ssize_t b
470
508
  void *ptr = um_prepare_read_buffer(buffer, maxlen, buffer_offset);
471
509
  io_uring_prep_read(sqe, fd, ptr, maxlen, file_offset);
472
510
 
473
- VALUE ret = um_fiber_switch(machine);
511
+ VALUE ret = um_yield(machine);
512
+
474
513
  if (um_check_completion(machine, &op)) {
475
514
  um_update_read_buffer(machine, buffer, buffer_offset, op.result.res, op.result.flags);
476
515
  ret = INT2NUM(op.result.res);
@@ -488,10 +527,10 @@ size_t um_read_raw(struct um *machine, int fd, char *buffer, size_t maxlen) {
488
527
  struct io_uring_sqe *sqe = um_get_sqe(machine, &op);
489
528
  io_uring_prep_read(sqe, fd, buffer, maxlen, -1);
490
529
 
491
- VALUE ret = um_fiber_switch(machine);
530
+ VALUE ret = um_yield(machine);
531
+
492
532
  if (um_check_completion(machine, &op)) {
493
533
  return op.result.res;
494
-
495
534
  }
496
535
 
497
536
  RAISE_IF_EXCEPTION(ret);
@@ -512,7 +551,8 @@ VALUE um_write(struct um *machine, int fd, VALUE buffer, size_t len, __u64 file_
512
551
 
513
552
  io_uring_prep_write(sqe, fd, base, len, file_offset);
514
553
 
515
- VALUE ret = um_fiber_switch(machine);
554
+ VALUE ret = um_yield(machine);
555
+
516
556
  if (um_check_completion(machine, &op))
517
557
  ret = INT2NUM(op.result.res);
518
558
 
@@ -530,10 +570,7 @@ VALUE um_write_async(struct um *machine, int fd, VALUE buffer, size_t len, __u64
530
570
 
531
571
  struct um_op *op = um_op_alloc(machine);
532
572
  um_prep_op(machine, op, OP_WRITE_ASYNC, OP_F_TRANSIENT | OP_F_FREE_ON_COMPLETE);
533
- RB_OBJ_WRITE(machine->self, &op->fiber, Qnil);
534
573
  RB_OBJ_WRITE(machine->self, &op->value, buffer);
535
- RB_OBJ_WRITE(machine->self, &op->async_op, Qnil);
536
-
537
574
 
538
575
  struct io_uring_sqe *sqe = um_get_sqe(machine, op);
539
576
  io_uring_prep_write(sqe, fd, base, len, file_offset);
@@ -548,7 +585,8 @@ VALUE um_close(struct um *machine, int fd) {
548
585
  struct io_uring_sqe *sqe = um_get_sqe(machine, &op);
549
586
  io_uring_prep_close(sqe, fd);
550
587
 
551
- VALUE ret = um_fiber_switch(machine);
588
+ VALUE ret = um_yield(machine);
589
+
552
590
  if (um_check_completion(machine, &op))
553
591
  ret = INT2NUM(fd);
554
592
 
@@ -560,9 +598,6 @@ VALUE um_close(struct um *machine, int fd) {
560
598
  VALUE um_close_async(struct um *machine, int fd) {
561
599
  struct um_op *op = um_op_alloc(machine);
562
600
  um_prep_op(machine, op, OP_CLOSE_ASYNC, OP_F_FREE_ON_COMPLETE);
563
- RB_OBJ_WRITE(machine->self, &op->fiber, Qnil);
564
- RB_OBJ_WRITE(machine->self, &op->value, Qnil);
565
- RB_OBJ_WRITE(machine->self, &op->async_op, Qnil);
566
601
 
567
602
  struct io_uring_sqe *sqe = um_get_sqe(machine, op);
568
603
  io_uring_prep_close(sqe, fd);
@@ -576,7 +611,8 @@ VALUE um_accept(struct um *machine, int fd) {
576
611
  struct io_uring_sqe *sqe = um_get_sqe(machine, &op);
577
612
  io_uring_prep_accept(sqe, fd, NULL, NULL, 0);
578
613
 
579
- VALUE ret = um_fiber_switch(machine);
614
+ VALUE ret = um_yield(machine);
615
+
580
616
  if (um_check_completion(machine, &op))
581
617
  ret = INT2NUM(op.result.res);
582
618
 
@@ -591,7 +627,8 @@ VALUE um_socket(struct um *machine, int domain, int type, int protocol, uint fla
591
627
  struct io_uring_sqe *sqe = um_get_sqe(machine, &op);
592
628
  io_uring_prep_socket(sqe, domain, type, protocol, flags);
593
629
 
594
- VALUE ret = um_fiber_switch(machine);
630
+ VALUE ret = um_yield(machine);
631
+
595
632
  if (um_check_completion(machine, &op))
596
633
  ret = INT2NUM(op.result.res);
597
634
 
@@ -606,7 +643,8 @@ VALUE um_connect(struct um *machine, int fd, const struct sockaddr *addr, sockle
606
643
  struct io_uring_sqe *sqe = um_get_sqe(machine, &op);
607
644
  io_uring_prep_connect(sqe, fd, addr, addrlen);
608
645
 
609
- VALUE ret = um_fiber_switch(machine);
646
+ VALUE ret = um_yield(machine);
647
+
610
648
  if (um_check_completion(machine, &op))
611
649
  ret = INT2NUM(op.result.res);
612
650
 
@@ -627,7 +665,8 @@ VALUE um_send(struct um *machine, int fd, VALUE buffer, size_t len, int flags) {
627
665
 
628
666
  io_uring_prep_send(sqe, fd, base, len, flags);
629
667
 
630
- VALUE ret = um_fiber_switch(machine);
668
+ VALUE ret = um_yield(machine);
669
+
631
670
  if (um_check_completion(machine, &op))
632
671
  ret = INT2NUM(op.result.res);
633
672
 
@@ -647,7 +686,8 @@ VALUE um_send_bundle(struct um *machine, int fd, int bgid, VALUE strings) {
647
686
  sqe->flags |= IOSQE_BUFFER_SELECT;
648
687
  sqe->buf_group = bgid;
649
688
 
650
- VALUE ret = um_fiber_switch(machine);
689
+ VALUE ret = um_yield(machine);
690
+
651
691
  if (um_check_completion(machine, &op))
652
692
  ret = INT2NUM(op.result.res);
653
693
 
@@ -664,7 +704,8 @@ VALUE um_recv(struct um *machine, int fd, VALUE buffer, size_t maxlen, int flags
664
704
 
665
705
  io_uring_prep_recv(sqe, fd, ptr, maxlen, flags);
666
706
 
667
- VALUE ret = um_fiber_switch(machine);
707
+ VALUE ret = um_yield(machine);
708
+
668
709
  if (um_check_completion(machine, &op)) {
669
710
  um_update_read_buffer(machine, buffer, 0, op.result.res, op.result.flags);
670
711
  ret = INT2NUM(op.result.res);
@@ -681,7 +722,8 @@ VALUE um_bind(struct um *machine, int fd, struct sockaddr *addr, socklen_t addrl
681
722
  struct io_uring_sqe *sqe = um_get_sqe(machine, &op);
682
723
  io_uring_prep_bind(sqe, fd, addr, addrlen);
683
724
 
684
- VALUE ret = um_fiber_switch(machine);
725
+ VALUE ret = um_yield(machine);
726
+
685
727
  if (um_check_completion(machine, &op))
686
728
  ret = INT2NUM(op.result.res);
687
729
 
@@ -696,7 +738,8 @@ VALUE um_listen(struct um *machine, int fd, int backlog) {
696
738
  struct io_uring_sqe *sqe = um_get_sqe(machine, &op);
697
739
  io_uring_prep_listen(sqe, fd, backlog);
698
740
 
699
- VALUE ret = um_fiber_switch(machine);
741
+ VALUE ret = um_yield(machine);
742
+
700
743
  if (um_check_completion(machine, &op))
701
744
  ret = INT2NUM(op.result.res);
702
745
 
@@ -714,7 +757,8 @@ VALUE um_getsockopt(struct um *machine, int fd, int level, int opt) {
714
757
  struct io_uring_sqe *sqe = um_get_sqe(machine, &op);
715
758
  io_uring_prep_cmd_sock(sqe, SOCKET_URING_OP_GETSOCKOPT, fd, level, opt, &value, sizeof(value));
716
759
 
717
- ret = um_fiber_switch(machine);
760
+ ret = um_yield(machine);
761
+
718
762
  if (um_check_completion(machine, &op))
719
763
  ret = INT2NUM(value);
720
764
 
@@ -731,7 +775,8 @@ VALUE um_setsockopt(struct um *machine, int fd, int level, int opt, int value) {
731
775
  struct io_uring_sqe *sqe = um_get_sqe(machine, &op);
732
776
  io_uring_prep_cmd_sock(sqe, SOCKET_URING_OP_SETSOCKOPT, fd, level, opt, &value, sizeof(value));
733
777
 
734
- ret = um_fiber_switch(machine);
778
+ ret = um_yield(machine);
779
+
735
780
  if (um_check_completion(machine, &op))
736
781
  ret = INT2NUM(op.result.res);
737
782
 
@@ -748,7 +793,8 @@ VALUE um_shutdown(struct um *machine, int fd, int how) {
748
793
  struct io_uring_sqe *sqe = um_get_sqe(machine, &op);
749
794
  io_uring_prep_shutdown(sqe, fd, how);
750
795
 
751
- ret = um_fiber_switch(machine);
796
+ ret = um_yield(machine);
797
+
752
798
  if (um_check_completion(machine, &op))
753
799
  ret = INT2NUM(op.result.res);
754
800
 
@@ -760,9 +806,6 @@ VALUE um_shutdown(struct um *machine, int fd, int how) {
760
806
  VALUE um_shutdown_async(struct um *machine, int fd, int how) {
761
807
  struct um_op *op = um_op_alloc(machine);
762
808
  um_prep_op(machine, op, OP_SHUTDOWN_ASYNC, OP_F_FREE_ON_COMPLETE);
763
- RB_OBJ_WRITE(machine->self, &op->fiber, Qnil);
764
- RB_OBJ_WRITE(machine->self, &op->value, Qnil);
765
- RB_OBJ_WRITE(machine->self, &op->async_op, Qnil);
766
809
 
767
810
  struct io_uring_sqe *sqe = um_get_sqe(machine, op);
768
811
  io_uring_prep_shutdown(sqe, fd, how);
@@ -776,7 +819,8 @@ VALUE um_open(struct um *machine, VALUE pathname, int flags, int mode) {
776
819
  struct io_uring_sqe *sqe = um_get_sqe(machine, &op);
777
820
  io_uring_prep_open(sqe, StringValueCStr(pathname), flags, mode);
778
821
 
779
- VALUE ret = um_fiber_switch(machine);
822
+ VALUE ret = um_yield(machine);
823
+
780
824
  if (um_check_completion(machine, &op))
781
825
  ret = INT2NUM(op.result.res);
782
826
 
@@ -791,12 +835,15 @@ VALUE um_poll(struct um *machine, int fd, unsigned mask) {
791
835
  struct io_uring_sqe *sqe = um_get_sqe(machine, &op);
792
836
  io_uring_prep_poll_add(sqe, fd, mask);
793
837
 
794
- VALUE ret = um_fiber_switch(machine);
838
+ VALUE ret = um_yield(machine);
839
+
795
840
  if (um_check_completion(machine, &op))
796
841
  ret = INT2NUM(op.result.res);
797
842
 
798
843
  RAISE_IF_EXCEPTION(ret);
799
844
  RB_GC_GUARD(ret);
845
+ RB_GC_GUARD(op.fiber);
846
+ RB_GC_GUARD(op.value);
800
847
  return ret;
801
848
  }
802
849
 
@@ -822,7 +869,8 @@ VALUE um_select_single(struct um *machine, VALUE rfds, VALUE wfds, VALUE efds, u
822
869
  prepare_select_poll_ops(machine, &idx, &op, efds, efds_len, OP_F_SELECT_POLLPRI, POLLPRI);
823
870
  assert(idx == 1);
824
871
 
825
- VALUE ret = um_fiber_switch(machine);
872
+ VALUE ret = um_yield(machine);
873
+
826
874
  um_check_completion(machine, &op);
827
875
  RAISE_IF_EXCEPTION(ret);
828
876
 
@@ -854,7 +902,7 @@ VALUE um_select(struct um *machine, VALUE rfds, VALUE wfds, VALUE efds) {
854
902
  prepare_select_poll_ops(machine, &idx, ops, efds, efds_len, OP_F_SELECT_POLLPRI, POLLPRI);
855
903
  assert(idx == total_len);
856
904
 
857
- VALUE ret = um_fiber_switch(machine);
905
+ VALUE ret = um_yield(machine);
858
906
  if (unlikely(um_value_is_exception_p(ret))) {
859
907
  free(ops);
860
908
  um_raise_exception(ret);
@@ -916,7 +964,8 @@ VALUE um_waitid(struct um *machine, int idtype, int id, int options) {
916
964
  siginfo_t infop;
917
965
  io_uring_prep_waitid(sqe, idtype, id, &infop, options, 0);
918
966
 
919
- VALUE ret = um_fiber_switch(machine);
967
+ VALUE ret = um_yield(machine);
968
+
920
969
  if (um_check_completion(machine, &op))
921
970
  ret = INT2NUM(op.result.res);
922
971
 
@@ -937,7 +986,7 @@ VALUE um_waitid_status(struct um *machine, int idtype, int id, int options) {
937
986
  siginfo_t infop;
938
987
  io_uring_prep_waitid(sqe, idtype, id, &infop, options | WNOWAIT, 0);
939
988
 
940
- VALUE ret = um_fiber_switch(machine);
989
+ VALUE ret = um_yield(machine);
941
990
  if (um_check_completion(machine, &op))
942
991
  ret = INT2NUM(op.result.res);
943
992
 
@@ -983,7 +1032,8 @@ VALUE um_statx(struct um *machine, int dirfd, VALUE path, int flags, unsigned in
983
1032
  memset(&stat, 0, sizeof(stat));
984
1033
  io_uring_prep_statx(sqe, dirfd, path_ptr, flags, mask, &stat);
985
1034
 
986
- VALUE ret = um_fiber_switch(machine);
1035
+ VALUE ret = um_yield(machine);
1036
+
987
1037
  if (um_check_completion(machine, &op))
988
1038
  ret = INT2NUM(op.result.res);
989
1039
 
@@ -1003,7 +1053,7 @@ VALUE accept_each_start(VALUE arg) {
1003
1053
  io_uring_prep_multishot_accept(sqe, ctx->fd, NULL, NULL, 0);
1004
1054
 
1005
1055
  while (true) {
1006
- VALUE ret = um_fiber_switch(ctx->machine);
1056
+ VALUE ret = um_yield(ctx->machine);
1007
1057
  if (!um_op_completed_p(ctx->op)) {
1008
1058
  RAISE_IF_EXCEPTION(ret);
1009
1059
  return ret;
@@ -1045,6 +1095,8 @@ VALUE multishot_complete(VALUE arg) {
1045
1095
  if (ctx->read_buf)
1046
1096
  free(ctx->read_buf);
1047
1097
 
1098
+ rb_hash_delete(ctx->machine->pending_fibers, ctx->op->fiber);
1099
+
1048
1100
  return Qnil;
1049
1101
  }
1050
1102
 
@@ -1067,7 +1119,7 @@ int um_read_each_singleshot_loop(struct op_ctx *ctx) {
1067
1119
  struct io_uring_sqe *sqe = um_get_sqe(ctx->machine, ctx->op);
1068
1120
  io_uring_prep_read(sqe, ctx->fd, ctx->read_buf, ctx->read_maxlen, -1);
1069
1121
 
1070
- VALUE ret = um_fiber_switch(ctx->machine);
1122
+ VALUE ret = um_yield(ctx->machine);
1071
1123
  if (um_op_completed_p(ctx->op)) {
1072
1124
  um_raise_on_error_result(ctx->op->result.res);
1073
1125
  if (!ctx->op->result.res) return total;
@@ -1128,7 +1180,7 @@ VALUE read_recv_each_start(VALUE arg) {
1128
1180
  int total = 0;
1129
1181
 
1130
1182
  while (true) {
1131
- VALUE ret = um_fiber_switch(ctx->machine);
1183
+ VALUE ret = um_yield(ctx->machine);
1132
1184
  if (!um_op_completed_p(ctx->op)) {
1133
1185
  RAISE_IF_EXCEPTION(ret);
1134
1186
  return ret;
@@ -1179,7 +1231,7 @@ VALUE periodically_start(VALUE arg) {
1179
1231
  io_uring_prep_timeout(sqe, &ctx->ts, 0, IORING_TIMEOUT_MULTISHOT);
1180
1232
 
1181
1233
  while (true) {
1182
- VALUE ret = um_fiber_switch(ctx->machine);
1234
+ VALUE ret = um_switch(ctx->machine);
1183
1235
  if (!um_op_completed_p(ctx->op)) {
1184
1236
  RAISE_IF_EXCEPTION(ret);
1185
1237
  return ret;
@@ -1215,3 +1267,40 @@ VALUE um_periodically(struct um *machine, double interval) {
1215
1267
  struct op_ctx ctx = { .machine = machine, .op = &op, .ts = op.ts, .read_buf = NULL };
1216
1268
  return rb_ensure(periodically_start, (VALUE)&ctx, multishot_complete, (VALUE)&ctx);
1217
1269
  }
1270
+
1271
+ extern VALUE SYM_size;
1272
+ extern VALUE SYM_total_ops;
1273
+ extern VALUE SYM_total_switches;
1274
+ extern VALUE SYM_total_waits;
1275
+ extern VALUE SYM_ops_pending;
1276
+ extern VALUE SYM_ops_unsubmitted;
1277
+ extern VALUE SYM_ops_runqueue;
1278
+ extern VALUE SYM_ops_free;
1279
+ extern VALUE SYM_ops_transient;
1280
+ extern VALUE SYM_time_total_cpu;
1281
+ extern VALUE SYM_time_total_wait;
1282
+
1283
+ VALUE um_metrics(struct um *machine, struct um_metrics *metrics) {
1284
+ VALUE hash = rb_hash_new();
1285
+
1286
+ rb_hash_aset(hash, SYM_size, UINT2NUM(machine->size));
1287
+
1288
+ rb_hash_aset(hash, SYM_total_ops, ULONG2NUM(metrics->total_ops));
1289
+ rb_hash_aset(hash, SYM_total_switches, ULONG2NUM(metrics->total_switches));
1290
+ rb_hash_aset(hash, SYM_total_waits, ULONG2NUM(metrics->total_waits));
1291
+
1292
+ rb_hash_aset(hash, SYM_ops_pending, UINT2NUM(metrics->ops_pending));
1293
+ rb_hash_aset(hash, SYM_ops_unsubmitted, UINT2NUM(metrics->ops_unsubmitted));
1294
+ rb_hash_aset(hash, SYM_ops_runqueue, UINT2NUM(metrics->ops_runqueue));
1295
+ rb_hash_aset(hash, SYM_ops_free, UINT2NUM(metrics->ops_free));
1296
+ rb_hash_aset(hash, SYM_ops_transient, UINT2NUM(metrics->ops_transient));
1297
+
1298
+ if (machine->profile_mode) {
1299
+ double total_cpu = um_get_time_cpu() - metrics->time_first_cpu;
1300
+ rb_hash_aset(hash, SYM_time_total_cpu, DBL2NUM(total_cpu));
1301
+ rb_hash_aset(hash, SYM_time_total_wait, DBL2NUM(metrics->time_total_wait));
1302
+ }
1303
+
1304
+ return hash;
1305
+ RB_GC_GUARD(hash);
1306
+ }