uringmachine 0.20.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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +3 -4
  3. data/.rubocop.yml +2 -0
  4. data/CHANGELOG.md +34 -0
  5. data/TODO.md +132 -26
  6. data/benchmark/README.md +173 -0
  7. data/benchmark/bm_io_pipe.rb +70 -0
  8. data/benchmark/bm_io_socketpair.rb +71 -0
  9. data/benchmark/bm_mutex_cpu.rb +57 -0
  10. data/benchmark/bm_mutex_io.rb +64 -0
  11. data/benchmark/bm_pg_client.rb +109 -0
  12. data/benchmark/bm_queue.rb +76 -0
  13. data/benchmark/chart.png +0 -0
  14. data/benchmark/common.rb +135 -0
  15. data/benchmark/dns_client.rb +47 -0
  16. data/{examples/bm_http_parse.rb → benchmark/http_parse.rb} +1 -1
  17. data/benchmark/run_bm.rb +8 -0
  18. data/benchmark/sqlite.rb +108 -0
  19. data/{examples/bm_write.rb → benchmark/write.rb} +6 -3
  20. data/ext/um/extconf.rb +1 -1
  21. data/ext/um/um.c +404 -95
  22. data/ext/um/um.h +77 -24
  23. data/ext/um/um_async_op.c +2 -2
  24. data/ext/um/um_class.c +168 -18
  25. data/ext/um/um_op.c +43 -0
  26. data/ext/um/um_sync.c +10 -16
  27. data/ext/um/um_utils.c +16 -0
  28. data/grant-2025/journal.md +242 -1
  29. data/grant-2025/tasks.md +136 -41
  30. data/lib/uringmachine/actor.rb +8 -0
  31. data/lib/uringmachine/dns_resolver.rb +1 -2
  32. data/lib/uringmachine/fiber_scheduler.rb +283 -110
  33. data/lib/uringmachine/version.rb +1 -1
  34. data/lib/uringmachine.rb +32 -3
  35. data/test/helper.rb +7 -18
  36. data/test/test_actor.rb +12 -3
  37. data/test/test_async_op.rb +10 -10
  38. data/test/test_fiber.rb +84 -1
  39. data/test/test_fiber_scheduler.rb +1425 -20
  40. data/test/test_um.rb +565 -113
  41. data/uringmachine.gemspec +6 -5
  42. data/vendor/liburing/src/include/liburing/io_uring.h +1 -0
  43. data/vendor/liburing/src/include/liburing.h +13 -0
  44. data/vendor/liburing/src/liburing-ffi.map +1 -0
  45. data/vendor/liburing/test/bind-listen.c +175 -13
  46. data/vendor/liburing/test/read-write.c +4 -4
  47. data/vendor/liburing/test/ringbuf-read.c +4 -4
  48. data/vendor/liburing/test/send_recv.c +8 -7
  49. metadata +50 -28
  50. data/examples/bm_fileno.rb +0 -33
  51. data/examples/bm_queue.rb +0 -110
  52. data/examples/bm_side_running.rb +0 -83
  53. data/examples/bm_sqlite.rb +0 -89
  54. data/examples/dns_client.rb +0 -12
  55. /data/{examples/bm_mutex.rb → benchmark/mutex.rb} +0 -0
  56. /data/{examples/bm_mutex_single.rb → benchmark/mutex_single.rb} +0 -0
  57. /data/{examples/bm_send.rb → benchmark/send.rb} +0 -0
  58. /data/{examples/bm_snooze.rb → benchmark/snooze.rb} +0 -0
data/ext/um/um.h CHANGED
@@ -5,11 +5,22 @@
5
5
  #include <liburing.h>
6
6
 
7
7
  // debugging
8
+ enum {
9
+ // set to 1 to enable debug logging
10
+ DEBUG = 0
11
+ };
12
+
8
13
  #define OBJ_ID(obj) (NUM2LONG(rb_funcall(obj, rb_intern("object_id"), 0)))
9
14
  #define INSPECT(str, obj) { printf(str); VALUE s = rb_funcall(obj, rb_intern("inspect"), 0); printf(": %s\n", StringValueCStr(s)); }
10
15
  #define CALLER() rb_funcall(rb_mKernel, rb_intern("caller"), 0)
11
16
  #define TRACE_CALLER() INSPECT("caller: ", CALLER())
12
17
  #define TRACE_FREE(ptr) //printf("Free %p %s:%d\n", ptr, __FILE__, __LINE__)
18
+ #define DEBUG_MARK(machine, markv, msg) \
19
+ if (machine->mark == markv) printf("%s\n", msg);
20
+ #define DEBUG_PRINTF(...) \
21
+ if (DEBUG) fprintf(stderr, __VA_ARGS__)
22
+
23
+ #define SYM_DEF(name) SYM_#name = ID2SYM(rb_intern("#name"))
13
24
 
14
25
  // branching
15
26
  #ifndef unlikely
@@ -23,7 +34,8 @@
23
34
  #define IO_BUFFER_P(buffer) \
24
35
  (TYPE(buffer) == RUBY_T_DATA) && rb_obj_is_instance_of(buffer, rb_cIOBuffer)
25
36
 
26
- enum op_kind {
37
+ enum um_op_kind {
38
+ OP_UNDEFINED,
27
39
  OP_TIMEOUT,
28
40
  OP_SCHEDULE,
29
41
 
@@ -62,12 +74,17 @@ enum op_kind {
62
74
  OP_SLEEP_MULTISHOT
63
75
  };
64
76
 
65
- #define OP_F_COMPLETED (1U << 0) // op is completed (set on each CQE for multishot ops)
66
- #define OP_F_TRANSIENT (1U << 1) // op is heap allocated
67
- #define OP_F_ASYNC (1U << 2) // op belongs to an AsyncOp
68
- #define OP_F_IGNORE_CANCELED (1U << 3) // CQE with -ECANCEL should be ignored
69
- #define OP_F_MULTISHOT (1U << 4) // op is multishot
70
- #define OP_F_FREE_ON_COMPLETE (1U << 5) // op should be freed on receiving CQE
77
+ #define OP_F_COMPLETED (1U << 0) // op is completed (set on each CQE for multishot ops)
78
+ #define OP_F_TRANSIENT (1U << 1) // op is heap allocated
79
+ #define OP_F_ASYNC (1U << 2) // op belongs to an AsyncOp
80
+ #define OP_F_CANCELED (1U << 3) // op is cancelled
81
+ #define OP_F_IGNORE_CANCELED (1U << 4) // CQE with -ECANCEL should be ignored
82
+ #define OP_F_MULTISHOT (1U << 5) // op is multishot
83
+ #define OP_F_FREE_ON_COMPLETE (1U << 6) // op should be freed on receiving CQE
84
+ #define OP_F_RUNQUEUE_SKIP (1U << 7) // runqueue entry should be skipped
85
+ #define OP_F_SELECT_POLLIN (1U << 8) // select POLLIN
86
+ #define OP_F_SELECT_POLLOUT (1U << 9) // select POLLOUT
87
+ #define OP_F_SELECT_POLLPRI (1U << 10) // select POLLPRI
71
88
 
72
89
  struct um_op_result {
73
90
  __s32 res;
@@ -79,8 +96,8 @@ struct um_op {
79
96
  struct um_op *prev;
80
97
  struct um_op *next;
81
98
 
82
- enum op_kind kind;
83
- unsigned flags;
99
+ enum um_op_kind kind;
100
+ uint flags;
84
101
 
85
102
  VALUE fiber;
86
103
  VALUE value;
@@ -88,7 +105,7 @@ struct um_op {
88
105
 
89
106
  struct um_op_result result;
90
107
  struct um_op_result *multishot_result_tail;
91
- unsigned multishot_result_count;
108
+ uint multishot_result_count;
92
109
 
93
110
  struct __kernel_timespec ts; // used for timeout operation
94
111
  };
@@ -108,6 +125,22 @@ struct buf_ring_descriptor {
108
125
  void *buf_base;
109
126
  };
110
127
 
128
+ struct um_metrics {
129
+ ulong total_ops; // total ops submitted
130
+ ulong total_switches; // total fiber switches
131
+ ulong total_waits; // total number of CQE waits
132
+
133
+ uint ops_pending; // number of pending ops
134
+ uint ops_unsubmitted; // number of unsubmitted
135
+ uint ops_runqueue; // number of ops in runqueue
136
+ uint ops_free; // number of ops in freelist
137
+ uint ops_transient; // number of ops in transient list
138
+
139
+ double time_total_wait; // total CPU time waiting for CQEs
140
+ double time_last_cpu; // last seen time stamp
141
+ double time_first_cpu; // last seen time stamp
142
+ };
143
+
111
144
  #define BUFFER_RING_MAX_COUNT 10
112
145
 
113
146
  struct um {
@@ -117,14 +150,22 @@ struct um {
117
150
 
118
151
  struct io_uring ring;
119
152
 
120
- unsigned int ring_initialized;
121
- unsigned int unsubmitted_count;
122
- unsigned int pending_count;
153
+ uint ring_initialized; // is the ring initialized successfully
154
+ uint mark; // used to mark instances for debugging
155
+
156
+ struct um_metrics metrics;
157
+ int profile_mode;
158
+
159
+ uint buffer_ring_count; // number of registered buffer rings
160
+
161
+ uint size; // size of SQ
162
+ uint sqpoll_mode; // SQPOLL mode enabled
123
163
 
124
164
  struct buf_ring_descriptor buffer_rings[BUFFER_RING_MAX_COUNT];
125
- unsigned int buffer_ring_count;
126
165
 
127
- struct um_op *transient_head;
166
+ struct um_op *transient_head; // list of pending transient ops
167
+ VALUE pending_fibers; // hash containing pending fibers
168
+
128
169
  struct um_op *runqueue_head;
129
170
  struct um_op *runqueue_tail;
130
171
 
@@ -184,9 +225,12 @@ extern VALUE cAsyncOp;
184
225
  extern VALUE eStreamRESPError;
185
226
 
186
227
  struct um *um_get_machine(VALUE self);
187
- void um_setup(VALUE self, struct um *machine);
228
+ void um_setup(VALUE self, struct um *machine, uint size, uint sqpoll_timeout_msec);
188
229
  void um_teardown(struct um *machine);
189
230
 
231
+ VALUE um_metrics(struct um *machine, struct um_metrics *metrics);
232
+
233
+ const char * um_op_kind_name(enum um_op_kind kind);
190
234
  struct um_op *um_op_alloc(struct um *machine);
191
235
  void um_op_free(struct um *machine, struct um_op *op);
192
236
  void um_op_clear(struct um *machine, struct um_op *op);
@@ -207,12 +251,14 @@ void um_free_buffer_linked_list(struct um *machine);
207
251
 
208
252
  struct __kernel_timespec um_double_to_timespec(double value);
209
253
  double um_timestamp_to_double(__s64 tv_sec, __u32 tv_nsec);
254
+ double um_get_time_cpu();
255
+ double um_get_time_monotonic();
210
256
  int um_value_is_exception_p(VALUE v);
211
257
  VALUE um_raise_exception(VALUE v);
212
258
 
213
- #define RAISE_IF_EXCEPTION(v) if (um_value_is_exception_p(v)) { um_raise_exception(v); }
259
+ #define RAISE_IF_EXCEPTION(v) if (unlikely(um_value_is_exception_p(v))) { um_raise_exception(v); }
214
260
 
215
- void um_prep_op(struct um *machine, struct um_op *op, enum op_kind kind, unsigned flags);
261
+ void um_prep_op(struct um *machine, struct um_op *op, enum um_op_kind kind, unsigned flags);
216
262
  void um_raise_on_error_result(int result);
217
263
  void um_get_buffer_bytes_for_writing(VALUE buffer, const void **base, size_t *size);
218
264
  void * um_prepare_read_buffer(VALUE buffer, ssize_t len, ssize_t ofs);
@@ -223,9 +269,11 @@ void um_add_strings_to_buffer_ring(struct um *machine, int bgid, VALUE strings);
223
269
 
224
270
  struct io_uring_sqe *um_get_sqe(struct um *machine, struct um_op *op);
225
271
 
226
- VALUE um_fiber_switch(struct um *machine);
227
- VALUE um_await(struct um *machine);
228
- void um_submit_cancel_op(struct um *machine, struct um_op *op);
272
+ uint um_submit(struct um *machine);
273
+ VALUE um_yield(struct um *machine);
274
+ VALUE um_switch(struct um *machine);
275
+ VALUE um_wakeup(struct um *machine);
276
+ void um_cancel_op(struct um *machine, struct um_op *op);
229
277
  void um_cancel_and_wait(struct um *machine, struct um_op *op);
230
278
  int um_check_completion(struct um *machine, struct um_op *op);
231
279
 
@@ -236,17 +284,22 @@ VALUE um_timeout(struct um *machine, VALUE interval, VALUE class);
236
284
 
237
285
  VALUE um_sleep(struct um *machine, double duration);
238
286
  VALUE um_periodically(struct um *machine, double interval);
239
- VALUE um_read(struct um *machine, int fd, VALUE buffer, size_t maxlen, ssize_t buffer_offset);
287
+ VALUE um_read(struct um *machine, int fd, VALUE buffer, size_t maxlen, ssize_t buffer_offset, __u64 file_offset);
240
288
  size_t um_read_raw(struct um *machine, int fd, char *buffer, size_t maxlen);
241
289
  VALUE um_read_each(struct um *machine, int fd, int bgid);
242
- VALUE um_write(struct um *machine, int fd, VALUE buffer, size_t len);
243
- VALUE um_write_async(struct um *machine, int fd, VALUE buffer);
290
+ VALUE um_write(struct um *machine, int fd, VALUE buffer, size_t len, __u64 file_offset);
291
+ VALUE um_write_async(struct um *machine, int fd, VALUE buffer, size_t len, __u64 file_offset);
244
292
  VALUE um_close(struct um *machine, int fd);
245
293
  VALUE um_close_async(struct um *machine, int fd);
246
294
  VALUE um_open(struct um *machine, VALUE pathname, int flags, int mode);
247
295
  VALUE um_poll(struct um *machine, int fd, unsigned mask);
296
+ VALUE um_select(struct um *machine, VALUE rfds, VALUE wfds, VALUE efds);
248
297
  VALUE um_waitid(struct um *machine, int idtype, int id, int options);
298
+
299
+ #ifdef HAVE_RB_PROCESS_STATUS_NEW
249
300
  VALUE um_waitid_status(struct um *machine, int idtype, int id, int options);
301
+ #endif
302
+
250
303
  VALUE um_statx(struct um *machine, int dirfd, VALUE path, int flags, unsigned int mask);
251
304
 
252
305
  VALUE um_accept(struct um *machine, int fd);
data/ext/um/um_async_op.c CHANGED
@@ -26,7 +26,7 @@ VALUE um_async_op_await(struct um_async_op *async_op) {
26
26
  RB_OBJ_WRITE(async_op->machine->self, &async_op->op->fiber, rb_fiber_current());
27
27
  async_op->op->flags &= ~OP_F_ASYNC;
28
28
 
29
- VALUE ret = um_fiber_switch(async_op->machine);
29
+ VALUE ret = um_switch(async_op->machine);
30
30
  if (!um_op_completed_p(async_op->op))
31
31
  um_cancel_and_wait(async_op->machine, async_op->op);
32
32
 
@@ -36,5 +36,5 @@ VALUE um_async_op_await(struct um_async_op *async_op) {
36
36
  }
37
37
 
38
38
  void um_async_op_cancel(struct um_async_op *async_op) {
39
- um_submit_cancel_op(async_op->machine, async_op->op);
39
+ um_cancel_op(async_op->machine, async_op->op);
40
40
  }
data/ext/um/um_class.c CHANGED
@@ -3,15 +3,29 @@
3
3
  #include <ruby/io.h>
4
4
  #include <sys/syscall.h>
5
5
  #include <unistd.h>
6
+ #include <sys/socket.h>
6
7
 
7
8
  VALUE cUM;
8
9
  VALUE eUMError;
9
10
 
11
+ VALUE SYM_size;
12
+ VALUE SYM_total_ops;
13
+ VALUE SYM_total_switches;
14
+ VALUE SYM_total_waits;
15
+ VALUE SYM_ops_pending;
16
+ VALUE SYM_ops_unsubmitted;
17
+ VALUE SYM_ops_runqueue;
18
+ VALUE SYM_ops_free;
19
+ VALUE SYM_ops_transient;
20
+ VALUE SYM_time_total_cpu;
21
+ VALUE SYM_time_total_wait;
22
+
10
23
  static ID id_fileno;
11
24
 
12
25
  static void UM_mark(void *ptr) {
13
26
  struct um *machine = ptr;
14
27
  rb_gc_mark_movable(machine->self);
28
+ rb_gc_mark_movable(machine->pending_fibers);
15
29
 
16
30
  um_op_list_mark(machine, machine->transient_head);
17
31
  um_op_list_mark(machine, machine->runqueue_head);
@@ -20,6 +34,7 @@ static void UM_mark(void *ptr) {
20
34
  static void UM_compact(void *ptr) {
21
35
  struct um *machine = ptr;
22
36
  machine->self = rb_gc_location(machine->self);
37
+ machine->pending_fibers = rb_gc_location(machine->pending_fibers);
23
38
 
24
39
  um_op_list_compact(machine, machine->transient_head);
25
40
  um_op_list_compact(machine, machine->runqueue_head);
@@ -54,9 +69,33 @@ inline struct um *um_get_machine(VALUE self) {
54
69
  return um;
55
70
  }
56
71
 
57
- VALUE UM_initialize(VALUE self) {
72
+ static inline uint get_sqpoll_timeout_msec(VALUE sqpoll_timeout) {
73
+ switch (TYPE(sqpoll_timeout)) {
74
+ case T_NIL:
75
+ case T_FALSE:
76
+ return 0;
77
+ case T_FLOAT:
78
+ return (uint)(NUM2DBL(sqpoll_timeout) * 1000);
79
+ case T_FIXNUM:
80
+ return NUM2UINT(sqpoll_timeout) * 1000;
81
+ case T_TRUE:
82
+ return 1000;
83
+ default:
84
+ rb_raise(eUMError, "Invalid sqpoll_timeout value");
85
+ }
86
+ }
87
+
88
+ VALUE UM_initialize(int argc, VALUE *argv, VALUE self) {
58
89
  struct um *machine = RTYPEDDATA_DATA(self);
59
- um_setup(self, machine);
90
+ VALUE entries;
91
+ VALUE sqpoll_timeout;
92
+ rb_scan_args(argc, argv, "02", &entries, &sqpoll_timeout);
93
+
94
+ uint entries_i = NIL_P(entries) ? 0 : NUM2UINT(entries);
95
+ uint sqpoll_timeout_msec = get_sqpoll_timeout_msec(sqpoll_timeout);
96
+
97
+
98
+ um_setup(self, machine, entries_i, sqpoll_timeout_msec);
60
99
  return self;
61
100
  }
62
101
 
@@ -66,20 +105,78 @@ VALUE UM_setup_buffer_ring(VALUE self, VALUE size, VALUE count) {
66
105
  return INT2NUM(bgid);
67
106
  }
68
107
 
69
- VALUE UM_pending_count(VALUE self) {
108
+ VALUE UM_size(VALUE self) {
109
+ struct um *machine = um_get_machine(self);
110
+ return UINT2NUM(machine->size);
111
+ }
112
+
113
+ VALUE UM_mark_m(VALUE self, VALUE mark) {
70
114
  struct um *machine = um_get_machine(self);
71
- return INT2NUM(machine->pending_count);
115
+ machine->mark = NUM2UINT(mark);
116
+ return self;
117
+ }
118
+
119
+ VALUE UM_metrics(VALUE self) {
120
+ struct um *machine = um_get_machine(self);
121
+ return um_metrics(machine, &machine->metrics);
122
+ }
123
+
124
+ VALUE UM_profile_p(VALUE self) {
125
+ struct um *machine = um_get_machine(self);
126
+ return machine->profile_mode ? Qtrue : Qfalse;
127
+ }
128
+
129
+ VALUE UM_profile_set(VALUE self, VALUE value) {
130
+ struct um *machine = um_get_machine(self);
131
+ machine->profile_mode = RTEST(value);
132
+ if (machine->profile_mode) {
133
+ machine->metrics.time_total_wait = 0.0;
134
+ machine->metrics.time_last_cpu = machine->metrics.time_first_cpu = um_get_time_cpu();
135
+ }
136
+ return value;
72
137
  }
73
138
 
74
139
  VALUE UM_snooze(VALUE self) {
75
140
  struct um *machine = um_get_machine(self);
76
141
  um_schedule(machine, rb_fiber_current(), Qnil);
77
- return um_await(machine);
142
+
143
+ // the current fiber is already scheduled, and the runqueue is GC-marked, so
144
+ // we can safely call um_switch, which is faster than calling um_yield.
145
+ VALUE ret = um_switch(machine);
146
+ RAISE_IF_EXCEPTION(ret);
147
+ return ret;
78
148
  }
79
149
 
80
150
  VALUE UM_yield(VALUE self) {
81
151
  struct um *machine = um_get_machine(self);
82
- return um_await(machine);
152
+
153
+ VALUE ret = um_yield(machine);
154
+ RAISE_IF_EXCEPTION(ret);
155
+ return ret;
156
+ }
157
+
158
+ VALUE UM_switch(VALUE self) {
159
+ struct um *machine = um_get_machine(self);
160
+
161
+ VALUE ret = um_switch(machine);
162
+ RAISE_IF_EXCEPTION(ret);
163
+ return ret;
164
+ }
165
+
166
+ VALUE UM_wakeup(VALUE self) {
167
+ struct um *machine = um_get_machine(self);
168
+ return um_wakeup(machine);
169
+ }
170
+
171
+ VALUE UM_submit(VALUE self) {
172
+ struct um *machine = um_get_machine(self);
173
+ uint ret = um_submit(machine);
174
+ return UINT2NUM(ret);
175
+ }
176
+
177
+ VALUE UM_pending_fibers(VALUE self) {
178
+ struct um *machine = um_get_machine(self);
179
+ return machine->pending_fibers;
83
180
  }
84
181
 
85
182
  VALUE UM_schedule(VALUE self, VALUE fiber, VALUE value) {
@@ -109,12 +206,14 @@ VALUE UM_read(int argc, VALUE *argv, VALUE self) {
109
206
  VALUE buffer;
110
207
  VALUE maxlen;
111
208
  VALUE buffer_offset;
112
- rb_scan_args(argc, argv, "22", &fd, &buffer, &maxlen, &buffer_offset);
209
+ VALUE file_offset;
210
+ rb_scan_args(argc, argv, "23", &fd, &buffer, &maxlen, &buffer_offset, &file_offset);
113
211
 
114
212
  ssize_t maxlen_i = NIL_P(maxlen) ? -1 : NUM2INT(maxlen);
115
213
  ssize_t buffer_offset_i = NIL_P(buffer_offset) ? 0 : NUM2INT(buffer_offset);
214
+ __u64 file_offset_i = NIL_P(file_offset) ? (__u64)-1 : NUM2UINT(file_offset);
116
215
 
117
- return um_read(machine, NUM2INT(fd), buffer, maxlen_i, buffer_offset_i);
216
+ return um_read(machine, NUM2INT(fd), buffer, maxlen_i, buffer_offset_i, file_offset_i);
118
217
  }
119
218
 
120
219
  VALUE UM_read_each(VALUE self, VALUE fd, VALUE bgid) {
@@ -127,15 +226,27 @@ VALUE UM_write(int argc, VALUE *argv, VALUE self) {
127
226
  VALUE fd;
128
227
  VALUE buffer;
129
228
  VALUE len;
130
- rb_scan_args(argc, argv, "21", &fd, &buffer, &len);
229
+ VALUE file_offset;
230
+ rb_scan_args(argc, argv, "22", &fd, &buffer, &len, &file_offset);
231
+
232
+ size_t len_i = NIL_P(len) ? (size_t)-1 : NUM2UINT(len);
233
+ __u64 file_offset_i = NIL_P(file_offset) ? (__u64)-1 : NUM2UINT(file_offset);
131
234
 
132
- size_t bytes = NIL_P(len) ? (size_t)-1 : NUM2UINT(len);
133
- return um_write(machine, NUM2INT(fd), buffer, bytes);
235
+ return um_write(machine, NUM2INT(fd), buffer, len_i, file_offset_i);
134
236
  }
135
237
 
136
- VALUE UM_write_async(VALUE self, VALUE fd, VALUE buffer) {
238
+ VALUE UM_write_async(int argc, VALUE *argv, VALUE self) {
137
239
  struct um *machine = um_get_machine(self);
138
- return um_write_async(machine, NUM2INT(fd), buffer);
240
+ VALUE fd;
241
+ VALUE buffer;
242
+ VALUE len;
243
+ VALUE file_offset;
244
+ rb_scan_args(argc, argv, "22", &fd, &buffer, &len, &file_offset);
245
+
246
+ size_t len_i = NIL_P(len) ? (size_t)-1 : NUM2UINT(len);
247
+ __u64 file_offset_i = NIL_P(file_offset) ? (__u64)-1 : NUM2UINT(file_offset);
248
+
249
+ return um_write_async(machine, NUM2INT(fd), buffer, len_i, file_offset_i);
139
250
  }
140
251
 
141
252
  VALUE UM_statx(VALUE self, VALUE dirfd, VALUE path, VALUE flags, VALUE mask) {
@@ -330,6 +441,11 @@ VALUE UM_poll(VALUE self, VALUE fd, VALUE mask) {
330
441
  return um_poll(machine, NUM2INT(fd), NUM2UINT(mask));
331
442
  }
332
443
 
444
+ VALUE UM_select(VALUE self, VALUE rfds, VALUE wfds, VALUE efds) {
445
+ struct um *machine = um_get_machine(self);
446
+ return um_select(machine, rfds, wfds, efds);
447
+ }
448
+
333
449
  VALUE UM_waitid(VALUE self, VALUE idtype, VALUE id, VALUE options) {
334
450
  struct um *machine = um_get_machine(self);
335
451
  return um_waitid(machine, NUM2INT(idtype), NUM2INT(id), NUM2INT(options));
@@ -358,6 +474,17 @@ VALUE UM_pipe(VALUE self) {
358
474
  return rb_ary_new_from_args(2, INT2NUM(fds[0]), INT2NUM(fds[1]));
359
475
  }
360
476
 
477
+ VALUE UM_socketpair(VALUE self, VALUE domain, VALUE type, VALUE protocol) {
478
+ int fds[2];
479
+ int ret = socketpair(NUM2INT(domain), NUM2INT(type), NUM2INT(protocol), fds);
480
+ if (ret) {
481
+ int e = errno;
482
+ rb_syserr_fail(e, strerror(e));
483
+ }
484
+
485
+ return rb_ary_new_from_args(2, INT2NUM(fds[0]), INT2NUM(fds[1]));
486
+ }
487
+
361
488
  VALUE UM_pidfd_open(VALUE self, VALUE pid) {
362
489
  int fd = syscall(SYS_pidfd_open, NUM2INT(pid), 0);
363
490
  if (fd == -1) {
@@ -413,7 +540,7 @@ VALUE UM_kernel_version(VALUE self) {
413
540
  }
414
541
 
415
542
  VALUE UM_debug(VALUE self, VALUE str) {
416
- printf("%s\n", StringValueCStr(str));
543
+ fprintf(stderr, "%s\n", StringValueCStr(str));
417
544
  return Qnil;
418
545
  }
419
546
 
@@ -423,11 +550,17 @@ void Init_UM(void) {
423
550
  cUM = rb_define_class("UringMachine", rb_cObject);
424
551
  rb_define_alloc_func(cUM, UM_allocate);
425
552
 
426
- rb_define_method(cUM, "initialize", UM_initialize, 0);
427
- rb_define_method(cUM, "pending_count", UM_pending_count, 0);
553
+ rb_define_method(cUM, "initialize", UM_initialize, -1);
554
+ rb_define_method(cUM, "size", UM_size, 0);
555
+ rb_define_method(cUM, "mark", UM_mark_m, 1);
556
+ rb_define_method(cUM, "metrics", UM_metrics, 0);
557
+ rb_define_method(cUM, "profile?", UM_profile_p, 0);
558
+ rb_define_method(cUM, "profile", UM_profile_set, 1);
559
+
428
560
  rb_define_method(cUM, "setup_buffer_ring", UM_setup_buffer_ring, 2);
429
561
 
430
562
  rb_define_singleton_method(cUM, "pipe", UM_pipe, 0);
563
+ rb_define_singleton_method(cUM, "socketpair", UM_socketpair, 3);
431
564
  rb_define_singleton_method(cUM, "pidfd_open", UM_pidfd_open, 1);
432
565
  rb_define_singleton_method(cUM, "pidfd_send_signal", UM_pidfd_send_signal, 2);
433
566
 
@@ -436,11 +569,14 @@ void Init_UM(void) {
436
569
  rb_define_singleton_method(cUM, "kernel_version", UM_kernel_version, 0);
437
570
  rb_define_singleton_method(cUM, "debug", UM_debug, 1);
438
571
 
439
-
440
572
  rb_define_method(cUM, "schedule", UM_schedule, 2);
441
573
  rb_define_method(cUM, "snooze", UM_snooze, 0);
442
574
  rb_define_method(cUM, "timeout", UM_timeout, 2);
443
575
  rb_define_method(cUM, "yield", UM_yield, 0);
576
+ rb_define_method(cUM, "switch", UM_switch, 0);
577
+ rb_define_method(cUM, "wakeup", UM_wakeup, 0);
578
+ rb_define_method(cUM, "submit", UM_submit, 0);
579
+ rb_define_method(cUM, "pending_fibers", UM_pending_fibers, 0);
444
580
 
445
581
  rb_define_method(cUM, "close", UM_close, 1);
446
582
  rb_define_method(cUM, "close_async", UM_close_async, 1);
@@ -450,11 +586,13 @@ void Init_UM(void) {
450
586
  rb_define_method(cUM, "sleep", UM_sleep, 1);
451
587
  rb_define_method(cUM, "periodically", UM_periodically, 1);
452
588
  rb_define_method(cUM, "write", UM_write, -1);
453
- rb_define_method(cUM, "write_async", UM_write_async, 2);
589
+ rb_define_method(cUM, "write_async", UM_write_async, -1);
454
590
  rb_define_method(cUM, "statx", UM_statx, 4);
455
591
 
456
592
  rb_define_method(cUM, "poll", UM_poll, 2);
593
+ rb_define_method(cUM, "select", UM_select, 3);
457
594
  rb_define_method(cUM, "waitid", UM_waitid, 3);
595
+
458
596
  #ifdef HAVE_RB_PROCESS_STATUS_NEW
459
597
  rb_define_method(cUM, "waitid_status", UM_waitid_status, 3);
460
598
  #endif
@@ -486,5 +624,17 @@ void Init_UM(void) {
486
624
 
487
625
  um_define_net_constants(cUM);
488
626
 
627
+ SYM_size = ID2SYM(rb_intern("size"));
628
+ SYM_total_ops = ID2SYM(rb_intern("total_ops"));
629
+ SYM_total_switches = ID2SYM(rb_intern("total_switches"));
630
+ SYM_total_waits = ID2SYM(rb_intern("total_waits"));
631
+ SYM_ops_pending = ID2SYM(rb_intern("ops_pending"));
632
+ SYM_ops_unsubmitted = ID2SYM(rb_intern("ops_unsubmitted"));
633
+ SYM_ops_runqueue = ID2SYM(rb_intern("ops_runqueue"));
634
+ SYM_ops_free = ID2SYM(rb_intern("ops_free"));
635
+ SYM_ops_transient = ID2SYM(rb_intern("ops_transient"));
636
+ SYM_time_total_cpu = ID2SYM(rb_intern("time_total_cpu"));
637
+ SYM_time_total_wait = ID2SYM(rb_intern("time_total_wait"));
638
+
489
639
  id_fileno = rb_intern_const("fileno");
490
640
  }
data/ext/um/um_op.c CHANGED
@@ -1,5 +1,42 @@
1
1
  #include "um.h"
2
2
 
3
+ const char * um_op_kind_name(enum um_op_kind kind) {
4
+ switch (kind) {
5
+ case OP_TIMEOUT: return "OP_TIMEOUT";
6
+ case OP_SCHEDULE: return "OP_SCHEDULE";
7
+ case OP_SLEEP: return "OP_SLEEP";
8
+ case OP_OPEN: return "OP_OPEN";
9
+ case OP_READ: return "OP_READ";
10
+ case OP_WRITE: return "OP_WRITE";
11
+ case OP_WRITE_ASYNC: return "OP_WRITE_ASYNC";
12
+ case OP_CLOSE: return "OP_CLOSE";
13
+ case OP_CLOSE_ASYNC: return "OP_CLOSE_ASYNC";
14
+ case OP_STATX: return "OP_STATX";
15
+ case OP_ACCEPT: return "OP_ACCEPT";
16
+ case OP_RECV: return "OP_RECV";
17
+ case OP_SEND: return "OP_SEND";
18
+ case OP_SEND_BUNDLE: return "OP_SEND_BUNDLE";
19
+ case OP_SOCKET: return "OP_SOCKET";
20
+ case OP_CONNECT: return "OP_CONNECT";
21
+ case OP_BIND: return "OP_BIND";
22
+ case OP_LISTEN: return "OP_LISTEN";
23
+ case OP_GETSOCKOPT: return "OP_GETSOCKOPT";
24
+ case OP_SETSOCKOPT: return "OP_SETSOCKOPT";
25
+ case OP_SHUTDOWN: return "OP_SHUTDOWN";
26
+ case OP_SHUTDOWN_ASYNC: return "OP_SHUTDOWN_ASYNC";
27
+ case OP_POLL: return "OP_POLL";
28
+ case OP_WAITID: return "OP_WAITID";
29
+ case OP_FUTEX_WAIT: return "OP_FUTEX_WAIT";
30
+ case OP_FUTEX_WAKE: return "OP_FUTEX_WAKE";
31
+ case OP_ACCEPT_MULTISHOT: return "OP_ACCEPT_MULTISHOT";
32
+ case OP_READ_MULTISHOT: return "OP_READ_MULTISHOT";
33
+ case OP_RECV_MULTISHOT: return "OP_RECV_MULTISHOT";
34
+ case OP_TIMEOUT_MULTISHOT: return "OP_TIMEOUT_MULTISHOT";
35
+ case OP_SLEEP_MULTISHOT: return "OP_SLEEP_MULTISHOT";
36
+ default: return "UNKNOWN_OP_KIND";
37
+ }
38
+ }
39
+
3
40
  inline void um_op_clear(struct um *machine, struct um_op *op) {
4
41
  memset(op, 0, sizeof(struct um_op));
5
42
  op->fiber = Qnil;
@@ -13,6 +50,7 @@ inline void um_op_transient_add(struct um *machine, struct um_op *op) {
13
50
  machine->transient_head->prev = op;
14
51
  }
15
52
  machine->transient_head = op;
53
+ machine->metrics.ops_transient++;
16
54
  }
17
55
 
18
56
  inline void um_op_transient_remove(struct um *machine, struct um_op *op) {
@@ -23,6 +61,7 @@ inline void um_op_transient_remove(struct um *machine, struct um_op *op) {
23
61
 
24
62
  if (machine->transient_head == op)
25
63
  machine->transient_head = op->next;
64
+ machine->metrics.ops_transient--;
26
65
  }
27
66
 
28
67
  inline void um_runqueue_push(struct um *machine, struct um_op *op) {
@@ -34,6 +73,7 @@ inline void um_runqueue_push(struct um *machine, struct um_op *op) {
34
73
  else
35
74
  machine->runqueue_head = machine->runqueue_tail = op;
36
75
  op->next = NULL;
76
+ machine->metrics.ops_runqueue++;
37
77
  }
38
78
 
39
79
  inline struct um_op *um_runqueue_shift(struct um *machine) {
@@ -43,6 +83,7 @@ inline struct um_op *um_runqueue_shift(struct um *machine) {
43
83
  machine->runqueue_head = op->next;
44
84
  if (!machine->runqueue_head)
45
85
  machine->runqueue_tail = NULL;
86
+ machine->metrics.ops_runqueue--;
46
87
  return op;
47
88
  }
48
89
 
@@ -115,6 +156,7 @@ inline struct um_op *um_op_alloc(struct um *machine) {
115
156
  if (machine->op_freelist) {
116
157
  struct um_op *op = machine->op_freelist;
117
158
  machine->op_freelist = op->next;
159
+ machine->metrics.ops_free--;
118
160
  return op;
119
161
  }
120
162
  return malloc(sizeof(struct um_op));
@@ -123,4 +165,5 @@ inline struct um_op *um_op_alloc(struct um *machine) {
123
165
  inline void um_op_free(struct um *machine, struct um_op *op) {
124
166
  op->next = machine->op_freelist;
125
167
  machine->op_freelist = op;
168
+ machine->metrics.ops_free++;
126
169
  }
data/ext/um/um_sync.c CHANGED
@@ -4,16 +4,16 @@
4
4
 
5
5
  #define FUTEX2_SIZE_U32 0x02
6
6
 
7
- void um_futex_wait(struct um *machine, uint32_t *futex, uint32_t expect) {
7
+ // The value argument is the current (known) futex value.
8
+ void um_futex_wait(struct um *machine, uint32_t *futex, uint32_t value) {
8
9
  struct um_op op;
9
10
  um_prep_op(machine, &op, OP_FUTEX_WAIT, 0);
10
11
  struct io_uring_sqe *sqe = um_get_sqe(machine, &op);
11
12
  io_uring_prep_futex_wait(
12
- sqe, (uint32_t *)futex, expect, FUTEX_BITSET_MATCH_ANY,
13
- FUTEX2_SIZE_U32, 0
13
+ sqe, (uint32_t *)futex, value, FUTEX_BITSET_MATCH_ANY, FUTEX2_SIZE_U32, 0
14
14
  );
15
15
 
16
- VALUE ret = um_fiber_switch(machine);
16
+ VALUE ret = um_yield(machine);
17
17
  if (!um_op_completed_p(&op))
18
18
  um_cancel_and_wait(machine, &op);
19
19
  else {
@@ -29,13 +29,11 @@ void um_futex_wake(struct um *machine, uint32_t *futex, uint32_t num_waiters) {
29
29
  struct um_op op;
30
30
  um_prep_op(machine, &op, OP_FUTEX_WAKE, 0);
31
31
  struct io_uring_sqe *sqe = um_get_sqe(machine, &op);
32
- // submit futex_wait
33
32
  io_uring_prep_futex_wake(
34
- sqe, (uint32_t *)futex, num_waiters, FUTEX_BITSET_MATCH_ANY,
35
- FUTEX2_SIZE_U32, 0
33
+ sqe, (uint32_t *)futex, num_waiters, FUTEX_BITSET_MATCH_ANY, FUTEX2_SIZE_U32, 0
36
34
  );
37
35
 
38
- VALUE ret = um_fiber_switch(machine);
36
+ VALUE ret = um_yield(machine);
39
37
  um_check_completion(machine, &op);
40
38
 
41
39
  RAISE_IF_EXCEPTION(ret);
@@ -45,12 +43,11 @@ void um_futex_wake(struct um *machine, uint32_t *futex, uint32_t num_waiters) {
45
43
  void um_futex_wake_transient(struct um *machine, uint32_t *futex, uint32_t num_waiters) {
46
44
  struct io_uring_sqe *sqe = um_get_sqe(machine, NULL);
47
45
  io_uring_prep_futex_wake(
48
- sqe, (uint32_t *)futex, num_waiters, FUTEX_BITSET_MATCH_ANY,
49
- FUTEX2_SIZE_U32, 0
46
+ sqe, (uint32_t *)futex, num_waiters, FUTEX_BITSET_MATCH_ANY, FUTEX2_SIZE_U32, 0
50
47
  );
48
+ um_submit(machine);
51
49
  }
52
50
 
53
-
54
51
  #define MUTEX_LOCKED 1
55
52
  #define MUTEX_UNLOCKED 0
56
53
 
@@ -210,7 +207,6 @@ static inline VALUE um_queue_add(struct um *machine, struct um_queue *queue, VAL
210
207
  else queue_add_tail(queue, value);
211
208
 
212
209
  queue->count++;
213
-
214
210
  queue->state = QUEUE_READY;
215
211
  if (queue->num_waiters)
216
212
  um_futex_wake_transient(machine, &queue->state, 1);
@@ -241,10 +237,8 @@ VALUE um_queue_remove_start(VALUE arg) {
241
237
  um_futex_wait(ctx->machine, &ctx->queue->state, QUEUE_EMPTY);
242
238
  }
243
239
 
244
- if (ctx->queue->state != QUEUE_READY)
245
- um_raise_internal_error("Internal error: queue should be in ready state!");
246
- if (!ctx->queue->tail)
247
- um_raise_internal_error("Internal error: queue should be in ready state!");
240
+ assert(ctx->queue->state == QUEUE_READY);
241
+ assert(ctx->queue->tail);
248
242
 
249
243
  ctx->queue->count--;
250
244
  return (ctx->op == QUEUE_POP ? queue_remove_tail : queue_remove_head)(ctx->queue);