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.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +3 -4
- data/.rubocop.yml +2 -0
- data/CHANGELOG.md +34 -0
- data/TODO.md +132 -26
- data/benchmark/README.md +173 -0
- data/benchmark/bm_io_pipe.rb +70 -0
- data/benchmark/bm_io_socketpair.rb +71 -0
- data/benchmark/bm_mutex_cpu.rb +57 -0
- data/benchmark/bm_mutex_io.rb +64 -0
- data/benchmark/bm_pg_client.rb +109 -0
- data/benchmark/bm_queue.rb +76 -0
- data/benchmark/chart.png +0 -0
- data/benchmark/common.rb +135 -0
- data/benchmark/dns_client.rb +47 -0
- data/{examples/bm_http_parse.rb → benchmark/http_parse.rb} +1 -1
- data/benchmark/run_bm.rb +8 -0
- data/benchmark/sqlite.rb +108 -0
- data/{examples/bm_write.rb → benchmark/write.rb} +6 -3
- data/ext/um/extconf.rb +1 -1
- data/ext/um/um.c +404 -95
- data/ext/um/um.h +77 -24
- data/ext/um/um_async_op.c +2 -2
- data/ext/um/um_class.c +168 -18
- data/ext/um/um_op.c +43 -0
- data/ext/um/um_sync.c +10 -16
- data/ext/um/um_utils.c +16 -0
- data/grant-2025/journal.md +242 -1
- data/grant-2025/tasks.md +136 -41
- data/lib/uringmachine/actor.rb +8 -0
- data/lib/uringmachine/dns_resolver.rb +1 -2
- data/lib/uringmachine/fiber_scheduler.rb +283 -110
- data/lib/uringmachine/version.rb +1 -1
- data/lib/uringmachine.rb +32 -3
- data/test/helper.rb +7 -18
- data/test/test_actor.rb +12 -3
- data/test/test_async_op.rb +10 -10
- data/test/test_fiber.rb +84 -1
- data/test/test_fiber_scheduler.rb +1425 -20
- data/test/test_um.rb +565 -113
- data/uringmachine.gemspec +6 -5
- data/vendor/liburing/src/include/liburing/io_uring.h +1 -0
- data/vendor/liburing/src/include/liburing.h +13 -0
- data/vendor/liburing/src/liburing-ffi.map +1 -0
- data/vendor/liburing/test/bind-listen.c +175 -13
- data/vendor/liburing/test/read-write.c +4 -4
- data/vendor/liburing/test/ringbuf-read.c +4 -4
- data/vendor/liburing/test/send_recv.c +8 -7
- metadata +50 -28
- data/examples/bm_fileno.rb +0 -33
- data/examples/bm_queue.rb +0 -110
- data/examples/bm_side_running.rb +0 -83
- data/examples/bm_sqlite.rb +0 -89
- data/examples/dns_client.rb +0 -12
- /data/{examples/bm_mutex.rb → benchmark/mutex.rb} +0 -0
- /data/{examples/bm_mutex_single.rb → benchmark/mutex_single.rb} +0 -0
- /data/{examples/bm_send.rb → benchmark/send.rb} +0 -0
- /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
|
|
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 <<
|
|
66
|
-
#define OP_F_TRANSIENT (1U <<
|
|
67
|
-
#define OP_F_ASYNC (1U <<
|
|
68
|
-
#define
|
|
69
|
-
#define
|
|
70
|
-
#define
|
|
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
|
|
83
|
-
|
|
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
|
-
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
|
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
|
-
|
|
227
|
-
VALUE
|
|
228
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
238
|
+
VALUE UM_write_async(int argc, VALUE *argv, VALUE self) {
|
|
137
239
|
struct um *machine = um_get_machine(self);
|
|
138
|
-
|
|
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
|
-
|
|
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,
|
|
427
|
-
rb_define_method(cUM, "
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
245
|
-
|
|
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);
|