polyphony 0.54.0 → 0.55.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +3 -1
- data/CHANGELOG.md +32 -25
- data/Gemfile.lock +1 -1
- data/TODO.md +0 -3
- data/examples/io/stdio.rb +8 -0
- data/ext/polyphony/backend_common.h +10 -10
- data/ext/polyphony/backend_io_uring.c +178 -70
- data/ext/polyphony/backend_io_uring_context.c +14 -2
- data/ext/polyphony/backend_io_uring_context.h +11 -11
- data/ext/polyphony/backend_libev.c +1 -14
- data/ext/polyphony/polyphony.h +1 -0
- data/ext/polyphony/runqueue.c +29 -1
- data/ext/polyphony/thread.c +14 -4
- data/lib/polyphony/extensions/socket.rb +6 -20
- data/lib/polyphony/version.rb +1 -1
- data/test/helper.rb +3 -3
- data/test/test_backend.rb +46 -2
- data/test/test_fiber.rb +0 -1
- data/test/test_io.rb +6 -3
- data/test/test_signal.rb +1 -1
- data/test/test_thread_pool.rb +1 -1
- data/test/test_timer.rb +13 -7
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2f0f6698a072e4ba2913477d20f5884d5d7579b99cca1db0f2ffa3aeca42ebcc
|
4
|
+
data.tar.gz: 97fec9986f6693d685bfb0c1745995c8a85413a9b6cc1a1ff69393d55a5f3b74
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 053beffc7c27658d129002b5efc29f64c7fcf1ff7fbf3f2eea1d588b2a9e40e89251640bf9bb82ce478837f38c32863987d8ac0af987d374d4ac242d012363dd
|
7
|
+
data.tar.gz: e89df4044f09f4df64fc57622f22e70d932f2f09ea3b2a5c2090698784c286367df43d352231068b8ef96d6a7ba89e5492b4c237c9960aeb5fa79af9ee738768
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,11 @@
|
|
1
|
-
## 0.
|
1
|
+
## 0.55.0 2021-06-17
|
2
|
+
|
3
|
+
- Finish io_uring implementation of Backend#chain
|
4
|
+
- Reimplement io_uring op_context acquire/release algorithm (using ref count)
|
5
|
+
- Fix #gets on sockets
|
6
|
+
- Redesign event anti-starvation mechanism
|
7
|
+
|
8
|
+
## 0.54.0 2021-06-14
|
2
9
|
|
3
10
|
- Implement Mutex#owned?, #locked? (#50)
|
4
11
|
- Fix arity for SSLSocket#peeraddr (#55)
|
@@ -6,15 +13,15 @@
|
|
6
13
|
- Fix SSLSocket buffering behaviour
|
7
14
|
- Add recv_loop alias for SSLSocket (#54)
|
8
15
|
|
9
|
-
## 0.53.2
|
16
|
+
## 0.53.2 2021-05-10
|
10
17
|
|
11
18
|
- Remove `splice` methods on libev backend on non-Linux OS (#43)
|
12
19
|
|
13
|
-
## 0.53.0
|
20
|
+
## 0.53.0 2021-04-23
|
14
21
|
|
15
22
|
- Implement `Backend#splice`, `Backend#splice_to_eof`, along with `IO#splice`, `IO#splice_to_eof`
|
16
23
|
|
17
|
-
## 0.52.0
|
24
|
+
## 0.52.0 2021-02-28
|
18
25
|
|
19
26
|
- Polyphony is now compatible with Ruby 3.0
|
20
27
|
- Add `Backend#sendv` method for sending multiple strings
|
@@ -24,19 +31,19 @@
|
|
24
31
|
- libev backend: Use` pidfd_open` for Linux 5.3+, otherwise use a libev child watcher
|
25
32
|
- Use `:call` as default method in `#feed_loop`
|
26
33
|
|
27
|
-
## 0.51.0
|
34
|
+
## 0.51.0 2021-02-02
|
28
35
|
|
29
36
|
- Implement `IO#feed_loop`, `Socket#feed_loop`
|
30
37
|
- Fix error handling in `Process.kill_and_await`
|
31
38
|
|
32
|
-
## 0.50.1
|
39
|
+
## 0.50.1 2021-01-31
|
33
40
|
|
34
41
|
- Set `IOSQE_ASYNC` flag in io_uring backend
|
35
42
|
- Fix error handling in `Backend#waitpid`
|
36
43
|
- Reimplement libev backend's `#waitpid` by using pidfd_open (in similar manner
|
37
44
|
to the io_uring backend)
|
38
45
|
|
39
|
-
## 0.50.0
|
46
|
+
## 0.50.0 2021-01-28
|
40
47
|
|
41
48
|
- Use `Process::CLOCK_MONOTONIC` in Timer
|
42
49
|
- Add `Timer#sleep`, `Timer#after`, `Timer#every`
|
@@ -44,50 +51,50 @@
|
|
44
51
|
- Add `Thread#fiber_index_of` method
|
45
52
|
- Use `Backend#wait_event` in `Fiber#await`
|
46
53
|
|
47
|
-
## 0.49.2
|
54
|
+
## 0.49.2 2021-01-19
|
48
55
|
|
49
56
|
- Fix hang with 100s or more child fibers when terminating
|
50
57
|
- Fix double pending_count increment in io_uring backend
|
51
58
|
|
52
|
-
## 0.49.1
|
59
|
+
## 0.49.1 2021-01-13
|
53
60
|
|
54
61
|
- Use `TCPSocket` instead of `Socket` in `Net.tcp_connect`
|
55
62
|
- Catch `Errno::ERSCH` in `Process.kill_and_await`
|
56
63
|
- Set io_uring queue size to 2048
|
57
64
|
|
58
|
-
## 0.49.0
|
65
|
+
## 0.49.0 2021-01-11
|
59
66
|
|
60
67
|
- Implement `Polyphony::Timer` for performant timeouts
|
61
68
|
|
62
|
-
## 0.48.0
|
69
|
+
## 0.48.0 2021-01-05
|
63
70
|
|
64
71
|
- Implement graceful shutdown
|
65
72
|
- Add support for `break` / `StopIteration` in `spin_loop`
|
66
73
|
- Fix `IO#gets`, `IO#readpartial`
|
67
74
|
|
68
|
-
## 0.47.5.1
|
75
|
+
## 0.47.5.1 2020-11-20
|
69
76
|
|
70
77
|
- Add missing `Socket#accept_loop` method
|
71
78
|
|
72
|
-
## 0.47.5
|
79
|
+
## 0.47.5 2020-11-20
|
73
80
|
|
74
81
|
- Add `socket_class` argument to `Backend#accept`, `Backend#accept_loop`
|
75
82
|
- Fix `#supervise` to stop when all children fibers are done
|
76
83
|
|
77
|
-
## 0.47.4
|
84
|
+
## 0.47.4 2020-11-14
|
78
85
|
|
79
86
|
- Add support for Unix sockets
|
80
87
|
|
81
|
-
## 0.47.3
|
88
|
+
## 0.47.3 2020-11-12
|
82
89
|
|
83
90
|
- Enable I/O in signal handlers (#45)
|
84
91
|
- Accept `:interval` argument in `#spin_loop`
|
85
92
|
|
86
|
-
## 0.47.2
|
93
|
+
## 0.47.2 2020-11-10
|
87
94
|
|
88
95
|
- Fix API compatibility between TCPSocket and IO
|
89
96
|
|
90
|
-
## 0.47.0
|
97
|
+
## 0.47.0 2020-11-10
|
91
98
|
|
92
99
|
- Implement `#spin_scope` used for creating blocking fiber scopes
|
93
100
|
- Reimplement `move_on_after`, `cancel_after`, `Timeout.timeout` using
|
@@ -95,18 +102,18 @@
|
|
95
102
|
- Implement `Backend#timeout` API
|
96
103
|
- Implemented capped queues
|
97
104
|
|
98
|
-
## 0.46.1
|
105
|
+
## 0.46.1 2020-11-04
|
99
106
|
|
100
107
|
- Add `TCPServer#accept_loop`, `OpenSSL::SSL::SSLSocket#accept_loop` method
|
101
108
|
- Fix compilation error on MacOS (#43)
|
102
109
|
- Fix backtrace for `Timeout.timeout`
|
103
110
|
- Add `Backend#timer_loop`
|
104
111
|
|
105
|
-
## 0.46.0
|
112
|
+
## 0.46.0 2020-10-08
|
106
113
|
|
107
114
|
- Implement [io_uring backend](https://github.com/digital-fabric/polyphony/pull/44)
|
108
115
|
|
109
|
-
## 0.45.5
|
116
|
+
## 0.45.5 2020-10-04
|
110
117
|
|
111
118
|
- Fix compilation error (#43)
|
112
119
|
- Add support for resetting move_on_after, cancel_after timeouts
|
@@ -115,22 +122,22 @@
|
|
115
122
|
- Schedule parent with priority on uncaught exception
|
116
123
|
- Fix race condition in `Mutex#synchronize` (#41)
|
117
124
|
|
118
|
-
## 0.45.4
|
125
|
+
## 0.45.4 2020-09-06
|
119
126
|
|
120
127
|
- Improve signal trapping mechanism
|
121
128
|
|
122
|
-
## 0.45.3
|
129
|
+
## 0.45.3 2020-09-02
|
123
130
|
|
124
131
|
- Don't swallow error in `Process#kill_and_await`
|
125
132
|
- Add `Fiber#mailbox` attribute reader
|
126
133
|
- Fix bug in `Fiber.await`
|
127
134
|
- Implement `IO#getc`, `IO#getbyte`
|
128
135
|
|
129
|
-
## 0.45.2
|
136
|
+
## 0.45.2 2020-08-03
|
130
137
|
|
131
138
|
- Rewrite `Fiber#<<`, `Fiber#await`, `Fiber#receive` in C
|
132
139
|
|
133
|
-
## 0.45.1
|
140
|
+
## 0.45.1 2020-08-01
|
134
141
|
|
135
142
|
- Fix Net::HTTP compatibility
|
136
143
|
- Fix fs adapter
|
@@ -140,7 +147,7 @@
|
|
140
147
|
- Cleanup code
|
141
148
|
- Improve support for Ruby 3 keyword args
|
142
149
|
|
143
|
-
## 0.45.0
|
150
|
+
## 0.45.0 2020-07-29
|
144
151
|
|
145
152
|
- Cleanup code
|
146
153
|
- Rename `Agent` to `Backend`
|
data/Gemfile.lock
CHANGED
data/TODO.md
CHANGED
@@ -10,9 +10,6 @@
|
|
10
10
|
|
11
11
|
- Add support for `break` and `StopIteration` in all loops (with tests)
|
12
12
|
|
13
|
-
- Change `IO#gets` to use `String#split` to cut into lines, much faster (see
|
14
|
-
examples/performance/line_splitting.rb)
|
15
|
-
|
16
13
|
- More tight loops
|
17
14
|
- `IO#gets_loop`, `Socket#gets_loop`, `OpenSSL::Socket#gets_loop` (medium effort)
|
18
15
|
- `Fiber#receive_loop` (very little effort, should be implemented in C)
|
@@ -26,7 +26,7 @@ struct io_internal_read_struct {
|
|
26
26
|
|
27
27
|
#define StringValue(v) rb_string_value(&(v))
|
28
28
|
|
29
|
-
int io_setstrbuf(VALUE *str, long len) {
|
29
|
+
inline int io_setstrbuf(VALUE *str, long len) {
|
30
30
|
#ifdef _WIN32
|
31
31
|
len = (len + 1) & ~1L; /* round up for wide char */
|
32
32
|
#endif
|
@@ -55,7 +55,7 @@ inline void io_shrink_read_string(VALUE str, long n) {
|
|
55
55
|
}
|
56
56
|
}
|
57
57
|
|
58
|
-
void io_set_read_length(VALUE str, long n, int shrinkable) {
|
58
|
+
inline void io_set_read_length(VALUE str, long n, int shrinkable) {
|
59
59
|
if (RSTRING_LEN(str) != n) {
|
60
60
|
rb_str_modify(str);
|
61
61
|
rb_str_set_len(str, n);
|
@@ -64,16 +64,16 @@ void io_set_read_length(VALUE str, long n, int shrinkable) {
|
|
64
64
|
}
|
65
65
|
|
66
66
|
inline rb_encoding* io_read_encoding(rb_io_t *fptr) {
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
67
|
+
if (fptr->encs.enc) {
|
68
|
+
return fptr->encs.enc;
|
69
|
+
}
|
70
|
+
return rb_default_external_encoding();
|
71
71
|
}
|
72
72
|
|
73
|
-
VALUE io_enc_str(VALUE str, rb_io_t *fptr) {
|
74
|
-
|
75
|
-
|
76
|
-
|
73
|
+
inline VALUE io_enc_str(VALUE str, rb_io_t *fptr) {
|
74
|
+
OBJ_TAINT(str);
|
75
|
+
rb_enc_associate(str, io_read_encoding(fptr));
|
76
|
+
return str;
|
77
77
|
}
|
78
78
|
|
79
79
|
//////////////////////////////////////////////////////////////////////
|
@@ -21,6 +21,9 @@
|
|
21
21
|
#include "ruby/io.h"
|
22
22
|
|
23
23
|
VALUE SYM_io_uring;
|
24
|
+
VALUE SYM_send;
|
25
|
+
VALUE SYM_splice;
|
26
|
+
VALUE SYM_write;
|
24
27
|
|
25
28
|
#ifdef POLYPHONY_UNSET_NONBLOCK
|
26
29
|
ID ID_ivar_is_nonblocking;
|
@@ -51,7 +54,6 @@ typedef struct Backend_t {
|
|
51
54
|
// common fields
|
52
55
|
unsigned int currently_polling;
|
53
56
|
unsigned int pending_count;
|
54
|
-
unsigned int poll_no_wait_count;
|
55
57
|
|
56
58
|
// implementation-specific fields
|
57
59
|
struct io_uring ring;
|
@@ -88,7 +90,6 @@ static VALUE Backend_initialize(VALUE self) {
|
|
88
90
|
|
89
91
|
backend->currently_polling = 0;
|
90
92
|
backend->pending_count = 0;
|
91
|
-
backend->poll_no_wait_count = 0;
|
92
93
|
backend->pending_sqes = 0;
|
93
94
|
backend->prepared_limit = 2048;
|
94
95
|
|
@@ -118,7 +119,6 @@ VALUE Backend_post_fork(VALUE self) {
|
|
118
119
|
context_store_free(&backend->store);
|
119
120
|
backend->currently_polling = 0;
|
120
121
|
backend->pending_count = 0;
|
121
|
-
backend->poll_no_wait_count = 0;
|
122
122
|
backend->pending_sqes = 0;
|
123
123
|
|
124
124
|
return self;
|
@@ -155,17 +155,9 @@ static inline void io_uring_backend_handle_completion(struct io_uring_cqe *cqe,
|
|
155
155
|
if (!ctx) return;
|
156
156
|
|
157
157
|
ctx->result = cqe->res;
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
// completion, so we can release the context
|
162
|
-
context_store_release(&backend->store, ctx);
|
163
|
-
else {
|
164
|
-
// otherwise, we mark it as completed, schedule the fiber and let it deal
|
165
|
-
// with releasing the context
|
166
|
-
ctx->completed = 1;
|
167
|
-
if (ctx->result != -ECANCELED) Fiber_make_runnable(ctx->fiber, ctx->resume_value);
|
168
|
-
}
|
158
|
+
if (ctx->ref_count == 2 && ctx->result != -ECANCELED && ctx->fiber)
|
159
|
+
Fiber_make_runnable(ctx->fiber, ctx->resume_value);
|
160
|
+
context_store_release(&backend->store, ctx);
|
169
161
|
}
|
170
162
|
|
171
163
|
// adapted from io_uring_peek_batch_cqe in queue.c
|
@@ -219,16 +211,6 @@ VALUE Backend_poll(VALUE self, VALUE nowait, VALUE current_fiber, VALUE runqueue
|
|
219
211
|
Backend_t *backend;
|
220
212
|
GetBackend(self, backend);
|
221
213
|
|
222
|
-
if (is_nowait) {
|
223
|
-
backend->poll_no_wait_count++;
|
224
|
-
if (backend->poll_no_wait_count < 10) return self;
|
225
|
-
|
226
|
-
long runnable_count = Runqueue_len(runqueue);
|
227
|
-
if (backend->poll_no_wait_count < runnable_count) return self;
|
228
|
-
}
|
229
|
-
|
230
|
-
backend->poll_no_wait_count = 0;
|
231
|
-
|
232
214
|
if (is_nowait && backend->pending_sqes) {
|
233
215
|
backend->pending_sqes = 0;
|
234
216
|
io_uring_submit(&backend->ring);
|
@@ -283,10 +265,9 @@ int io_uring_backend_defer_submit_and_await(
|
|
283
265
|
|
284
266
|
switchpoint_result = backend_await(backend);
|
285
267
|
|
286
|
-
if (
|
268
|
+
if (ctx->ref_count > 1) {
|
269
|
+
// op was not completed (an exception was raised), so we need to cancel it
|
287
270
|
ctx->result = -ECANCELED;
|
288
|
-
|
289
|
-
// op was not completed, so we need to cancel it
|
290
271
|
struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
|
291
272
|
io_uring_prep_cancel(sqe, ctx, 0);
|
292
273
|
backend->pending_sqes = 0;
|
@@ -300,7 +281,7 @@ int io_uring_backend_defer_submit_and_await(
|
|
300
281
|
}
|
301
282
|
|
302
283
|
VALUE io_uring_backend_wait_fd(Backend_t *backend, int fd, int write) {
|
303
|
-
op_context_t *ctx =
|
284
|
+
op_context_t *ctx = context_store_acquire(&backend->store, OP_POLL);
|
304
285
|
VALUE resumed_value = Qnil;
|
305
286
|
|
306
287
|
struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
|
@@ -332,14 +313,14 @@ VALUE Backend_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof)
|
|
332
313
|
|
333
314
|
while (1) {
|
334
315
|
VALUE resume_value = Qnil;
|
335
|
-
op_context_t *ctx =
|
316
|
+
op_context_t *ctx = context_store_acquire(&backend->store, OP_READ);
|
336
317
|
struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
|
337
318
|
io_uring_prep_read(sqe, fptr->fd, buf, buffer_size - total, -1);
|
338
319
|
|
339
320
|
int result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
|
340
|
-
|
321
|
+
int completed = context_store_release(&backend->store, ctx);
|
341
322
|
RAISE_IF_EXCEPTION(resume_value);
|
342
|
-
if (!
|
323
|
+
if (!completed) return resume_value;
|
343
324
|
RB_GC_GUARD(resume_value);
|
344
325
|
|
345
326
|
if (result < 0)
|
@@ -393,14 +374,14 @@ VALUE Backend_read_loop(VALUE self, VALUE io) {
|
|
393
374
|
|
394
375
|
while (1) {
|
395
376
|
VALUE resume_value = Qnil;
|
396
|
-
op_context_t *ctx =
|
377
|
+
op_context_t *ctx = context_store_acquire(&backend->store, OP_READ);
|
397
378
|
struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
|
398
379
|
io_uring_prep_read(sqe, fptr->fd, buf, len, -1);
|
399
380
|
|
400
381
|
ssize_t result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
|
401
|
-
|
382
|
+
int completed = context_store_release(&backend->store, ctx);
|
402
383
|
RAISE_IF_EXCEPTION(resume_value);
|
403
|
-
if (!
|
384
|
+
if (!completed) return resume_value;
|
404
385
|
RB_GC_GUARD(resume_value);
|
405
386
|
|
406
387
|
if (result < 0)
|
@@ -440,14 +421,14 @@ VALUE Backend_feed_loop(VALUE self, VALUE io, VALUE receiver, VALUE method) {
|
|
440
421
|
|
441
422
|
while (1) {
|
442
423
|
VALUE resume_value = Qnil;
|
443
|
-
op_context_t *ctx =
|
424
|
+
op_context_t *ctx = context_store_acquire(&backend->store, OP_READ);
|
444
425
|
struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
|
445
426
|
io_uring_prep_read(sqe, fptr->fd, buf, len, -1);
|
446
427
|
|
447
428
|
ssize_t result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
|
448
|
-
|
429
|
+
int completed = context_store_release(&backend->store, ctx);
|
449
430
|
RAISE_IF_EXCEPTION(resume_value);
|
450
|
-
if (!
|
431
|
+
if (!completed) return resume_value;
|
451
432
|
RB_GC_GUARD(resume_value);
|
452
433
|
|
453
434
|
if (result < 0)
|
@@ -483,14 +464,14 @@ VALUE Backend_write(VALUE self, VALUE io, VALUE str) {
|
|
483
464
|
|
484
465
|
while (left > 0) {
|
485
466
|
VALUE resume_value = Qnil;
|
486
|
-
op_context_t *ctx =
|
467
|
+
op_context_t *ctx = context_store_acquire(&backend->store, OP_WRITE);
|
487
468
|
struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
|
488
469
|
io_uring_prep_write(sqe, fptr->fd, buf, left, -1);
|
489
470
|
|
490
471
|
int result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
|
491
|
-
|
472
|
+
int completed = context_store_release(&backend->store, ctx);
|
492
473
|
RAISE_IF_EXCEPTION(resume_value);
|
493
|
-
if (!
|
474
|
+
if (!completed) return resume_value;
|
494
475
|
RB_GC_GUARD(resume_value);
|
495
476
|
|
496
477
|
if (result < 0)
|
@@ -532,17 +513,17 @@ VALUE Backend_writev(VALUE self, VALUE io, int argc, VALUE *argv) {
|
|
532
513
|
|
533
514
|
while (1) {
|
534
515
|
VALUE resume_value = Qnil;
|
535
|
-
op_context_t *ctx =
|
516
|
+
op_context_t *ctx = context_store_acquire(&backend->store, OP_WRITEV);
|
536
517
|
struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
|
537
518
|
io_uring_prep_writev(sqe, fptr->fd, iov_ptr, iov_count, -1);
|
538
519
|
|
539
520
|
int result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
|
540
|
-
|
521
|
+
int completed = context_store_release(&backend->store, ctx);
|
541
522
|
if (TEST_EXCEPTION(resume_value)) {
|
542
523
|
free(iov);
|
543
524
|
RAISE_EXCEPTION(resume_value);
|
544
525
|
}
|
545
|
-
if (!
|
526
|
+
if (!completed) {
|
546
527
|
free(iov);
|
547
528
|
return resume_value;
|
548
529
|
}
|
@@ -605,14 +586,14 @@ VALUE Backend_recv(VALUE self, VALUE io, VALUE str, VALUE length) {
|
|
605
586
|
|
606
587
|
while (1) {
|
607
588
|
VALUE resume_value = Qnil;
|
608
|
-
op_context_t *ctx =
|
589
|
+
op_context_t *ctx = context_store_acquire(&backend->store, OP_RECV);
|
609
590
|
struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
|
610
591
|
io_uring_prep_recv(sqe, fptr->fd, buf, len - total, 0);
|
611
592
|
|
612
593
|
int result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
|
613
|
-
|
594
|
+
int completed = context_store_release(&backend->store, ctx);
|
614
595
|
RAISE_IF_EXCEPTION(resume_value);
|
615
|
-
if (!
|
596
|
+
if (!completed) return resume_value;
|
616
597
|
RB_GC_GUARD(resume_value);
|
617
598
|
|
618
599
|
if (result < 0)
|
@@ -652,14 +633,14 @@ VALUE Backend_recv_loop(VALUE self, VALUE io) {
|
|
652
633
|
|
653
634
|
while (1) {
|
654
635
|
VALUE resume_value = Qnil;
|
655
|
-
op_context_t *ctx =
|
636
|
+
op_context_t *ctx = context_store_acquire(&backend->store, OP_RECV);
|
656
637
|
struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
|
657
638
|
io_uring_prep_recv(sqe, fptr->fd, buf, len, 0);
|
658
639
|
|
659
640
|
int result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
|
660
|
-
|
641
|
+
int completed = context_store_release(&backend->store, ctx);
|
661
642
|
RAISE_IF_EXCEPTION(resume_value);
|
662
|
-
if (!
|
643
|
+
if (!completed) return resume_value;
|
663
644
|
RB_GC_GUARD(resume_value);
|
664
645
|
|
665
646
|
if (result < 0)
|
@@ -698,14 +679,14 @@ VALUE Backend_recv_feed_loop(VALUE self, VALUE io, VALUE receiver, VALUE method)
|
|
698
679
|
|
699
680
|
while (1) {
|
700
681
|
VALUE resume_value = Qnil;
|
701
|
-
op_context_t *ctx =
|
682
|
+
op_context_t *ctx = context_store_acquire(&backend->store, OP_RECV);
|
702
683
|
struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
|
703
684
|
io_uring_prep_recv(sqe, fptr->fd, buf, len, 0);
|
704
685
|
|
705
686
|
int result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
|
706
|
-
|
687
|
+
int completed = context_store_release(&backend->store, ctx);
|
707
688
|
RAISE_IF_EXCEPTION(resume_value);
|
708
|
-
if (!
|
689
|
+
if (!completed) return resume_value;
|
709
690
|
RB_GC_GUARD(resume_value);
|
710
691
|
|
711
692
|
if (result < 0)
|
@@ -741,14 +722,14 @@ VALUE Backend_send(VALUE self, VALUE io, VALUE str, VALUE flags) {
|
|
741
722
|
|
742
723
|
while (left > 0) {
|
743
724
|
VALUE resume_value = Qnil;
|
744
|
-
op_context_t *ctx =
|
725
|
+
op_context_t *ctx = context_store_acquire(&backend->store, OP_SEND);
|
745
726
|
struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
|
746
727
|
io_uring_prep_send(sqe, fptr->fd, buf, left, flags_int);
|
747
728
|
|
748
729
|
int result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
|
749
|
-
|
730
|
+
int completed = context_store_release(&backend->store, ctx);
|
750
731
|
RAISE_IF_EXCEPTION(resume_value);
|
751
|
-
if (!
|
732
|
+
if (!completed) return resume_value;
|
752
733
|
RB_GC_GUARD(resume_value);
|
753
734
|
|
754
735
|
if (result < 0)
|
@@ -775,14 +756,14 @@ VALUE io_uring_backend_accept(Backend_t *backend, VALUE server_socket, VALUE soc
|
|
775
756
|
|
776
757
|
while (1) {
|
777
758
|
VALUE resume_value = Qnil;
|
778
|
-
op_context_t *ctx =
|
759
|
+
op_context_t *ctx = context_store_acquire(&backend->store, OP_ACCEPT);
|
779
760
|
struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
|
780
761
|
io_uring_prep_accept(sqe, fptr->fd, &addr, &len, 0);
|
781
762
|
|
782
763
|
int fd = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
|
783
|
-
|
764
|
+
int completed = context_store_release(&backend->store, ctx);
|
784
765
|
RAISE_IF_EXCEPTION(resume_value);
|
785
|
-
if (!
|
766
|
+
if (!completed) return resume_value;
|
786
767
|
RB_GC_GUARD(resume_value);
|
787
768
|
|
788
769
|
if (fd < 0)
|
@@ -846,14 +827,14 @@ VALUE io_uring_backend_splice(Backend_t *backend, VALUE src, VALUE dest, VALUE m
|
|
846
827
|
VALUE resume_value = Qnil;
|
847
828
|
|
848
829
|
while (1) {
|
849
|
-
op_context_t *ctx =
|
830
|
+
op_context_t *ctx = context_store_acquire(&backend->store, OP_SPLICE);
|
850
831
|
struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
|
851
832
|
io_uring_prep_splice(sqe, src_fptr->fd, -1, dest_fptr->fd, -1, NUM2INT(maxlen), 0);
|
852
833
|
|
853
834
|
int result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
|
854
|
-
|
835
|
+
int completed = context_store_release(&backend->store, ctx);
|
855
836
|
RAISE_IF_EXCEPTION(resume_value);
|
856
|
-
if (!
|
837
|
+
if (!completed) return resume_value;
|
857
838
|
|
858
839
|
if (result < 0)
|
859
840
|
rb_syserr_fail(-result, strerror(-result));
|
@@ -897,13 +878,13 @@ VALUE Backend_connect(VALUE self, VALUE sock, VALUE host, VALUE port) {
|
|
897
878
|
addr.sin_port = htons(NUM2INT(port));
|
898
879
|
|
899
880
|
VALUE resume_value = Qnil;
|
900
|
-
op_context_t *ctx =
|
881
|
+
op_context_t *ctx = context_store_acquire(&backend->store, OP_CONNECT);
|
901
882
|
struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
|
902
883
|
io_uring_prep_connect(sqe, fptr->fd, (struct sockaddr *)&addr, sizeof(addr));
|
903
884
|
int result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
|
904
|
-
|
885
|
+
int completed = context_store_release(&backend->store, ctx);
|
905
886
|
RAISE_IF_EXCEPTION(resume_value);
|
906
|
-
if (!
|
887
|
+
if (!completed) return resume_value;
|
907
888
|
RB_GC_GUARD(resume_value);
|
908
889
|
|
909
890
|
if (result < 0) rb_syserr_fail(-result, strerror(-result));
|
@@ -943,12 +924,11 @@ int io_uring_backend_submit_timeout_and_await(Backend_t *backend, double duratio
|
|
943
924
|
struct __kernel_timespec ts = double_to_timespec(duration);
|
944
925
|
struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
|
945
926
|
|
946
|
-
op_context_t *ctx =
|
927
|
+
op_context_t *ctx = context_store_acquire(&backend->store, OP_TIMEOUT);
|
947
928
|
io_uring_prep_timeout(sqe, &ts, 0, 0);
|
948
929
|
|
949
930
|
io_uring_backend_defer_submit_and_await(backend, sqe, ctx, resume_value);
|
950
|
-
|
951
|
-
return ctx->completed;
|
931
|
+
return context_store_release(&backend->store, ctx);
|
952
932
|
}
|
953
933
|
|
954
934
|
VALUE Backend_sleep(VALUE self, VALUE duration) {
|
@@ -996,7 +976,7 @@ struct Backend_timeout_ctx {
|
|
996
976
|
|
997
977
|
VALUE Backend_timeout_ensure(VALUE arg) {
|
998
978
|
struct Backend_timeout_ctx *timeout_ctx = (struct Backend_timeout_ctx *)arg;
|
999
|
-
if (
|
979
|
+
if (timeout_ctx->ctx->ref_count) {
|
1000
980
|
timeout_ctx->ctx->result = -ECANCELED;
|
1001
981
|
|
1002
982
|
// op was not completed, so we need to cancel it
|
@@ -1005,7 +985,7 @@ VALUE Backend_timeout_ensure(VALUE arg) {
|
|
1005
985
|
timeout_ctx->backend->pending_sqes = 0;
|
1006
986
|
io_uring_submit(&timeout_ctx->backend->ring);
|
1007
987
|
}
|
1008
|
-
|
988
|
+
context_store_release(&timeout_ctx->backend->store, timeout_ctx->ctx);
|
1009
989
|
return Qnil;
|
1010
990
|
}
|
1011
991
|
|
@@ -1023,7 +1003,7 @@ VALUE Backend_timeout(int argc, VALUE *argv, VALUE self) {
|
|
1023
1003
|
|
1024
1004
|
struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
|
1025
1005
|
|
1026
|
-
op_context_t *ctx =
|
1006
|
+
op_context_t *ctx = context_store_acquire(&backend->store, OP_TIMEOUT);
|
1027
1007
|
ctx->resume_value = timeout;
|
1028
1008
|
io_uring_prep_timeout(sqe, &ts, 0, 0);
|
1029
1009
|
io_uring_sqe_set_data(sqe, ctx);
|
@@ -1091,6 +1071,130 @@ VALUE Backend_kind(VALUE self) {
|
|
1091
1071
|
return SYM_io_uring;
|
1092
1072
|
}
|
1093
1073
|
|
1074
|
+
struct io_uring_sqe *Backend_chain_prepare_write(Backend_t *backend, VALUE io, VALUE str) {
|
1075
|
+
rb_io_t *fptr;
|
1076
|
+
VALUE underlying_io;
|
1077
|
+
|
1078
|
+
underlying_io = rb_ivar_get(io, ID_ivar_io);
|
1079
|
+
if (underlying_io != Qnil) io = underlying_io;
|
1080
|
+
io = rb_io_get_write_io(io);
|
1081
|
+
GetOpenFile(io, fptr);
|
1082
|
+
io_unset_nonblock(fptr, io);
|
1083
|
+
|
1084
|
+
char *buf = StringValuePtr(str);
|
1085
|
+
long len = RSTRING_LEN(str);
|
1086
|
+
|
1087
|
+
struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
|
1088
|
+
io_uring_prep_write(sqe, fptr->fd, buf, len, -1);
|
1089
|
+
return sqe;
|
1090
|
+
}
|
1091
|
+
|
1092
|
+
struct io_uring_sqe *Backend_chain_prepare_send(Backend_t *backend, VALUE io, VALUE str, VALUE flags) {
|
1093
|
+
rb_io_t *fptr;
|
1094
|
+
VALUE underlying_io;
|
1095
|
+
|
1096
|
+
underlying_io = rb_ivar_get(io, ID_ivar_io);
|
1097
|
+
if (underlying_io != Qnil) io = underlying_io;
|
1098
|
+
io = rb_io_get_write_io(io);
|
1099
|
+
GetOpenFile(io, fptr);
|
1100
|
+
io_unset_nonblock(fptr, io);
|
1101
|
+
|
1102
|
+
char *buf = StringValuePtr(str);
|
1103
|
+
long len = RSTRING_LEN(str);
|
1104
|
+
int flags_int = NUM2INT(flags);
|
1105
|
+
|
1106
|
+
struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
|
1107
|
+
io_uring_prep_send(sqe, fptr->fd, buf, len, flags_int);
|
1108
|
+
return sqe;
|
1109
|
+
}
|
1110
|
+
|
1111
|
+
struct io_uring_sqe *Backend_chain_prepare_splice(Backend_t *backend, VALUE src, VALUE dest, VALUE maxlen) {
|
1112
|
+
rb_io_t *src_fptr;
|
1113
|
+
rb_io_t *dest_fptr;
|
1114
|
+
VALUE underlying_io;
|
1115
|
+
|
1116
|
+
underlying_io = rb_ivar_get(src, ID_ivar_io);
|
1117
|
+
if (underlying_io != Qnil) src = underlying_io;
|
1118
|
+
GetOpenFile(src, src_fptr);
|
1119
|
+
io_unset_nonblock(src_fptr, src);
|
1120
|
+
|
1121
|
+
underlying_io = rb_ivar_get(dest, ID_ivar_io);
|
1122
|
+
if (underlying_io != Qnil) dest = underlying_io;
|
1123
|
+
dest = rb_io_get_write_io(dest);
|
1124
|
+
GetOpenFile(dest, dest_fptr);
|
1125
|
+
io_unset_nonblock(dest_fptr, dest);
|
1126
|
+
|
1127
|
+
struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
|
1128
|
+
io_uring_prep_splice(sqe, src_fptr->fd, -1, dest_fptr->fd, -1, NUM2INT(maxlen), 0);
|
1129
|
+
return sqe;
|
1130
|
+
}
|
1131
|
+
|
1132
|
+
VALUE Backend_chain(int argc,VALUE *argv, VALUE self) {
|
1133
|
+
VALUE resume_value = Qnil;
|
1134
|
+
unsigned int sqe_count = 0;
|
1135
|
+
struct io_uring_sqe *last_sqe = 0;
|
1136
|
+
Backend_t *backend;
|
1137
|
+
GetBackend(self, backend);
|
1138
|
+
if (argc == 0) return resume_value;
|
1139
|
+
|
1140
|
+
op_context_t *ctx = context_store_acquire(&backend->store, OP_CHAIN);
|
1141
|
+
for (int i = 0; i < argc; i++) {
|
1142
|
+
VALUE op = argv[i];
|
1143
|
+
VALUE op_type = RARRAY_AREF(op, 0);
|
1144
|
+
VALUE op_len = RARRAY_LEN(op);
|
1145
|
+
|
1146
|
+
if (op_type == SYM_write && op_len == 3) {
|
1147
|
+
last_sqe = Backend_chain_prepare_write(backend, RARRAY_AREF(op, 1), RARRAY_AREF(op, 2));
|
1148
|
+
}
|
1149
|
+
else if (op_type == SYM_send && op_len == 4)
|
1150
|
+
last_sqe = Backend_chain_prepare_send(backend, RARRAY_AREF(op, 1), RARRAY_AREF(op, 2), RARRAY_AREF(op, 3));
|
1151
|
+
else if (op_type == SYM_splice && op_len == 4)
|
1152
|
+
last_sqe = Backend_chain_prepare_splice(backend, RARRAY_AREF(op, 1), RARRAY_AREF(op, 2), RARRAY_AREF(op, 3));
|
1153
|
+
else {
|
1154
|
+
if (sqe_count) {
|
1155
|
+
io_uring_sqe_set_data(last_sqe, ctx);
|
1156
|
+
io_uring_sqe_set_flags(last_sqe, IOSQE_ASYNC);
|
1157
|
+
|
1158
|
+
ctx->ref_count = sqe_count;
|
1159
|
+
ctx->result = -ECANCELED;
|
1160
|
+
struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
|
1161
|
+
io_uring_prep_cancel(sqe, ctx, 0);
|
1162
|
+
backend->pending_sqes = 0;
|
1163
|
+
io_uring_submit(&backend->ring);
|
1164
|
+
}
|
1165
|
+
else {
|
1166
|
+
ctx->ref_count = 1;
|
1167
|
+
context_store_release(&backend->store, ctx);
|
1168
|
+
}
|
1169
|
+
rb_raise(rb_eRuntimeError, "Invalid op specified or bad op arity");
|
1170
|
+
}
|
1171
|
+
|
1172
|
+
io_uring_sqe_set_data(last_sqe, ctx);
|
1173
|
+
unsigned int flags = (i == argc - 1) ? IOSQE_ASYNC : IOSQE_ASYNC & IOSQE_IO_LINK;
|
1174
|
+
io_uring_sqe_set_flags(last_sqe, flags);
|
1175
|
+
sqe_count++;
|
1176
|
+
}
|
1177
|
+
|
1178
|
+
ctx->ref_count = sqe_count + 1;
|
1179
|
+
io_uring_backend_defer_submit(backend);
|
1180
|
+
resume_value = backend_await(backend);
|
1181
|
+
int result = ctx->result;
|
1182
|
+
int completed = context_store_release(&backend->store, ctx);
|
1183
|
+
if (!completed) {
|
1184
|
+
// op was not completed (an exception was raised), so we need to cancel it
|
1185
|
+
ctx->result = -ECANCELED;
|
1186
|
+
struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
|
1187
|
+
io_uring_prep_cancel(sqe, ctx, 0);
|
1188
|
+
backend->pending_sqes = 0;
|
1189
|
+
io_uring_submit(&backend->ring);
|
1190
|
+
RAISE_IF_EXCEPTION(resume_value);
|
1191
|
+
return resume_value;
|
1192
|
+
}
|
1193
|
+
|
1194
|
+
RB_GC_GUARD(resume_value);
|
1195
|
+
return INT2NUM(result);
|
1196
|
+
}
|
1197
|
+
|
1094
1198
|
void Init_Backend() {
|
1095
1199
|
VALUE cBackend = rb_define_class_under(mPolyphony, "Backend", rb_cObject);
|
1096
1200
|
rb_define_alloc_func(cBackend, Backend_allocate);
|
@@ -1102,6 +1206,7 @@ void Init_Backend() {
|
|
1102
1206
|
rb_define_method(cBackend, "poll", Backend_poll, 3);
|
1103
1207
|
rb_define_method(cBackend, "break", Backend_wakeup, 0);
|
1104
1208
|
rb_define_method(cBackend, "kind", Backend_kind, 0);
|
1209
|
+
rb_define_method(cBackend, "chain", Backend_chain, -1);
|
1105
1210
|
|
1106
1211
|
rb_define_method(cBackend, "accept", Backend_accept, 2);
|
1107
1212
|
rb_define_method(cBackend, "accept_loop", Backend_accept_loop, 2);
|
@@ -1130,6 +1235,9 @@ void Init_Backend() {
|
|
1130
1235
|
#endif
|
1131
1236
|
|
1132
1237
|
SYM_io_uring = ID2SYM(rb_intern("io_uring"));
|
1238
|
+
SYM_send = ID2SYM(rb_intern("send"));
|
1239
|
+
SYM_splice = ID2SYM(rb_intern("splice"));
|
1240
|
+
SYM_write = ID2SYM(rb_intern("write"));
|
1133
1241
|
}
|
1134
1242
|
|
1135
1243
|
#endif // POLYPHONY_BACKEND_LIBURING
|
@@ -1,4 +1,5 @@
|
|
1
1
|
#include <stdlib.h>
|
2
|
+
#include <assert.h>
|
2
3
|
#include "ruby.h"
|
3
4
|
#include "polyphony.h"
|
4
5
|
#include "backend_io_uring_context.h"
|
@@ -15,6 +16,7 @@ const char *op_type_to_str(enum op_type type) {
|
|
15
16
|
case OP_POLL: return "POLL";
|
16
17
|
case OP_ACCEPT: return "ACCEPT";
|
17
18
|
case OP_CONNECT: return "CONNECT";
|
19
|
+
case OP_CHAIN: return "CHAIN";
|
18
20
|
default: return "";
|
19
21
|
};
|
20
22
|
}
|
@@ -35,6 +37,7 @@ inline op_context_t *context_store_acquire(op_context_store_t *store, enum op_ty
|
|
35
37
|
ctx = malloc(sizeof(op_context_t));
|
36
38
|
}
|
37
39
|
ctx->id = (++store->last_id);
|
40
|
+
// printf("acquire %d (%s)\n", ctx->id, op_type_to_str(type));
|
38
41
|
|
39
42
|
ctx->prev = NULL;
|
40
43
|
ctx->next = store->taken;
|
@@ -44,13 +47,21 @@ inline op_context_t *context_store_acquire(op_context_store_t *store, enum op_ty
|
|
44
47
|
ctx->type = type;
|
45
48
|
ctx->fiber = rb_fiber_current();
|
46
49
|
ctx->resume_value = Qnil;
|
47
|
-
ctx->
|
50
|
+
ctx->ref_count = 2;
|
48
51
|
ctx->result = 0;
|
49
52
|
|
50
53
|
return ctx;
|
51
54
|
}
|
52
55
|
|
53
|
-
|
56
|
+
// returns true if ctx was released
|
57
|
+
inline int context_store_release(op_context_store_t *store, op_context_t *ctx) {
|
58
|
+
// printf("release %d (%s, ref_count: %d)\n", ctx->id, op_type_to_str(ctx->type), ctx->ref_count);
|
59
|
+
|
60
|
+
assert(ctx->ref_count);
|
61
|
+
|
62
|
+
ctx->ref_count--;
|
63
|
+
if (ctx->ref_count) return 0;
|
64
|
+
|
54
65
|
if (ctx->next) ctx->next->prev = ctx->prev;
|
55
66
|
if (ctx->prev) ctx->prev->next = ctx->next;
|
56
67
|
if (store->taken == ctx) store->taken = ctx->next;
|
@@ -59,6 +70,7 @@ inline void context_store_release(op_context_store_t *store, op_context_t *ctx)
|
|
59
70
|
ctx->next = store->available;
|
60
71
|
if (ctx->next) ctx->next->prev = ctx;
|
61
72
|
store->available = ctx;
|
73
|
+
return 1;
|
62
74
|
}
|
63
75
|
|
64
76
|
void context_store_free(op_context_store_t *store) {
|
@@ -14,14 +14,15 @@ enum op_type {
|
|
14
14
|
OP_TIMEOUT,
|
15
15
|
OP_POLL,
|
16
16
|
OP_ACCEPT,
|
17
|
-
OP_CONNECT
|
17
|
+
OP_CONNECT,
|
18
|
+
OP_CHAIN
|
18
19
|
};
|
19
20
|
|
20
21
|
typedef struct op_context {
|
21
22
|
struct op_context *prev;
|
22
23
|
struct op_context *next;
|
23
24
|
enum op_type type: 16;
|
24
|
-
int
|
25
|
+
unsigned int ref_count : 16;
|
25
26
|
int id;
|
26
27
|
int result;
|
27
28
|
VALUE fiber;
|
@@ -38,17 +39,16 @@ const char *op_type_to_str(enum op_type type);
|
|
38
39
|
|
39
40
|
void context_store_initialize(op_context_store_t *store);
|
40
41
|
op_context_t *context_store_acquire(op_context_store_t *store, enum op_type type);
|
41
|
-
|
42
|
+
int context_store_release(op_context_store_t *store, op_context_t *ctx);
|
42
43
|
void context_store_free(op_context_store_t *store);
|
43
44
|
|
44
|
-
|
45
|
-
|
46
|
-
if (ctx->
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
} \
|
45
|
+
inline unsigned int OP_CONTEXT_RELEASE(op_context_store_t *store, op_context_t *ctx) {
|
46
|
+
int completed = !ctx->ref_count;
|
47
|
+
if (ctx->ref_count)
|
48
|
+
ctx->ref_count -= 1;
|
49
|
+
else
|
50
|
+
context_store_release(store, ctx);
|
51
|
+
return completed;
|
52
52
|
}
|
53
53
|
|
54
54
|
#endif /* BACKEND_IO_URING_CONTEXT_H */
|
@@ -90,7 +90,6 @@ typedef struct Backend_t {
|
|
90
90
|
// common fields
|
91
91
|
unsigned int currently_polling;
|
92
92
|
unsigned int pending_count;
|
93
|
-
unsigned int poll_no_wait_count;
|
94
93
|
|
95
94
|
// implementation-specific fields
|
96
95
|
struct ev_loop *ev_loop;
|
@@ -145,7 +144,6 @@ static VALUE Backend_initialize(VALUE self) {
|
|
145
144
|
|
146
145
|
backend->currently_polling = 0;
|
147
146
|
backend->pending_count = 0;
|
148
|
-
backend->poll_no_wait_count = 0;
|
149
147
|
|
150
148
|
return Qnil;
|
151
149
|
}
|
@@ -184,23 +182,12 @@ unsigned int Backend_pending_count(VALUE self) {
|
|
184
182
|
}
|
185
183
|
|
186
184
|
VALUE Backend_poll(VALUE self, VALUE nowait, VALUE current_fiber, VALUE runqueue) {
|
187
|
-
int is_nowait = nowait == Qtrue;
|
188
185
|
Backend_t *backend;
|
189
186
|
GetBackend(self, backend);
|
190
187
|
|
191
|
-
if (is_nowait) {
|
192
|
-
backend->poll_no_wait_count++;
|
193
|
-
if (backend->poll_no_wait_count < 10) return self;
|
194
|
-
|
195
|
-
long runnable_count = Runqueue_len(runqueue);
|
196
|
-
if (backend->poll_no_wait_count < runnable_count) return self;
|
197
|
-
}
|
198
|
-
|
199
|
-
backend->poll_no_wait_count = 0;
|
200
|
-
|
201
188
|
COND_TRACE(2, SYM_fiber_event_poll_enter, current_fiber);
|
202
189
|
backend->currently_polling = 1;
|
203
|
-
ev_run(backend->ev_loop,
|
190
|
+
ev_run(backend->ev_loop, nowait == Qtrue ? EVRUN_NOWAIT : EVRUN_ONCE);
|
204
191
|
backend->currently_polling = 0;
|
205
192
|
COND_TRACE(2, SYM_fiber_event_poll_leave, current_fiber);
|
206
193
|
|
data/ext/polyphony/polyphony.h
CHANGED
@@ -90,6 +90,7 @@ int Runqueue_index_of(VALUE self, VALUE fiber);
|
|
90
90
|
void Runqueue_clear(VALUE self);
|
91
91
|
long Runqueue_len(VALUE self);
|
92
92
|
int Runqueue_empty_p(VALUE self);
|
93
|
+
int Runqueue_should_poll_nonblocking(VALUE self);
|
93
94
|
|
94
95
|
#ifdef POLYPHONY_BACKEND_LIBEV
|
95
96
|
#define Backend_recv_loop Backend_read_loop
|
data/ext/polyphony/runqueue.c
CHANGED
@@ -3,6 +3,8 @@
|
|
3
3
|
|
4
4
|
typedef struct queue {
|
5
5
|
runqueue_ring_buffer entries;
|
6
|
+
unsigned int high_watermark;
|
7
|
+
unsigned int switch_count;
|
6
8
|
} Runqueue_t;
|
7
9
|
|
8
10
|
VALUE cRunqueue = Qnil;
|
@@ -43,6 +45,8 @@ static VALUE Runqueue_initialize(VALUE self) {
|
|
43
45
|
GetRunqueue(self, runqueue);
|
44
46
|
|
45
47
|
runqueue_ring_buffer_init(&runqueue->entries);
|
48
|
+
runqueue->high_watermark = 0;
|
49
|
+
runqueue->switch_count = 0;
|
46
50
|
|
47
51
|
return self;
|
48
52
|
}
|
@@ -53,6 +57,8 @@ void Runqueue_push(VALUE self, VALUE fiber, VALUE value, int reschedule) {
|
|
53
57
|
|
54
58
|
if (reschedule) runqueue_ring_buffer_delete(&runqueue->entries, fiber);
|
55
59
|
runqueue_ring_buffer_push(&runqueue->entries, fiber, value);
|
60
|
+
if (runqueue->entries.count > runqueue->high_watermark)
|
61
|
+
runqueue->high_watermark = runqueue->entries.count;
|
56
62
|
}
|
57
63
|
|
58
64
|
void Runqueue_unshift(VALUE self, VALUE fiber, VALUE value, int reschedule) {
|
@@ -60,12 +66,19 @@ void Runqueue_unshift(VALUE self, VALUE fiber, VALUE value, int reschedule) {
|
|
60
66
|
GetRunqueue(self, runqueue);
|
61
67
|
if (reschedule) runqueue_ring_buffer_delete(&runqueue->entries, fiber);
|
62
68
|
runqueue_ring_buffer_unshift(&runqueue->entries, fiber, value);
|
69
|
+
if (runqueue->entries.count > runqueue->high_watermark)
|
70
|
+
runqueue->high_watermark = runqueue->entries.count;
|
63
71
|
}
|
64
72
|
|
65
73
|
runqueue_entry Runqueue_shift(VALUE self) {
|
66
74
|
Runqueue_t *runqueue;
|
67
75
|
GetRunqueue(self, runqueue);
|
68
|
-
|
76
|
+
runqueue_entry entry = runqueue_ring_buffer_shift(&runqueue->entries);
|
77
|
+
if (entry.fiber == Qnil)
|
78
|
+
runqueue->high_watermark = 0;
|
79
|
+
else
|
80
|
+
runqueue->switch_count += 1;
|
81
|
+
return entry;
|
69
82
|
}
|
70
83
|
|
71
84
|
void Runqueue_delete(VALUE self, VALUE fiber) {
|
@@ -100,6 +113,21 @@ int Runqueue_empty_p(VALUE self) {
|
|
100
113
|
return (runqueue->entries.count == 0);
|
101
114
|
}
|
102
115
|
|
116
|
+
static const unsigned int ANTI_STARVE_HIGH_WATERMARK_THRESHOLD = 128;
|
117
|
+
static const unsigned int ANTI_STARVE_SWITCH_COUNT_THRESHOLD = 64;
|
118
|
+
|
119
|
+
int Runqueue_should_poll_nonblocking(VALUE self) {
|
120
|
+
Runqueue_t *runqueue;
|
121
|
+
GetRunqueue(self, runqueue);
|
122
|
+
|
123
|
+
if (runqueue->high_watermark < ANTI_STARVE_HIGH_WATERMARK_THRESHOLD) return 0;
|
124
|
+
if (runqueue->switch_count < ANTI_STARVE_SWITCH_COUNT_THRESHOLD) return 0;
|
125
|
+
|
126
|
+
// the
|
127
|
+
runqueue->switch_count = 0;
|
128
|
+
return 1;
|
129
|
+
}
|
130
|
+
|
103
131
|
void Init_Runqueue() {
|
104
132
|
cRunqueue = rb_define_class_under(mPolyphony, "Runqueue", rb_cObject);
|
105
133
|
rb_define_alloc_func(cRunqueue, Runqueue_allocate);
|
data/ext/polyphony/thread.c
CHANGED
@@ -86,7 +86,7 @@ VALUE Thread_switch_fiber(VALUE self) {
|
|
86
86
|
VALUE runqueue = rb_ivar_get(self, ID_ivar_runqueue);
|
87
87
|
runqueue_entry next;
|
88
88
|
VALUE backend = rb_ivar_get(self, ID_ivar_backend);
|
89
|
-
unsigned int
|
89
|
+
unsigned int pending_ops_count = Backend_pending_count(backend);
|
90
90
|
unsigned int backend_was_polled = 0;
|
91
91
|
|
92
92
|
if (__tracing_enabled__ && (rb_ivar_get(current_fiber, ID_ivar_running) != Qfalse))
|
@@ -95,14 +95,24 @@ VALUE Thread_switch_fiber(VALUE self) {
|
|
95
95
|
while (1) {
|
96
96
|
next = Runqueue_shift(runqueue);
|
97
97
|
if (next.fiber != Qnil) {
|
98
|
-
|
98
|
+
// Polling for I/O op completion is normally done when the run queue is
|
99
|
+
// empty, but if the runqueue never empties, we'll never get to process
|
100
|
+
// any event completions. In order to prevent this, an anti-starve
|
101
|
+
// mechanism is employed, under the following conditions:
|
102
|
+
// - a blocking poll was not yet performed
|
103
|
+
// - there are pending blocking operations
|
104
|
+
// - the runqueue has signalled that a non-blocking poll should be
|
105
|
+
// performed
|
106
|
+
// - the run queue length high watermark has reached its threshold (currently 128)
|
107
|
+
// - the run queue switch counter has reached its threshold (currently 64)
|
108
|
+
if (!backend_was_polled && pending_ops_count && Runqueue_should_poll_nonblocking(runqueue)) {
|
99
109
|
// this prevents event starvation in case the run queue never empties
|
100
110
|
Backend_poll(backend, Qtrue, current_fiber, runqueue);
|
101
111
|
}
|
102
112
|
break;
|
103
113
|
}
|
104
|
-
|
105
|
-
|
114
|
+
|
115
|
+
if (pending_ops_count == 0) break;
|
106
116
|
Backend_poll(backend, Qnil, current_fiber, runqueue);
|
107
117
|
backend_was_polled = 1;
|
108
118
|
}
|
@@ -36,9 +36,9 @@ class ::Socket
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def recvfrom(maxlen, flags = 0)
|
39
|
-
|
39
|
+
buf = +''
|
40
40
|
while true
|
41
|
-
result = recvfrom_nonblock(maxlen, flags,
|
41
|
+
result = recvfrom_nonblock(maxlen, flags, buf, **NO_EXCEPTION)
|
42
42
|
case result
|
43
43
|
when nil then raise IOError
|
44
44
|
when :wait_readable then Polyphony.backend_wait_io(self, false)
|
@@ -165,17 +165,10 @@ class ::TCPSocket
|
|
165
165
|
# Polyphony.backend_send(self, mesg, 0)
|
166
166
|
# end
|
167
167
|
|
168
|
-
def readpartial(maxlen, str =
|
169
|
-
|
170
|
-
result = Polyphony.backend_recv(self, @read_buffer, maxlen)
|
168
|
+
def readpartial(maxlen, str = +'')
|
169
|
+
result = Polyphony.backend_recv(self, str, maxlen)
|
171
170
|
raise EOFError unless result
|
172
171
|
|
173
|
-
if str
|
174
|
-
str << @read_buffer
|
175
|
-
else
|
176
|
-
str = @read_buffer
|
177
|
-
end
|
178
|
-
@read_buffer = +''
|
179
172
|
str
|
180
173
|
end
|
181
174
|
|
@@ -249,17 +242,10 @@ class ::UNIXSocket
|
|
249
242
|
Polyphony.backend_send(self, mesg, 0)
|
250
243
|
end
|
251
244
|
|
252
|
-
def readpartial(maxlen, str =
|
253
|
-
|
254
|
-
result = Polyphony.backend_recv(self, @read_buffer, maxlen)
|
245
|
+
def readpartial(maxlen, str = +'')
|
246
|
+
result = Polyphony.backend_recv(self, str, maxlen)
|
255
247
|
raise EOFError unless result
|
256
248
|
|
257
|
-
if str
|
258
|
-
str << @read_buffer
|
259
|
-
else
|
260
|
-
str = @read_buffer
|
261
|
-
end
|
262
|
-
@read_buffer = +''
|
263
249
|
str
|
264
250
|
end
|
265
251
|
|
data/lib/polyphony/version.rb
CHANGED
data/test/helper.rb
CHANGED
@@ -15,9 +15,9 @@ require 'minitest/reporters'
|
|
15
15
|
|
16
16
|
::Exception.__disable_sanitized_backtrace__ = true
|
17
17
|
|
18
|
-
Minitest::Reporters.use! [
|
19
|
-
|
20
|
-
]
|
18
|
+
# Minitest::Reporters.use! [
|
19
|
+
# Minitest::Reporters::SpecReporter.new
|
20
|
+
# ]
|
21
21
|
|
22
22
|
class ::Fiber
|
23
23
|
attr_writer :auto_watcher
|
data/test/test_backend.rb
CHANGED
@@ -26,7 +26,7 @@ class BackendTest < MiniTest::Test
|
|
26
26
|
@backend.sleep 0.01
|
27
27
|
count += 1
|
28
28
|
}.await
|
29
|
-
assert_in_delta 0.03, Time.now - t0, 0.
|
29
|
+
assert_in_delta 0.03, Time.now - t0, 0.01
|
30
30
|
assert_equal 3, count
|
31
31
|
end
|
32
32
|
|
@@ -309,6 +309,36 @@ class BackendChainTest < MiniTest::Test
|
|
309
309
|
assert_equal 'hello world', i.read
|
310
310
|
end
|
311
311
|
|
312
|
+
def test_simple_send_chain
|
313
|
+
port = rand(1234..5678)
|
314
|
+
server = TCPServer.new('127.0.0.1', port)
|
315
|
+
|
316
|
+
server_fiber = spin do
|
317
|
+
while (socket = server.accept)
|
318
|
+
spin do
|
319
|
+
while (data = socket.gets(8192))
|
320
|
+
socket << data
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
snooze
|
327
|
+
client = TCPSocket.new('127.0.0.1', port)
|
328
|
+
|
329
|
+
result = Thread.backend.chain(
|
330
|
+
[:send, client, 'hello', 0],
|
331
|
+
[:send, client, " world\n", 0]
|
332
|
+
)
|
333
|
+
sleep 0.1
|
334
|
+
assert_equal "hello world\n", client.recv(8192)
|
335
|
+
client.close
|
336
|
+
ensure
|
337
|
+
server_fiber&.stop
|
338
|
+
server_fiber&.await
|
339
|
+
server&.close
|
340
|
+
end
|
341
|
+
|
312
342
|
def chunk_header(len)
|
313
343
|
"Content-Length: #{len}\r\n\r\n"
|
314
344
|
end
|
@@ -346,7 +376,16 @@ class BackendChainTest < MiniTest::Test
|
|
346
376
|
|
347
377
|
assert_raises(RuntimeError) {
|
348
378
|
Thread.backend.chain(
|
349
|
-
[:read,
|
379
|
+
[:read, i]
|
380
|
+
)
|
381
|
+
}
|
382
|
+
|
383
|
+
assert_raises(RuntimeError) {
|
384
|
+
Thread.backend.chain(
|
385
|
+
[:write, o, 'abc'],
|
386
|
+
[:write, o, 'abc'],
|
387
|
+
[:write, o, 'abc'],
|
388
|
+
[:read, i]
|
350
389
|
)
|
351
390
|
}
|
352
391
|
|
@@ -355,5 +394,10 @@ class BackendChainTest < MiniTest::Test
|
|
355
394
|
[:write, o]
|
356
395
|
)
|
357
396
|
}
|
397
|
+
|
398
|
+
# Eventually we should add some APIs to the io_uring backend to query the
|
399
|
+
# contxt store, then add some tests here to verify that the chain op ctx is
|
400
|
+
# released properly before raising the error (for the time being this has
|
401
|
+
# been verified manually).
|
358
402
|
end
|
359
403
|
end
|
data/test/test_fiber.rb
CHANGED
data/test/test_io.rb
CHANGED
@@ -98,8 +98,10 @@ class IOTest < MiniTest::Test
|
|
98
98
|
|
99
99
|
buf = []
|
100
100
|
f = spin do
|
101
|
+
peer = receive
|
101
102
|
while (l = i.gets)
|
102
103
|
buf << l
|
104
|
+
peer << true
|
103
105
|
end
|
104
106
|
end
|
105
107
|
|
@@ -107,11 +109,12 @@ class IOTest < MiniTest::Test
|
|
107
109
|
assert_equal [], buf
|
108
110
|
|
109
111
|
o << 'fab'
|
110
|
-
|
112
|
+
f << Fiber.current
|
113
|
+
sleep 0.05
|
111
114
|
assert_equal [], buf
|
112
115
|
|
113
116
|
o << "ulous\n"
|
114
|
-
|
117
|
+
receive
|
115
118
|
assert_equal ["fabulous\n"], buf
|
116
119
|
|
117
120
|
o.close
|
@@ -287,7 +290,7 @@ class IOClassMethodsTest < MiniTest::Test
|
|
287
290
|
end
|
288
291
|
|
289
292
|
def test_foreach
|
290
|
-
skip
|
293
|
+
skip 'IO.foreach is not yet implemented'
|
291
294
|
lines = []
|
292
295
|
IO.foreach(__FILE__) { |l| lines << l }
|
293
296
|
assert_equal "# frozen_string_literal: true\n", lines[0]
|
data/test/test_signal.rb
CHANGED
data/test/test_thread_pool.rb
CHANGED
data/test/test_timer.rb
CHANGED
@@ -31,7 +31,7 @@ class TimerMoveOnAfterTest < MiniTest::Test
|
|
31
31
|
end
|
32
32
|
t1 = Time.now
|
33
33
|
|
34
|
-
assert_in_range 0.01..0.
|
34
|
+
assert_in_range 0.01..0.05, t1 - t0
|
35
35
|
assert_equal :bar, v
|
36
36
|
end
|
37
37
|
|
@@ -75,14 +75,21 @@ class TimerCancelAfterTest < MiniTest::Test
|
|
75
75
|
end
|
76
76
|
|
77
77
|
def test_timer_cancel_after_with_reset
|
78
|
-
|
78
|
+
buf = []
|
79
79
|
@timer.cancel_after(0.01) do
|
80
|
-
sleep 0.
|
80
|
+
sleep 0.005
|
81
|
+
buf << 1
|
81
82
|
@timer.reset
|
82
|
-
sleep 0.
|
83
|
+
sleep 0.005
|
84
|
+
buf << 2
|
85
|
+
@timer.reset
|
86
|
+
sleep 0.005
|
87
|
+
buf << 3
|
88
|
+
@timer.reset
|
89
|
+
sleep 0.005
|
90
|
+
buf << 4
|
83
91
|
end
|
84
|
-
|
85
|
-
assert_in_range 0.012..0.024, t1 - t0
|
92
|
+
assert_equal [1, 2, 3, 4], buf
|
86
93
|
end
|
87
94
|
|
88
95
|
class CustomException < Exception
|
@@ -140,7 +147,6 @@ class TimerMiscTest < MiniTest::Test
|
|
140
147
|
snooze
|
141
148
|
assert_equal [], buffer
|
142
149
|
sleep 0.1
|
143
|
-
p :post_sleep
|
144
150
|
assert_equal [2], buffer
|
145
151
|
end
|
146
152
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: polyphony
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.55.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sharon Rosner
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-06-
|
11
|
+
date: 2021-06-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake-compiler
|
@@ -359,6 +359,7 @@ files:
|
|
359
359
|
- examples/io/rack_server.rb
|
360
360
|
- examples/io/raw.rb
|
361
361
|
- examples/io/reline.rb
|
362
|
+
- examples/io/stdio.rb
|
362
363
|
- examples/io/system.rb
|
363
364
|
- examples/io/tcp_proxy.rb
|
364
365
|
- examples/io/tcpserver.rb
|