uringmachine 0.30.0 → 0.32.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/CHANGELOG.md +16 -4
- data/README.md +45 -38
- data/TODO.md +21 -4
- data/benchmark/bm_io_pipe.rb +2 -2
- data/benchmark/common.rb +16 -16
- data/benchmark/gets.rb +5 -5
- data/benchmark/gets_concurrent.rb +12 -12
- data/benchmark/http_parse.rb +14 -14
- data/benchmark/http_server_accept_queue.rb +11 -7
- data/benchmark/http_server_multi_accept.rb +7 -7
- data/benchmark/http_server_multi_ractor.rb +7 -7
- data/benchmark/http_server_single_thread.rb +8 -8
- data/benchmark/openssl.rb +50 -22
- data/docs/design/buffer_pool.md +1 -1
- data/examples/fiber_concurrency_io.rb +52 -0
- data/examples/fiber_concurrency_naive.rb +26 -0
- data/examples/fiber_concurrency_runqueue.rb +33 -0
- data/examples/fiber_concurrency_um.rb +16 -0
- data/examples/io_uring_simple.c +33 -0
- data/examples/pg.rb +2 -2
- data/examples/stream.rb +2 -2
- data/examples/um_cancellation.rb +20 -0
- data/examples/um_fiber_scheduler.rb +10 -0
- data/examples/um_io.rb +19 -0
- data/examples/um_mo.c +32 -0
- data/examples/um_multishot.rb +15 -0
- data/examples/um_ssl.rb +11 -0
- data/ext/um/um.c +20 -3
- data/ext/um/um.h +24 -18
- data/ext/um/um_ext.c +2 -4
- data/ext/um/um_io.c +775 -0
- data/ext/um/um_io_class.c +394 -0
- data/ext/um/um_ssl.c +37 -2
- data/ext/um/um_utils.c +1 -1
- data/grant-2025/final-report.md +2 -0
- data/grant-2025/journal.md +1 -1
- data/lib/uringmachine/version.rb +1 -1
- data/lib/uringmachine.rb +18 -18
- data/test/{test_stream.rb → test_io.rb} +290 -153
- data/test/test_um.rb +18 -18
- metadata +17 -6
- data/ext/um/um_stream.c +0 -706
- data/ext/um/um_stream_class.c +0 -317
data/ext/um/um_io.c
ADDED
|
@@ -0,0 +1,775 @@
|
|
|
1
|
+
#include <stdlib.h>
|
|
2
|
+
#include <ruby/io/buffer.h>
|
|
3
|
+
#include "um.h"
|
|
4
|
+
|
|
5
|
+
inline void io_add_segment(struct um_io *io, struct um_segment *segment) {
|
|
6
|
+
segment->next = NULL;
|
|
7
|
+
if (io->tail) {
|
|
8
|
+
io->tail->next = segment;
|
|
9
|
+
io->tail = segment;
|
|
10
|
+
}
|
|
11
|
+
else
|
|
12
|
+
io->head = io->tail = segment;
|
|
13
|
+
io->pending_bytes += segment->len;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
inline int io_process_op_result(struct um_io *io, struct um_op_result *result) {
|
|
17
|
+
if (likely(result->res > 0)) {
|
|
18
|
+
if (likely(result->segment)) {
|
|
19
|
+
io_add_segment(io, result->segment);
|
|
20
|
+
result->segment = NULL;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
else
|
|
24
|
+
io->eof = 1;
|
|
25
|
+
|
|
26
|
+
return result->res;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
#define IO_OP_FLAGS (OP_F_MULTISHOT | OP_F_BUFFER_POOL)
|
|
30
|
+
|
|
31
|
+
void io_multishot_op_start(struct um_io *io) {
|
|
32
|
+
if (!io->op)
|
|
33
|
+
io->op = um_op_acquire(io->machine);
|
|
34
|
+
struct io_uring_sqe *sqe;
|
|
35
|
+
|
|
36
|
+
bp_ensure_commit_level(io->machine);
|
|
37
|
+
|
|
38
|
+
switch (io->mode) {
|
|
39
|
+
case IO_FD:
|
|
40
|
+
um_prep_op(io->machine, io->op, OP_READ_MULTISHOT, 2, IO_OP_FLAGS);
|
|
41
|
+
sqe = um_get_sqe(io->machine, io->op);
|
|
42
|
+
io_uring_prep_read_multishot(sqe, io->fd, 0, -1, BP_BGID);
|
|
43
|
+
break;
|
|
44
|
+
case IO_SOCKET:
|
|
45
|
+
um_prep_op(io->machine, io->op, OP_RECV_MULTISHOT, 2, IO_OP_FLAGS);
|
|
46
|
+
sqe = um_get_sqe(io->machine, io->op);
|
|
47
|
+
io_uring_prep_recv_multishot(sqe, io->fd, NULL, 0, 0);
|
|
48
|
+
sqe->buf_group = BP_BGID;
|
|
49
|
+
sqe->flags |= IOSQE_BUFFER_SELECT;
|
|
50
|
+
break;
|
|
51
|
+
default:
|
|
52
|
+
um_raise_internal_error("Invalid multishot op");
|
|
53
|
+
}
|
|
54
|
+
io->op->bp_commit_level = io->machine->bp_commit_level;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
void io_multishot_op_stop(struct um_io *io) {
|
|
58
|
+
assert(!io->op);
|
|
59
|
+
|
|
60
|
+
if (!(io->op->flags & OP_F_CQE_DONE)) {
|
|
61
|
+
io->op->flags |= OP_F_ASYNC;
|
|
62
|
+
um_cancel_op(io->machine, io->op);
|
|
63
|
+
}
|
|
64
|
+
else
|
|
65
|
+
um_op_release(io->machine, io->op);
|
|
66
|
+
io->op = NULL;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
void um_io_cleanup(struct um_io *io) {
|
|
70
|
+
if (io->op) io_multishot_op_stop(io);
|
|
71
|
+
|
|
72
|
+
while (io->head) {
|
|
73
|
+
struct um_segment *next = io->head->next;
|
|
74
|
+
um_segment_checkin(io->machine, io->head);
|
|
75
|
+
io->head = next;
|
|
76
|
+
}
|
|
77
|
+
io->pending_bytes = 0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// returns true if case of ENOBUFS error, sets more to true if more data forthcoming
|
|
81
|
+
inline int io_process_segments(
|
|
82
|
+
struct um_io *io, size_t *total_bytes, int *more) {
|
|
83
|
+
|
|
84
|
+
*more = 0;
|
|
85
|
+
struct um_op_result *result = &io->op->result;
|
|
86
|
+
io->op->flags &= ~OP_F_CQE_SEEN;
|
|
87
|
+
while (result) {
|
|
88
|
+
if (unlikely(result->res == -ENOBUFS)) {
|
|
89
|
+
*more = 0;
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
if (unlikely(result->res == -ECANCELED)) {
|
|
93
|
+
*more = 0;
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
um_raise_on_error_result(result->res);
|
|
97
|
+
|
|
98
|
+
*more = (result->flags & IORING_CQE_F_MORE);
|
|
99
|
+
*total_bytes += result->res;
|
|
100
|
+
io_process_op_result(io, result);
|
|
101
|
+
result = result->next;
|
|
102
|
+
}
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
void io_clear(struct um_io *io) {
|
|
107
|
+
if (io->op && io->machine->ring_initialized) {
|
|
108
|
+
if (OP_CQE_SEEN_P(io->op)) {
|
|
109
|
+
size_t total_bytes = 0;
|
|
110
|
+
int more = false;
|
|
111
|
+
io_process_segments(io, &total_bytes, &more);
|
|
112
|
+
um_op_multishot_results_clear(io->machine, io->op);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (OP_CQE_DONE_P(io->op))
|
|
116
|
+
um_op_release(io->machine, io->op);
|
|
117
|
+
else
|
|
118
|
+
um_cancel_op_and_discard_cqe(io->machine, io->op);
|
|
119
|
+
|
|
120
|
+
io->op = NULL;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
while (io->head) {
|
|
124
|
+
struct um_segment *next = io->head->next;
|
|
125
|
+
um_segment_checkin(io->machine, io->head);
|
|
126
|
+
io->head = next;
|
|
127
|
+
}
|
|
128
|
+
io->pending_bytes = 0;
|
|
129
|
+
|
|
130
|
+
if (io->working_buffer) {
|
|
131
|
+
bp_buffer_checkin(io->machine, io->working_buffer);
|
|
132
|
+
io->working_buffer = NULL;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
inline void io_await_segments(struct um_io *io) {
|
|
137
|
+
if (unlikely(!io->op)) io_multishot_op_start(io);
|
|
138
|
+
|
|
139
|
+
if (!OP_CQE_SEEN_P(io->op)) {
|
|
140
|
+
io->op->flags &= ~OP_F_ASYNC;
|
|
141
|
+
VALUE ret = um_yield(io->machine);
|
|
142
|
+
io->op->flags |= OP_F_ASYNC;
|
|
143
|
+
if (!OP_CQE_SEEN_P(io->op)) RAISE_IF_EXCEPTION(ret);
|
|
144
|
+
RB_GC_GUARD(ret);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
int io_get_more_segments_bp(struct um_io *io) {
|
|
149
|
+
size_t total_bytes = 0;
|
|
150
|
+
int more = false;
|
|
151
|
+
int enobufs = false;
|
|
152
|
+
|
|
153
|
+
while (1) {
|
|
154
|
+
if (unlikely(io->eof)) return 0;
|
|
155
|
+
|
|
156
|
+
io_await_segments(io);
|
|
157
|
+
enobufs = io_process_segments(io, &total_bytes, &more);
|
|
158
|
+
um_op_multishot_results_clear(io->machine, io->op);
|
|
159
|
+
if (unlikely(enobufs)) {
|
|
160
|
+
int should_restart = io->pending_bytes < (io->machine->bp_buffer_size * 4);
|
|
161
|
+
// int same_threshold = io->op->bp_commit_level == io->machine->bp_commit_level;
|
|
162
|
+
|
|
163
|
+
// fprintf(stderr, "%p enobufs total: %ld pending: %ld threshold: %ld bc: %d (same: %d, restart: %d)\n",
|
|
164
|
+
// io,
|
|
165
|
+
// total_bytes, io->pending_bytes, io->machine->bp_commit_level,
|
|
166
|
+
// io->machine->bp_buffer_count,
|
|
167
|
+
// same_threshold, should_restart
|
|
168
|
+
// );
|
|
169
|
+
|
|
170
|
+
// If multiple IO ops are happening at the same time, they'll all
|
|
171
|
+
// get ENOBUFS! We track the commit threshold in the op in order to
|
|
172
|
+
// prevent running bp_handle_enobufs() more than once.
|
|
173
|
+
|
|
174
|
+
if (should_restart) {
|
|
175
|
+
if (io->op->bp_commit_level == io->machine->bp_commit_level)
|
|
176
|
+
bp_handle_enobufs(io->machine);
|
|
177
|
+
|
|
178
|
+
um_op_release(io->machine, io->op);
|
|
179
|
+
io->op = NULL;
|
|
180
|
+
// io_multishot_op_start(io);
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
um_op_release(io->machine, io->op);
|
|
184
|
+
io->op = NULL;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (total_bytes) return total_bytes;
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
if (more)
|
|
191
|
+
io->op->flags &= ~OP_F_CQE_SEEN;
|
|
192
|
+
if (total_bytes || io->eof) return total_bytes;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
int io_get_more_segments_ssl(struct um_io *io) {
|
|
198
|
+
if (!io->working_buffer)
|
|
199
|
+
io->working_buffer = bp_buffer_checkout(io->machine);
|
|
200
|
+
|
|
201
|
+
char *ptr = io->working_buffer->buf + io->working_buffer->pos;
|
|
202
|
+
size_t maxlen = io->working_buffer->len - io->working_buffer->pos;
|
|
203
|
+
int res = um_ssl_read_raw(io->machine, io->target, ptr, maxlen);
|
|
204
|
+
if (res == 0) return 0;
|
|
205
|
+
if (res < 0) rb_raise(eUMError, "Failed to read segment");
|
|
206
|
+
|
|
207
|
+
struct um_segment *segment = bp_buffer_consume(io->machine, io->working_buffer, res);
|
|
208
|
+
if ((size_t)res == maxlen) {
|
|
209
|
+
bp_buffer_checkin(io->machine, io->working_buffer);
|
|
210
|
+
io->working_buffer = NULL;
|
|
211
|
+
}
|
|
212
|
+
io_add_segment(io, segment);
|
|
213
|
+
return 1;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
int io_get_more_segments(struct um_io *io) {
|
|
217
|
+
switch (io->mode) {
|
|
218
|
+
case IO_FD:
|
|
219
|
+
case IO_SOCKET:
|
|
220
|
+
return io_get_more_segments_bp(io);
|
|
221
|
+
case IO_SSL:
|
|
222
|
+
return io_get_more_segments_ssl(io);
|
|
223
|
+
default:
|
|
224
|
+
rb_raise(eUMError, "Invalid IO mode");
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
////////////////////////////////////////////////////////////////////////////////
|
|
229
|
+
|
|
230
|
+
inline void io_shift_head(struct um_io *io) {
|
|
231
|
+
struct um_segment *consumed = io->head;
|
|
232
|
+
io->head = consumed->next;
|
|
233
|
+
if (!io->head) io->tail = NULL;
|
|
234
|
+
um_segment_checkin(io->machine, consumed);
|
|
235
|
+
io->pos = 0;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
inline VALUE make_segment_io_buffer(struct um_segment *segment, size_t pos) {
|
|
239
|
+
return rb_io_buffer_new(
|
|
240
|
+
segment->ptr + pos, segment->len - pos,
|
|
241
|
+
RB_IO_BUFFER_LOCKED|RB_IO_BUFFER_READONLY
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
inline void io_skip(struct um_io *io, size_t inc, int safe_inc) {
|
|
246
|
+
if (unlikely(io->eof && !io->head)) return;
|
|
247
|
+
if (safe_inc && !io->tail && !io_get_more_segments(io)) return;
|
|
248
|
+
|
|
249
|
+
while (inc) {
|
|
250
|
+
size_t segment_len = io->head->len - io->pos;
|
|
251
|
+
size_t inc_len = (segment_len <= inc) ? segment_len : inc;
|
|
252
|
+
inc -= inc_len;
|
|
253
|
+
io->pos += inc_len;
|
|
254
|
+
io->consumed_bytes += inc_len;
|
|
255
|
+
io->pending_bytes -= inc_len;
|
|
256
|
+
if (io->pos == io->head->len) {
|
|
257
|
+
io_shift_head(io);
|
|
258
|
+
if (inc && safe_inc && !io->head) {
|
|
259
|
+
if (!io_get_more_segments(io)) break;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
inline void io_read_each(struct um_io *io) {
|
|
266
|
+
if (unlikely(io->eof && !io->head)) return;
|
|
267
|
+
if (!io->tail && !io_get_more_segments(io)) return;
|
|
268
|
+
|
|
269
|
+
struct um_segment *current = io->head;
|
|
270
|
+
size_t pos = io->pos;
|
|
271
|
+
|
|
272
|
+
VALUE buffer = Qnil;
|
|
273
|
+
while (true) {
|
|
274
|
+
struct um_segment *next = current->next;
|
|
275
|
+
buffer = make_segment_io_buffer(current, pos);
|
|
276
|
+
rb_yield(buffer);
|
|
277
|
+
rb_io_buffer_free_locked(buffer);
|
|
278
|
+
io_shift_head(io);
|
|
279
|
+
|
|
280
|
+
if (!next) {
|
|
281
|
+
if (!io_get_more_segments(io)) return;
|
|
282
|
+
}
|
|
283
|
+
current = io->head;
|
|
284
|
+
pos = 0;
|
|
285
|
+
}
|
|
286
|
+
RB_GC_GUARD(buffer);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
inline void io_copy(struct um_io *io, char *dest, size_t len) {
|
|
290
|
+
while (len) {
|
|
291
|
+
char *segment_ptr = io->head->ptr + io->pos;
|
|
292
|
+
size_t segment_len = io->head->len - io->pos;
|
|
293
|
+
size_t cpy_len = (segment_len <= len) ? segment_len : len;
|
|
294
|
+
memcpy(dest, segment_ptr, cpy_len);
|
|
295
|
+
|
|
296
|
+
len -= cpy_len;
|
|
297
|
+
io->pos += cpy_len;
|
|
298
|
+
io->consumed_bytes += cpy_len;
|
|
299
|
+
io->pending_bytes -= cpy_len;
|
|
300
|
+
dest += cpy_len;
|
|
301
|
+
if (io->pos == io->head->len) io_shift_head(io);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
VALUE io_consume_string(struct um_io *io, VALUE out_buffer, size_t len, size_t inc, int safe_inc) {
|
|
306
|
+
VALUE str = Qnil;
|
|
307
|
+
if (!NIL_P(out_buffer)) {
|
|
308
|
+
str = out_buffer;
|
|
309
|
+
size_t str_len = RSTRING_LEN(str);
|
|
310
|
+
if (str_len < len)
|
|
311
|
+
rb_str_resize(str, len);
|
|
312
|
+
else if (str_len > len)
|
|
313
|
+
rb_str_set_len(str, len);
|
|
314
|
+
}
|
|
315
|
+
else
|
|
316
|
+
str = rb_str_new(NULL, len);
|
|
317
|
+
char *dest = RSTRING_PTR(str);
|
|
318
|
+
|
|
319
|
+
io_copy(io, dest, len);
|
|
320
|
+
io_skip(io, inc, safe_inc);
|
|
321
|
+
return str;
|
|
322
|
+
RB_GC_GUARD(str);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
inline int trailing_cr_p(char *ptr, size_t len) {
|
|
326
|
+
return ptr[len - 1] == '\r';
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
VALUE io_read_line(struct um_io *io, VALUE out_buffer, size_t maxlen) {
|
|
330
|
+
if (unlikely(io->eof && !io->head)) return Qnil;
|
|
331
|
+
if (!io->tail && !io_get_more_segments(io)) return Qnil;
|
|
332
|
+
|
|
333
|
+
struct um_segment *last = NULL;
|
|
334
|
+
struct um_segment *current = io->head;
|
|
335
|
+
size_t remaining_len = maxlen;
|
|
336
|
+
size_t total_len = 0;
|
|
337
|
+
size_t inc = 1;
|
|
338
|
+
size_t pos = io->pos;
|
|
339
|
+
|
|
340
|
+
while (true) {
|
|
341
|
+
size_t segment_len = current->len - pos;
|
|
342
|
+
size_t search_len = segment_len;
|
|
343
|
+
if (maxlen && (search_len > remaining_len)) search_len = remaining_len;
|
|
344
|
+
char *start = current->ptr + pos;
|
|
345
|
+
char *lf_ptr = memchr(start, '\n', search_len);
|
|
346
|
+
|
|
347
|
+
if (lf_ptr) {
|
|
348
|
+
size_t len = lf_ptr - start;
|
|
349
|
+
|
|
350
|
+
total_len += len;
|
|
351
|
+
|
|
352
|
+
// search for \r
|
|
353
|
+
if (total_len > 0) {
|
|
354
|
+
if ((len && trailing_cr_p(start, len)) ||
|
|
355
|
+
(!len && last && trailing_cr_p(last->ptr, last->len))
|
|
356
|
+
) {
|
|
357
|
+
total_len -= 1;
|
|
358
|
+
inc = 2;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return io_consume_string(io, out_buffer, total_len, inc, false);
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
// not found, early return if segment len exceeds maxlen
|
|
366
|
+
if (maxlen && segment_len >= maxlen) return Qnil;
|
|
367
|
+
|
|
368
|
+
total_len += segment_len;
|
|
369
|
+
remaining_len -= segment_len;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (!current->next) {
|
|
373
|
+
if (!io_get_more_segments(io)) {
|
|
374
|
+
return Qnil;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
last = current;
|
|
379
|
+
current = current->next;
|
|
380
|
+
pos = 0;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
VALUE io_read(struct um_io *io, VALUE out_buffer, ssize_t len, size_t inc, int safe_inc) {
|
|
385
|
+
if (unlikely(io->eof && !io->head)) return Qnil;
|
|
386
|
+
if (!io->tail && !io_get_more_segments(io)) return Qnil;
|
|
387
|
+
|
|
388
|
+
struct um_segment *current = io->head;
|
|
389
|
+
size_t abs_len = labs(len);
|
|
390
|
+
size_t remaining_len = abs_len;
|
|
391
|
+
size_t total_len = 0;
|
|
392
|
+
size_t pos = io->pos;
|
|
393
|
+
|
|
394
|
+
while (true) {
|
|
395
|
+
size_t segment_len = current->len - pos;
|
|
396
|
+
if (abs_len && segment_len > remaining_len) {
|
|
397
|
+
segment_len = remaining_len;
|
|
398
|
+
}
|
|
399
|
+
total_len += segment_len;
|
|
400
|
+
if (abs_len) {
|
|
401
|
+
remaining_len -= segment_len;
|
|
402
|
+
if (!remaining_len)
|
|
403
|
+
return io_consume_string(io, out_buffer, total_len, inc, safe_inc);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (!current->next) {
|
|
407
|
+
if (len <= 0)
|
|
408
|
+
return io_consume_string(io, out_buffer, total_len, inc, safe_inc);
|
|
409
|
+
|
|
410
|
+
if (!io_get_more_segments(io))
|
|
411
|
+
return Qnil;
|
|
412
|
+
}
|
|
413
|
+
current = current->next;
|
|
414
|
+
pos = 0;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
static inline char delim_to_char(VALUE delim) {
|
|
419
|
+
if (TYPE(delim) != T_STRING)
|
|
420
|
+
rb_raise(rb_eArgError, "Delimiter must be a string");
|
|
421
|
+
|
|
422
|
+
if (RSTRING_LEN(delim) != 1)
|
|
423
|
+
rb_raise(eUMError, "Delimiter must be a single byte string");
|
|
424
|
+
|
|
425
|
+
return *RSTRING_PTR(delim);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
VALUE io_read_to_delim(struct um_io *io, VALUE out_buffer, VALUE delim, ssize_t maxlen) {
|
|
429
|
+
char delim_char = delim_to_char(delim);
|
|
430
|
+
|
|
431
|
+
if (unlikely(io->eof && !io->head)) return Qnil;
|
|
432
|
+
if (unlikely(!io->tail) && !io_get_more_segments(io)) return Qnil;
|
|
433
|
+
|
|
434
|
+
struct um_segment *current = io->head;
|
|
435
|
+
size_t abs_maxlen = labs(maxlen);
|
|
436
|
+
size_t remaining_len = abs_maxlen;
|
|
437
|
+
size_t total_len = 0;
|
|
438
|
+
size_t pos = io->pos;
|
|
439
|
+
|
|
440
|
+
while (true) {
|
|
441
|
+
size_t segment_len = current->len - pos;
|
|
442
|
+
size_t search_len = segment_len;
|
|
443
|
+
if (maxlen && (search_len > remaining_len)) search_len = remaining_len;
|
|
444
|
+
char *start = current->ptr + pos;
|
|
445
|
+
char *delim_ptr = memchr(start, delim_char, search_len);
|
|
446
|
+
|
|
447
|
+
if (delim_ptr) {
|
|
448
|
+
size_t len = delim_ptr - start;
|
|
449
|
+
total_len += len;
|
|
450
|
+
return io_consume_string(io, out_buffer, total_len, 1, false);
|
|
451
|
+
}
|
|
452
|
+
else {
|
|
453
|
+
// delimiter not found
|
|
454
|
+
total_len += search_len;
|
|
455
|
+
remaining_len -= search_len;
|
|
456
|
+
|
|
457
|
+
if (abs_maxlen && total_len >= abs_maxlen)
|
|
458
|
+
return (maxlen > 0) ? Qnil : io_consume_string(io, out_buffer, abs_maxlen, 1, false);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
if (!current->next && !io_get_more_segments(io)) return Qnil;
|
|
462
|
+
|
|
463
|
+
current = current->next;
|
|
464
|
+
pos = 0;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
size_t io_write_raw(struct um_io *io, const char *buffer, size_t len) {
|
|
469
|
+
switch (io->mode) {
|
|
470
|
+
case IO_FD:
|
|
471
|
+
return um_write_raw(io->machine, io->fd, buffer, len);
|
|
472
|
+
case IO_SOCKET:
|
|
473
|
+
return um_send_raw(io->machine, io->fd, buffer, len, 0);
|
|
474
|
+
case IO_SSL:
|
|
475
|
+
return um_ssl_write_raw(io->machine, io->target, buffer, len);
|
|
476
|
+
default:
|
|
477
|
+
rb_raise(eUMError, "Invalid IO mode");
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
VALUE io_writev(struct um_io *io, int argc, VALUE *argv) {
|
|
482
|
+
switch (io->mode) {
|
|
483
|
+
case IO_FD:
|
|
484
|
+
return um_writev(io->machine, io->fd, argc, argv);
|
|
485
|
+
case IO_SOCKET:
|
|
486
|
+
return um_sendv(io->machine, io->fd, argc, argv);
|
|
487
|
+
case IO_SSL:
|
|
488
|
+
return ULONG2NUM(um_ssl_writev(io->machine, io->target, argc, argv));
|
|
489
|
+
default:
|
|
490
|
+
rb_raise(eUMError, "Invalid IO mode");
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
////////////////////////////////////////////////////////////////////////////////
|
|
495
|
+
|
|
496
|
+
VALUE resp_read_line(struct um_io *io, VALUE out_buffer) {
|
|
497
|
+
if (unlikely(io->eof && !io->head)) return Qnil;
|
|
498
|
+
if (!io->tail && !io_get_more_segments(io)) return Qnil;
|
|
499
|
+
|
|
500
|
+
struct um_segment *current = io->head;
|
|
501
|
+
size_t total_len = 0;
|
|
502
|
+
size_t pos = io->pos;
|
|
503
|
+
|
|
504
|
+
while (true) {
|
|
505
|
+
size_t segment_len = current->len - pos;
|
|
506
|
+
char *start = current->ptr + pos;
|
|
507
|
+
char *lf_ptr = memchr(start, '\r', segment_len);
|
|
508
|
+
if (lf_ptr) {
|
|
509
|
+
size_t len = lf_ptr - start;
|
|
510
|
+
total_len += len;
|
|
511
|
+
return io_consume_string(io, out_buffer, total_len, 2, true);
|
|
512
|
+
}
|
|
513
|
+
else
|
|
514
|
+
total_len += segment_len;
|
|
515
|
+
|
|
516
|
+
if (!current->next)
|
|
517
|
+
if (!io_get_more_segments(io)) return Qnil;
|
|
518
|
+
|
|
519
|
+
current = current->next;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
inline VALUE resp_read_string(struct um_io *io, ulong len, VALUE out_buffer) {
|
|
524
|
+
return io_read(io, out_buffer, len, 2, true);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
inline ulong resp_parse_length_field(const char *ptr, int len) {
|
|
528
|
+
ulong acc = 0;
|
|
529
|
+
for(int i = 1; i < len; i++)
|
|
530
|
+
acc = acc * 10 + (ptr[i] - '0');
|
|
531
|
+
return acc;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
VALUE resp_decode_hash(struct um_io *io, VALUE out_buffer, ulong len) {
|
|
535
|
+
VALUE hash = rb_hash_new();
|
|
536
|
+
|
|
537
|
+
for (ulong i = 0; i < len; i++) {
|
|
538
|
+
VALUE key = resp_read(io, out_buffer);
|
|
539
|
+
VALUE value = resp_read(io, out_buffer);
|
|
540
|
+
rb_hash_aset(hash, key, value);
|
|
541
|
+
RB_GC_GUARD(key);
|
|
542
|
+
RB_GC_GUARD(value);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
RB_GC_GUARD(hash);
|
|
546
|
+
return hash;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
VALUE resp_decode_array(struct um_io *io, VALUE out_buffer, ulong len) {
|
|
550
|
+
VALUE array = rb_ary_new2(len);
|
|
551
|
+
|
|
552
|
+
for (ulong i = 0; i < len; i++) {
|
|
553
|
+
VALUE value = resp_read(io, out_buffer);
|
|
554
|
+
rb_ary_push(array, value);
|
|
555
|
+
RB_GC_GUARD(value);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
RB_GC_GUARD(array);
|
|
559
|
+
return array;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
static inline VALUE resp_decode_simple_string(char *ptr, ulong len) {
|
|
563
|
+
return rb_str_new(ptr + 1, len - 1);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
static inline VALUE resp_decode_string(struct um_io *io, VALUE out_buffer, ulong len) {
|
|
567
|
+
return resp_read_string(io, len, out_buffer);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
static inline VALUE resp_decode_string_with_encoding(struct um_io *io, VALUE out_buffer, ulong len) {
|
|
571
|
+
VALUE with_enc = resp_read_string(io, len, out_buffer);
|
|
572
|
+
char *ptr = RSTRING_PTR(with_enc);
|
|
573
|
+
len = RSTRING_LEN(with_enc);
|
|
574
|
+
if ((len < 4) || (ptr[3] != ':')) return Qnil;
|
|
575
|
+
|
|
576
|
+
return rb_utf8_str_new(ptr + 4, len - 4);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
static inline VALUE resp_decode_integer(char *ptr) {
|
|
580
|
+
long value = strtol(ptr + 1, NULL, 10);
|
|
581
|
+
return LONG2NUM(value);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
static inline VALUE resp_decode_float(char *ptr) {
|
|
585
|
+
double value = strtod(ptr + 1, NULL);
|
|
586
|
+
return DBL2NUM(value);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
static inline VALUE resp_decode_simple_error(char *ptr, ulong len) {
|
|
590
|
+
static ID ID_new = 0;
|
|
591
|
+
if (!ID_new) ID_new = rb_intern("new");
|
|
592
|
+
|
|
593
|
+
VALUE msg = rb_str_new(ptr + 1, len - 1);
|
|
594
|
+
VALUE err = rb_funcall(eIORESPError, ID_new, 1, msg);
|
|
595
|
+
RB_GC_GUARD(msg);
|
|
596
|
+
return err;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
static inline VALUE resp_decode_error(struct um_io *io, VALUE out_buffer, ulong len) {
|
|
600
|
+
static ID ID_new = 0;
|
|
601
|
+
if (!ID_new) ID_new = rb_intern("new");
|
|
602
|
+
|
|
603
|
+
VALUE msg = resp_decode_string(io, out_buffer, len);
|
|
604
|
+
VALUE err = rb_funcall(eIORESPError, ID_new, 1, msg);
|
|
605
|
+
RB_GC_GUARD(msg);
|
|
606
|
+
return err;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
VALUE resp_read(struct um_io *io, VALUE out_buffer) {
|
|
610
|
+
VALUE msg = resp_read_line(io, out_buffer);
|
|
611
|
+
if (msg == Qnil) return Qnil;
|
|
612
|
+
|
|
613
|
+
char *ptr = RSTRING_PTR(msg);
|
|
614
|
+
ulong len = RSTRING_LEN(msg);
|
|
615
|
+
ulong data_len;
|
|
616
|
+
if (len == 0) return Qnil;
|
|
617
|
+
|
|
618
|
+
switch (ptr[0]) {
|
|
619
|
+
case '%': // hash
|
|
620
|
+
case '|': // attributes hash
|
|
621
|
+
data_len = resp_parse_length_field(ptr, len);
|
|
622
|
+
return resp_decode_hash(io, out_buffer, data_len);
|
|
623
|
+
|
|
624
|
+
case '*': // array
|
|
625
|
+
case '~': // set
|
|
626
|
+
case '>': // pub/sub push
|
|
627
|
+
data_len = resp_parse_length_field(ptr, len);
|
|
628
|
+
return resp_decode_array(io, out_buffer, data_len);
|
|
629
|
+
|
|
630
|
+
case '+': // simple string
|
|
631
|
+
return resp_decode_simple_string(ptr, len);
|
|
632
|
+
case '$': // string
|
|
633
|
+
data_len = resp_parse_length_field(ptr, len);
|
|
634
|
+
return resp_decode_string(io, out_buffer, data_len);
|
|
635
|
+
case '=': // string with encoding
|
|
636
|
+
data_len = resp_parse_length_field(ptr, len);
|
|
637
|
+
return resp_decode_string_with_encoding(io, out_buffer, data_len);
|
|
638
|
+
|
|
639
|
+
case '_': // null
|
|
640
|
+
return Qnil;
|
|
641
|
+
case '#': // boolean
|
|
642
|
+
return (len > 1) && (ptr[1] == 't') ? Qtrue : Qfalse;
|
|
643
|
+
|
|
644
|
+
case ':': // integer
|
|
645
|
+
return resp_decode_integer(ptr);
|
|
646
|
+
case '(': // big integer
|
|
647
|
+
um_raise_internal_error("Big integers are not supported");
|
|
648
|
+
case ',': // float
|
|
649
|
+
return resp_decode_float(ptr);
|
|
650
|
+
|
|
651
|
+
case '-': // simple error
|
|
652
|
+
return resp_decode_simple_error(ptr, len);
|
|
653
|
+
case '!': // error
|
|
654
|
+
data_len = resp_parse_length_field(ptr, len);
|
|
655
|
+
return resp_decode_error(io, out_buffer, data_len);
|
|
656
|
+
default:
|
|
657
|
+
um_raise_internal_error("Invalid character encountered");
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
RB_GC_GUARD(msg);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
void write_buffer_init(struct um_write_buffer *buf, VALUE str) {
|
|
664
|
+
size_t capa = 1 << 12;
|
|
665
|
+
size_t len = RSTRING_LEN(str);
|
|
666
|
+
while (capa < len) capa += 1 << 12;
|
|
667
|
+
|
|
668
|
+
rb_str_resize(str, capa);
|
|
669
|
+
rb_str_set_len(str, len);
|
|
670
|
+
buf->str = str;
|
|
671
|
+
buf->capa = capa;
|
|
672
|
+
buf->len = len;
|
|
673
|
+
buf->ptr = RSTRING_PTR(str);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
static inline void write_buffer_expand(struct um_write_buffer *buf, size_t newsize) {
|
|
677
|
+
if (buf->capa < newsize) {
|
|
678
|
+
size_t old_capa = buf->capa;
|
|
679
|
+
while (buf->capa < newsize) buf->capa += 1 << 12;
|
|
680
|
+
rb_str_modify_expand(buf->str, buf->capa - old_capa);
|
|
681
|
+
buf->ptr = RSTRING_PTR(buf->str);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
static inline void write_buffer_append(struct um_write_buffer *buf, const char *ptr, size_t len) {
|
|
686
|
+
size_t total_len = buf->len + len;
|
|
687
|
+
write_buffer_expand(buf, total_len);
|
|
688
|
+
|
|
689
|
+
memcpy(buf->ptr + buf->len, ptr, len);
|
|
690
|
+
buf->len = total_len;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
static inline void write_buffer_append_cstr(struct um_write_buffer *buf, const char *str) {
|
|
694
|
+
write_buffer_append(buf, str, strlen(str));
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
static inline void write_buffer_append_resp_bulk_string(struct um_write_buffer *buf, VALUE str) {
|
|
698
|
+
// leave enough place for prefix and postfix
|
|
699
|
+
size_t str_len = RSTRING_LEN(str);
|
|
700
|
+
size_t total_len = buf->len + str_len + 16;
|
|
701
|
+
write_buffer_expand(buf, total_len);
|
|
702
|
+
|
|
703
|
+
|
|
704
|
+
int prefix_len = sprintf(buf->ptr + buf->len, "$%ld\r\n", str_len);
|
|
705
|
+
const char *src = RSTRING_PTR(str);
|
|
706
|
+
memcpy(buf->ptr + buf->len + prefix_len, src, str_len);
|
|
707
|
+
buf->ptr[buf->len + prefix_len + str_len + 0] = '\r';
|
|
708
|
+
buf->ptr[buf->len + prefix_len + str_len + 1] = '\n';
|
|
709
|
+
buf->len += prefix_len + str_len + 2;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
inline void write_buffer_update_len(struct um_write_buffer *buf) {
|
|
713
|
+
rb_str_set_len(buf->str, buf->len);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
struct resp_encode_hash_ctx {
|
|
717
|
+
struct um_write_buffer *buf;
|
|
718
|
+
VALUE obj;
|
|
719
|
+
};
|
|
720
|
+
|
|
721
|
+
int resp_encode_hash_entry(VALUE key, VALUE value, VALUE arg) {
|
|
722
|
+
struct resp_encode_hash_ctx *ctx = (struct resp_encode_hash_ctx *)arg;
|
|
723
|
+
|
|
724
|
+
resp_encode(ctx->buf, key);
|
|
725
|
+
resp_encode(ctx->buf, value);
|
|
726
|
+
return 0;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
void resp_encode(struct um_write_buffer *buf, VALUE obj) {
|
|
730
|
+
char tmp[60];
|
|
731
|
+
|
|
732
|
+
switch (TYPE(obj)) {
|
|
733
|
+
case T_NIL:
|
|
734
|
+
return write_buffer_append_cstr(buf, "_\r\n");
|
|
735
|
+
return;
|
|
736
|
+
case T_FALSE:
|
|
737
|
+
write_buffer_append_cstr(buf, "#f\r\n");
|
|
738
|
+
return;
|
|
739
|
+
case T_TRUE:
|
|
740
|
+
write_buffer_append_cstr(buf, "#t\r\n");
|
|
741
|
+
return;
|
|
742
|
+
case T_FIXNUM:
|
|
743
|
+
sprintf(tmp, ":%ld\r\n", NUM2LONG(obj));
|
|
744
|
+
write_buffer_append_cstr(buf, tmp);
|
|
745
|
+
return;
|
|
746
|
+
case T_FLOAT:
|
|
747
|
+
sprintf(tmp, ",%lg\r\n", NUM2DBL(obj));
|
|
748
|
+
write_buffer_append_cstr(buf, tmp);
|
|
749
|
+
return;
|
|
750
|
+
case T_STRING:
|
|
751
|
+
write_buffer_append_resp_bulk_string(buf, obj);
|
|
752
|
+
return;
|
|
753
|
+
case T_ARRAY:
|
|
754
|
+
{
|
|
755
|
+
ulong len = RARRAY_LEN(obj);
|
|
756
|
+
sprintf(tmp, "*%ld\r\n", len);
|
|
757
|
+
write_buffer_append_cstr(buf, tmp);
|
|
758
|
+
for (ulong i = 0; i < len; i++)
|
|
759
|
+
resp_encode(buf, rb_ary_entry(obj, i));
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
case T_HASH:
|
|
763
|
+
{
|
|
764
|
+
ulong len = rb_hash_size_num(obj);
|
|
765
|
+
sprintf(tmp, "%%%ld\r\n", len);
|
|
766
|
+
write_buffer_append_cstr(buf, tmp);
|
|
767
|
+
|
|
768
|
+
struct resp_encode_hash_ctx ctx = { buf, obj };
|
|
769
|
+
rb_hash_foreach(obj, resp_encode_hash_entry, (VALUE)&ctx);
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
default:
|
|
773
|
+
um_raise_internal_error("Can't encode object");
|
|
774
|
+
}
|
|
775
|
+
}
|