polyphony 0.47.5.1 → 0.48.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/Gemfile.lock +1 -1
- data/TODO.md +31 -17
- data/examples/io/tcp_proxy.rb +32 -0
- data/examples/performance/line_splitting.rb +34 -0
- data/examples/performance/loop.rb +32 -0
- data/examples/performance/thread-vs-fiber/polyphony_server.rb +6 -0
- data/ext/polyphony/backend_common.h +2 -2
- data/ext/polyphony/backend_io_uring.c +31 -68
- data/ext/polyphony/backend_libev.c +18 -59
- data/ext/polyphony/event.c +1 -1
- data/ext/polyphony/polyphony.c +0 -2
- data/ext/polyphony/polyphony.h +5 -4
- data/ext/polyphony/queue.c +2 -2
- data/ext/polyphony/thread.c +9 -28
- data/lib/polyphony/adapters/postgres.rb +3 -3
- data/lib/polyphony/core/global_api.rb +14 -2
- data/lib/polyphony/core/thread_pool.rb +3 -1
- data/lib/polyphony/core/throttler.rb +1 -1
- data/lib/polyphony/extensions/fiber.rb +20 -6
- data/lib/polyphony/extensions/io.rb +8 -14
- data/lib/polyphony/extensions/openssl.rb +4 -4
- data/lib/polyphony/extensions/socket.rb +1 -1
- data/lib/polyphony/version.rb +1 -1
- data/polyphony.gemspec +1 -1
- data/test/helper.rb +1 -0
- data/test/test_backend.rb +1 -1
- data/test/test_fiber.rb +64 -0
- data/test/test_global_api.rb +30 -0
- data/test/test_io.rb +26 -0
- data/test/test_socket.rb +0 -1
- data/test/test_supervise.rb +1 -1
- metadata +6 -4
- data/ext/polyphony/backend.h +0 -26
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3a699d313072b1beba17c900491dd2a4f454bc8faea69eb157634e98cba7ee90
|
4
|
+
data.tar.gz: 3d8579b2c927c34876560ce878e960b9bc48a857ce43890dc069b4f0fa5ad75f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d6efca5f42f21fe25ab5e6064675c2806dcad9d86e2e80c908987fcce855dd520ab51c4913e5a9001d3e4a84880a6606d124fa46dedff7c2e437a1ef6e156a82
|
7
|
+
data.tar.gz: 73ad6c59d4f7e9f8f0d317f480be1ba33c1a70dcf130a717693c2bb5a9ef3e226bfff3bdb635461b505bd2e9bbec7a2174c6a206bee2a94709ba9739ab087956
|
data/CHANGELOG.md
CHANGED
data/Gemfile.lock
CHANGED
data/TODO.md
CHANGED
@@ -1,29 +1,43 @@
|
|
1
|
-
-
|
1
|
+
- Override stock `SizedQueue` impl with Queue with capacity
|
2
|
+
|
3
|
+
- Add support for `break` and `StopIteration` in all loops (with tests)
|
2
4
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
5
|
+
- Change `IO#gets` to use `String#split` to cut into lines, much faster (see
|
6
|
+
examples/performance/line_splitting.rb)
|
7
|
+
|
8
|
+
- More tight loops
|
9
|
+
- `IO#gets_loop`, `Socket#gets_loop`, `OpenSSL::Socket#gets_loop` (medium effort)
|
10
|
+
- `Fiber#receive_loop` (very little effort, should be implemented in C)
|
7
11
|
|
8
|
-
- Set graceful termination in `@graceful_shutdown`
|
9
|
-
- Add `Fiber#graceful_shutdown?` method
|
10
|
-
- Returns `@graceful_shutdown`
|
11
12
|
|
12
|
-
|
13
|
+
- Add `Backend#splice`, `Backend#splice_loop` for implementing stuff like proxying:
|
13
14
|
|
14
15
|
```ruby
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
def two_way_proxy(socket1, socket2)
|
17
|
+
backend = Thread.current.backend
|
18
|
+
f1 = spin { backend.splice_loop(socket1, socket2) }
|
19
|
+
f2 = spin { backend.splice_loop(socket2, socket1) }
|
20
|
+
Fiber.await(f1, f2)
|
19
21
|
end
|
20
22
|
```
|
21
23
|
|
24
|
+
- Graceful shutdown again:
|
25
|
+
- What happens to children when doing a graceful shutdown?
|
26
|
+
- What are the implications of passing graceful shutdown flag to children?
|
27
|
+
- What about errors while doing a graceful shutdown?
|
28
|
+
- What about graceful restarts?
|
29
|
+
- Some interesting discussions:
|
30
|
+
- https://trio.discourse.group/search?q=graceful%20shutdown
|
31
|
+
- https://github.com/python-trio/trio/issues/147
|
32
|
+
- https://github.com/python-trio/trio/issues/143
|
33
|
+
- https://trio.discourse.group/t/graceful-shutdown/93/2
|
34
|
+
- https://250bpm.com/blog:146/
|
35
|
+
- https://www.rodrigoaraujo.me/posts/golang-pattern-graceful-shutdown-of-concurrent-events/
|
36
|
+
- https://github.com/tj/go-gracefully
|
22
37
|
- `Fiber#finalize_children` should pass graceful shutdown flag to children
|
23
|
-
|
24
|
-
-
|
25
|
-
|
26
|
-
- Fiber#receive_loop (very little effort, should be implemented in C)
|
38
|
+
- A good use case is an HTTP server that on graceful shutdown:
|
39
|
+
- stops listening
|
40
|
+
- waits for all ongoing requests to finish, optionally with a timeout
|
27
41
|
|
28
42
|
## Roadmap for Polyphony 1.0
|
29
43
|
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'polyphony'
|
5
|
+
|
6
|
+
server1 = TCPServer.open('127.0.0.1', 1234)
|
7
|
+
server2 = TCPServer.open('127.0.0.1', 1235)
|
8
|
+
|
9
|
+
puts "Pid: #{Process.pid}"
|
10
|
+
puts 'Proxying port 1234 => port 1235'
|
11
|
+
|
12
|
+
client1 = client2 = nil
|
13
|
+
|
14
|
+
f1 = spin {
|
15
|
+
client1 = server1.accept
|
16
|
+
loop do
|
17
|
+
if client2
|
18
|
+
Thread.current.backend.splice_loop(client1, client2)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
}
|
22
|
+
|
23
|
+
f2 = spin {
|
24
|
+
client2 = server2.accept
|
25
|
+
loop do
|
26
|
+
if client1
|
27
|
+
Thread.current.backend.splice_loop(client2, client1)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
}
|
31
|
+
|
32
|
+
Fiber.await(f1, f2)
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "benchmark/ips"
|
4
|
+
|
5
|
+
def slice
|
6
|
+
str = ('*' * 40) + "\n" + ('*' * 40) + "\n" + ('*' * 40) + "\n" + ('*' * 40) + "\n" + ('*' * 40)
|
7
|
+
lines = []
|
8
|
+
while true
|
9
|
+
idx = str.index("\n")
|
10
|
+
break unless idx
|
11
|
+
|
12
|
+
lines << str.slice!(0, idx + 1)
|
13
|
+
end
|
14
|
+
raise unless lines.size == 4
|
15
|
+
raise unless str == ('*' * 40)
|
16
|
+
end
|
17
|
+
|
18
|
+
def split
|
19
|
+
str = ('*' * 40) + "\n" + ('*' * 40) + "\n" + ('*' * 40) + "\n" + ('*' * 40) + "\n" + ('*' * 40)
|
20
|
+
lines = str.split("\n")
|
21
|
+
if str[-1] == "\n"
|
22
|
+
str = ''
|
23
|
+
else
|
24
|
+
str = lines.pop
|
25
|
+
end
|
26
|
+
raise unless lines.size == 4
|
27
|
+
raise unless str == ('*' * 40)
|
28
|
+
end
|
29
|
+
|
30
|
+
Benchmark.ips do |x|
|
31
|
+
x.report("slice") { slice }
|
32
|
+
x.report("split") { split }
|
33
|
+
x.compare!
|
34
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'benchmark'
|
4
|
+
|
5
|
+
LIMIT = 1_000_0
|
6
|
+
|
7
|
+
def do_while
|
8
|
+
i = 0
|
9
|
+
while true
|
10
|
+
i += 1
|
11
|
+
break if i == LIMIT
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def do_loop
|
16
|
+
i = 0
|
17
|
+
loop do
|
18
|
+
i += 1
|
19
|
+
break if i == LIMIT
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
GC.disable
|
24
|
+
Benchmark.bm do |x|
|
25
|
+
x.report('while') do
|
26
|
+
LIMIT.times { do_while }
|
27
|
+
end
|
28
|
+
x.report('loop') do
|
29
|
+
LIMIT.times { do_loop }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
@@ -29,6 +29,12 @@ end
|
|
29
29
|
server = TCPServer.open('0.0.0.0', 1234)
|
30
30
|
puts "pid #{Process.pid} Polyphony (#{Thread.current.backend.kind}) listening on port 1234"
|
31
31
|
|
32
|
+
spin_loop(interval: 10) do
|
33
|
+
p Thread.current.fiber_scheduling_stats
|
34
|
+
end
|
35
|
+
|
36
|
+
GC.disable
|
37
|
+
|
32
38
|
server.accept_loop do |c|
|
33
39
|
spin { handle_client(c) }
|
34
40
|
end
|
@@ -70,9 +70,9 @@ inline VALUE io_enc_str(VALUE str, rb_io_t *fptr) {
|
|
70
70
|
|
71
71
|
inline VALUE backend_await(Backend_t *backend) {
|
72
72
|
VALUE ret;
|
73
|
-
backend->
|
73
|
+
backend->pending_count++;
|
74
74
|
ret = Thread_switch_fiber(rb_thread_current());
|
75
|
-
backend->
|
75
|
+
backend->pending_count--;
|
76
76
|
RB_GC_GUARD(ret);
|
77
77
|
return ret;
|
78
78
|
}
|
@@ -30,11 +30,14 @@ static int pidfd_open(pid_t pid, unsigned int flags) {
|
|
30
30
|
VALUE SYM_io_uring;
|
31
31
|
|
32
32
|
typedef struct Backend_t {
|
33
|
+
// common fields
|
34
|
+
unsigned int currently_polling;
|
35
|
+
unsigned int pending_count;
|
36
|
+
unsigned int poll_no_wait_count;
|
37
|
+
|
38
|
+
// implementation-specific fields
|
33
39
|
struct io_uring ring;
|
34
40
|
op_context_store_t store;
|
35
|
-
int waiting_for_cqe;
|
36
|
-
unsigned int ref_count;
|
37
|
-
unsigned int run_no_wait_count;
|
38
41
|
unsigned int pending_sqes;
|
39
42
|
unsigned int prepared_limit;
|
40
43
|
int event_fd;
|
@@ -65,11 +68,11 @@ static VALUE Backend_initialize(VALUE self) {
|
|
65
68
|
Backend_t *backend;
|
66
69
|
GetBackend(self, backend);
|
67
70
|
|
68
|
-
backend->
|
69
|
-
backend->
|
70
|
-
backend->
|
71
|
+
backend->currently_polling = 0;
|
72
|
+
backend->pending_count = 0;
|
73
|
+
backend->poll_no_wait_count = 0;
|
71
74
|
backend->pending_sqes = 0;
|
72
|
-
backend->prepared_limit =
|
75
|
+
backend->prepared_limit = 256;
|
73
76
|
|
74
77
|
context_store_initialize(&backend->store);
|
75
78
|
io_uring_queue_init(backend->prepared_limit, &backend->ring, 0);
|
@@ -95,46 +98,19 @@ VALUE Backend_post_fork(VALUE self) {
|
|
95
98
|
io_uring_queue_exit(&backend->ring);
|
96
99
|
io_uring_queue_init(backend->prepared_limit, &backend->ring, 0);
|
97
100
|
context_store_free(&backend->store);
|
98
|
-
backend->
|
99
|
-
backend->
|
100
|
-
backend->
|
101
|
+
backend->currently_polling = 0;
|
102
|
+
backend->pending_count = 0;
|
103
|
+
backend->poll_no_wait_count = 0;
|
101
104
|
backend->pending_sqes = 0;
|
102
105
|
|
103
106
|
return self;
|
104
107
|
}
|
105
108
|
|
106
|
-
|
107
|
-
Backend_t *backend;
|
108
|
-
GetBackend(self, backend);
|
109
|
-
|
110
|
-
backend->ref_count++;
|
111
|
-
return self;
|
112
|
-
}
|
113
|
-
|
114
|
-
VALUE Backend_unref(VALUE self) {
|
109
|
+
unsigned int Backend_pending_count(VALUE self) {
|
115
110
|
Backend_t *backend;
|
116
111
|
GetBackend(self, backend);
|
117
112
|
|
118
|
-
backend->
|
119
|
-
return self;
|
120
|
-
}
|
121
|
-
|
122
|
-
int Backend_ref_count(VALUE self) {
|
123
|
-
Backend_t *backend;
|
124
|
-
GetBackend(self, backend);
|
125
|
-
|
126
|
-
return backend->ref_count;
|
127
|
-
}
|
128
|
-
|
129
|
-
void Backend_reset_ref_count(VALUE self) {
|
130
|
-
Backend_t *backend;
|
131
|
-
GetBackend(self, backend);
|
132
|
-
|
133
|
-
backend->ref_count = 0;
|
134
|
-
}
|
135
|
-
|
136
|
-
VALUE Backend_pending_count(VALUE self) {
|
137
|
-
return INT2NUM(0);
|
113
|
+
return backend->pending_count;
|
138
114
|
}
|
139
115
|
|
140
116
|
typedef struct poll_context {
|
@@ -158,7 +134,7 @@ static inline bool cq_ring_needs_flush(struct io_uring *ring) {
|
|
158
134
|
|
159
135
|
void io_uring_backend_handle_completion(struct io_uring_cqe *cqe, Backend_t *backend) {
|
160
136
|
op_context_t *ctx = io_uring_cqe_get_data(cqe);
|
161
|
-
if (ctx
|
137
|
+
if (!ctx) return;
|
162
138
|
|
163
139
|
ctx->result = cqe->res;
|
164
140
|
|
@@ -211,9 +187,9 @@ void io_uring_backend_poll(Backend_t *backend) {
|
|
211
187
|
io_uring_submit(&backend->ring);
|
212
188
|
}
|
213
189
|
|
214
|
-
backend->
|
190
|
+
backend->currently_polling = 1;
|
215
191
|
rb_thread_call_without_gvl(io_uring_backend_poll_without_gvl, (void *)&poll_ctx, RUBY_UBF_IO, 0);
|
216
|
-
backend->
|
192
|
+
backend->currently_polling = 0;
|
217
193
|
if (poll_ctx.result < 0) return;
|
218
194
|
|
219
195
|
io_uring_backend_handle_completion(poll_ctx.cqe, backend);
|
@@ -226,14 +202,14 @@ VALUE Backend_poll(VALUE self, VALUE nowait, VALUE current_fiber, VALUE runqueue
|
|
226
202
|
GetBackend(self, backend);
|
227
203
|
|
228
204
|
if (is_nowait) {
|
229
|
-
backend->
|
230
|
-
if (backend->
|
205
|
+
backend->poll_no_wait_count++;
|
206
|
+
if (backend->poll_no_wait_count < 10) return self;
|
231
207
|
|
232
208
|
long runnable_count = Runqueue_len(runqueue);
|
233
|
-
if (backend->
|
209
|
+
if (backend->poll_no_wait_count < runnable_count) return self;
|
234
210
|
}
|
235
211
|
|
236
|
-
backend->
|
212
|
+
backend->poll_no_wait_count = 0;
|
237
213
|
|
238
214
|
if (is_nowait && backend->pending_sqes) {
|
239
215
|
backend->pending_sqes = 0;
|
@@ -252,7 +228,7 @@ VALUE Backend_wakeup(VALUE self) {
|
|
252
228
|
Backend_t *backend;
|
253
229
|
GetBackend(self, backend);
|
254
230
|
|
255
|
-
if (backend->
|
231
|
+
if (backend->currently_polling) {
|
256
232
|
// Since we're currently blocking while waiting for a completion, we add a
|
257
233
|
// NOP which would cause the io_uring_enter syscall to return
|
258
234
|
struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
|
@@ -284,12 +260,12 @@ int io_uring_backend_defer_submit_and_await(
|
|
284
260
|
VALUE switchpoint_result = Qnil;
|
285
261
|
|
286
262
|
io_uring_sqe_set_data(sqe, ctx);
|
287
|
-
io_uring_sqe_set_flags(sqe, IOSQE_ASYNC);
|
263
|
+
// io_uring_sqe_set_flags(sqe, IOSQE_ASYNC);
|
288
264
|
io_uring_backend_defer_submit(backend);
|
289
265
|
|
290
|
-
backend->
|
266
|
+
backend->pending_count++;
|
291
267
|
switchpoint_result = backend_await(backend);
|
292
|
-
backend->
|
268
|
+
backend->pending_count--;
|
293
269
|
|
294
270
|
if (!ctx->completed) {
|
295
271
|
ctx->result = -ECANCELED;
|
@@ -351,7 +327,7 @@ VALUE Backend_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof)
|
|
351
327
|
|
352
328
|
if (result < 0)
|
353
329
|
rb_syserr_fail(-result, strerror(-result));
|
354
|
-
else if (result
|
330
|
+
else if (!result)
|
355
331
|
break; // EOF
|
356
332
|
else {
|
357
333
|
total += result;
|
@@ -373,7 +349,7 @@ VALUE Backend_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof)
|
|
373
349
|
io_set_read_length(str, total, shrinkable);
|
374
350
|
io_enc_str(str, fptr);
|
375
351
|
|
376
|
-
if (total
|
352
|
+
if (!total) return Qnil;
|
377
353
|
|
378
354
|
return str;
|
379
355
|
}
|
@@ -410,7 +386,7 @@ VALUE Backend_read_loop(VALUE self, VALUE io) {
|
|
410
386
|
|
411
387
|
if (result < 0)
|
412
388
|
rb_syserr_fail(-result, strerror(-result));
|
413
|
-
else if (result
|
389
|
+
else if (!result)
|
414
390
|
break; // EOF
|
415
391
|
else {
|
416
392
|
total = result;
|
@@ -581,7 +557,7 @@ VALUE Backend_recv(VALUE self, VALUE io, VALUE str, VALUE length) {
|
|
581
557
|
io_set_read_length(str, total, shrinkable);
|
582
558
|
io_enc_str(str, fptr);
|
583
559
|
|
584
|
-
if (total
|
560
|
+
if (!total) return Qnil;
|
585
561
|
|
586
562
|
return str;
|
587
563
|
}
|
@@ -618,7 +594,7 @@ VALUE Backend_recv_loop(VALUE self, VALUE io) {
|
|
618
594
|
|
619
595
|
if (result < 0)
|
620
596
|
rb_syserr_fail(-result, strerror(-result));
|
621
|
-
else if (result
|
597
|
+
else if (!result)
|
622
598
|
break; // EOF
|
623
599
|
else {
|
624
600
|
total = result;
|
@@ -950,10 +926,6 @@ void Init_Backend() {
|
|
950
926
|
rb_define_method(cBackend, "initialize", Backend_initialize, 0);
|
951
927
|
rb_define_method(cBackend, "finalize", Backend_finalize, 0);
|
952
928
|
rb_define_method(cBackend, "post_fork", Backend_post_fork, 0);
|
953
|
-
rb_define_method(cBackend, "pending_count", Backend_pending_count, 0);
|
954
|
-
|
955
|
-
rb_define_method(cBackend, "ref", Backend_ref, 0);
|
956
|
-
rb_define_method(cBackend, "unref", Backend_unref, 0);
|
957
929
|
|
958
930
|
rb_define_method(cBackend, "poll", Backend_poll, 3);
|
959
931
|
rb_define_method(cBackend, "break", Backend_wakeup, 0);
|
@@ -977,15 +949,6 @@ void Init_Backend() {
|
|
977
949
|
rb_define_method(cBackend, "kind", Backend_kind, 0);
|
978
950
|
|
979
951
|
SYM_io_uring = ID2SYM(rb_intern("io_uring"));
|
980
|
-
|
981
|
-
__BACKEND__.pending_count = Backend_pending_count;
|
982
|
-
__BACKEND__.poll = Backend_poll;
|
983
|
-
__BACKEND__.ref = Backend_ref;
|
984
|
-
__BACKEND__.ref_count = Backend_ref_count;
|
985
|
-
__BACKEND__.reset_ref_count = Backend_reset_ref_count;
|
986
|
-
__BACKEND__.unref = Backend_unref;
|
987
|
-
__BACKEND__.wait_event = Backend_wait_event;
|
988
|
-
__BACKEND__.wakeup = Backend_wakeup;
|
989
952
|
}
|
990
953
|
|
991
954
|
#endif // POLYPHONY_BACKEND_LIBURING
|
@@ -40,11 +40,14 @@ inline void io_set_nonblock(rb_io_t *fptr, VALUE io) {
|
|
40
40
|
}
|
41
41
|
|
42
42
|
typedef struct Backend_t {
|
43
|
+
// common fields
|
44
|
+
unsigned int currently_polling;
|
45
|
+
unsigned int pending_count;
|
46
|
+
unsigned int poll_no_wait_count;
|
47
|
+
|
48
|
+
// implementation-specific fields
|
43
49
|
struct ev_loop *ev_loop;
|
44
50
|
struct ev_async break_async;
|
45
|
-
int running;
|
46
|
-
int ref_count;
|
47
|
-
int run_no_wait_count;
|
48
51
|
} Backend_t;
|
49
52
|
|
50
53
|
static size_t Backend_size(const void *ptr) {
|
@@ -83,9 +86,9 @@ static VALUE Backend_initialize(VALUE self) {
|
|
83
86
|
ev_async_start(backend->ev_loop, &backend->break_async);
|
84
87
|
ev_unref(backend->ev_loop); // don't count the break_async watcher
|
85
88
|
|
86
|
-
backend->
|
87
|
-
backend->
|
88
|
-
backend->
|
89
|
+
backend->currently_polling = 0;
|
90
|
+
backend->pending_count = 0;
|
91
|
+
backend->poll_no_wait_count = 0;
|
89
92
|
|
90
93
|
return Qnil;
|
91
94
|
}
|
@@ -116,42 +119,11 @@ VALUE Backend_post_fork(VALUE self) {
|
|
116
119
|
return self;
|
117
120
|
}
|
118
121
|
|
119
|
-
|
120
|
-
Backend_t *backend;
|
121
|
-
GetBackend(self, backend);
|
122
|
-
|
123
|
-
backend->ref_count++;
|
124
|
-
return self;
|
125
|
-
}
|
126
|
-
|
127
|
-
VALUE Backend_unref(VALUE self) {
|
128
|
-
Backend_t *backend;
|
129
|
-
GetBackend(self, backend);
|
130
|
-
|
131
|
-
backend->ref_count--;
|
132
|
-
return self;
|
133
|
-
}
|
134
|
-
|
135
|
-
int Backend_ref_count(VALUE self) {
|
136
|
-
Backend_t *backend;
|
137
|
-
GetBackend(self, backend);
|
138
|
-
|
139
|
-
return backend->ref_count;
|
140
|
-
}
|
141
|
-
|
142
|
-
void Backend_reset_ref_count(VALUE self) {
|
122
|
+
unsigned int Backend_pending_count(VALUE self) {
|
143
123
|
Backend_t *backend;
|
144
124
|
GetBackend(self, backend);
|
145
125
|
|
146
|
-
backend->
|
147
|
-
}
|
148
|
-
|
149
|
-
VALUE Backend_pending_count(VALUE self) {
|
150
|
-
int count;
|
151
|
-
Backend_t *backend;
|
152
|
-
GetBackend(self, backend);
|
153
|
-
count = ev_pending_count(backend->ev_loop);
|
154
|
-
return INT2NUM(count);
|
126
|
+
return backend->pending_count;
|
155
127
|
}
|
156
128
|
|
157
129
|
VALUE Backend_poll(VALUE self, VALUE nowait, VALUE current_fiber, VALUE runqueue) {
|
@@ -160,19 +132,19 @@ VALUE Backend_poll(VALUE self, VALUE nowait, VALUE current_fiber, VALUE runqueue
|
|
160
132
|
GetBackend(self, backend);
|
161
133
|
|
162
134
|
if (is_nowait) {
|
163
|
-
backend->
|
164
|
-
if (backend->
|
135
|
+
backend->poll_no_wait_count++;
|
136
|
+
if (backend->poll_no_wait_count < 10) return self;
|
165
137
|
|
166
138
|
long runnable_count = Runqueue_len(runqueue);
|
167
|
-
if (backend->
|
139
|
+
if (backend->poll_no_wait_count < runnable_count) return self;
|
168
140
|
}
|
169
141
|
|
170
|
-
backend->
|
142
|
+
backend->poll_no_wait_count = 0;
|
171
143
|
|
172
144
|
COND_TRACE(2, SYM_fiber_event_poll_enter, current_fiber);
|
173
|
-
backend->
|
145
|
+
backend->currently_polling = 1;
|
174
146
|
ev_run(backend->ev_loop, is_nowait ? EVRUN_NOWAIT : EVRUN_ONCE);
|
175
|
-
backend->
|
147
|
+
backend->currently_polling = 0;
|
176
148
|
COND_TRACE(2, SYM_fiber_event_poll_leave, current_fiber);
|
177
149
|
|
178
150
|
return self;
|
@@ -182,7 +154,7 @@ VALUE Backend_wakeup(VALUE self) {
|
|
182
154
|
Backend_t *backend;
|
183
155
|
GetBackend(self, backend);
|
184
156
|
|
185
|
-
if (backend->
|
157
|
+
if (backend->currently_polling) {
|
186
158
|
// Since the loop will run until at least one event has occurred, we signal
|
187
159
|
// the selector's associated async watcher, which will cause the ev loop to
|
188
160
|
// return. In contrast to using `ev_break` to break out of the loop, which
|
@@ -854,10 +826,6 @@ void Init_Backend() {
|
|
854
826
|
rb_define_method(cBackend, "initialize", Backend_initialize, 0);
|
855
827
|
rb_define_method(cBackend, "finalize", Backend_finalize, 0);
|
856
828
|
rb_define_method(cBackend, "post_fork", Backend_post_fork, 0);
|
857
|
-
rb_define_method(cBackend, "pending_count", Backend_pending_count, 0);
|
858
|
-
|
859
|
-
rb_define_method(cBackend, "ref", Backend_ref, 0);
|
860
|
-
rb_define_method(cBackend, "unref", Backend_unref, 0);
|
861
829
|
|
862
830
|
rb_define_method(cBackend, "poll", Backend_poll, 3);
|
863
831
|
rb_define_method(cBackend, "break", Backend_wakeup, 0);
|
@@ -882,15 +850,6 @@ void Init_Backend() {
|
|
882
850
|
|
883
851
|
ID_ivar_is_nonblocking = rb_intern("@is_nonblocking");
|
884
852
|
SYM_libev = ID2SYM(rb_intern("libev"));
|
885
|
-
|
886
|
-
__BACKEND__.pending_count = Backend_pending_count;
|
887
|
-
__BACKEND__.poll = Backend_poll;
|
888
|
-
__BACKEND__.ref = Backend_ref;
|
889
|
-
__BACKEND__.ref_count = Backend_ref_count;
|
890
|
-
__BACKEND__.reset_ref_count = Backend_reset_ref_count;
|
891
|
-
__BACKEND__.unref = Backend_unref;
|
892
|
-
__BACKEND__.wait_event = Backend_wait_event;
|
893
|
-
__BACKEND__.wakeup = Backend_wakeup;
|
894
853
|
}
|
895
854
|
|
896
855
|
#endif // POLYPHONY_BACKEND_LIBEV
|
data/ext/polyphony/event.c
CHANGED
@@ -66,7 +66,7 @@ VALUE Event_await(VALUE self) {
|
|
66
66
|
|
67
67
|
VALUE backend = rb_ivar_get(rb_thread_current(), ID_ivar_backend);
|
68
68
|
event->waiting_fiber = rb_fiber_current();
|
69
|
-
VALUE switchpoint_result =
|
69
|
+
VALUE switchpoint_result = Backend_wait_event(backend, Qnil);
|
70
70
|
event->waiting_fiber = Qnil;
|
71
71
|
|
72
72
|
RAISE_IF_EXCEPTION(switchpoint_result);
|
data/ext/polyphony/polyphony.c
CHANGED
data/ext/polyphony/polyphony.h
CHANGED
@@ -4,7 +4,6 @@
|
|
4
4
|
#include <execinfo.h>
|
5
5
|
|
6
6
|
#include "ruby.h"
|
7
|
-
#include "backend.h"
|
8
7
|
#include "runqueue_ring_buffer.h"
|
9
8
|
|
10
9
|
// debugging
|
@@ -32,9 +31,6 @@
|
|
32
31
|
// Fiber#transfer
|
33
32
|
#define FIBER_TRANSFER(fiber, value) rb_funcall(fiber, ID_transfer, 1, value)
|
34
33
|
|
35
|
-
extern backend_interface_t backend_interface;
|
36
|
-
#define __BACKEND__ (backend_interface)
|
37
|
-
|
38
34
|
extern VALUE mPolyphony;
|
39
35
|
extern VALUE cQueue;
|
40
36
|
extern VALUE cEvent;
|
@@ -92,6 +88,11 @@ void Runqueue_clear(VALUE self);
|
|
92
88
|
long Runqueue_len(VALUE self);
|
93
89
|
int Runqueue_empty_p(VALUE self);
|
94
90
|
|
91
|
+
unsigned int Backend_pending_count(VALUE self);
|
92
|
+
VALUE Backend_poll(VALUE self, VALUE nowait, VALUE current_fiber, VALUE runqueue);
|
93
|
+
VALUE Backend_wait_event(VALUE self, VALUE raise_on_exception);
|
94
|
+
VALUE Backend_wakeup(VALUE self);
|
95
|
+
|
95
96
|
VALUE Thread_schedule_fiber(VALUE thread, VALUE fiber, VALUE value);
|
96
97
|
VALUE Thread_schedule_fiber_with_priority(VALUE thread, VALUE fiber, VALUE value);
|
97
98
|
VALUE Thread_switch_fiber(VALUE thread);
|
data/ext/polyphony/queue.c
CHANGED
@@ -86,7 +86,7 @@ inline void capped_queue_block_push(Queue_t *queue) {
|
|
86
86
|
if (queue->capacity > queue->values.count) Fiber_make_runnable(fiber, Qnil);
|
87
87
|
|
88
88
|
ring_buffer_push(&queue->push_queue, fiber);
|
89
|
-
switchpoint_result =
|
89
|
+
switchpoint_result = Backend_wait_event(backend, Qnil);
|
90
90
|
ring_buffer_delete(&queue->push_queue, fiber);
|
91
91
|
|
92
92
|
RAISE_IF_EXCEPTION(switchpoint_result);
|
@@ -131,7 +131,7 @@ VALUE Queue_shift(VALUE self) {
|
|
131
131
|
if (queue->values.count) Fiber_make_runnable(fiber, Qnil);
|
132
132
|
|
133
133
|
ring_buffer_push(&queue->shift_queue, fiber);
|
134
|
-
VALUE switchpoint_result =
|
134
|
+
VALUE switchpoint_result = Backend_wait_event(backend, Qnil);
|
135
135
|
ring_buffer_delete(&queue->shift_queue, fiber);
|
136
136
|
|
137
137
|
RAISE_IF_EXCEPTION(switchpoint_result);
|
data/ext/polyphony/thread.c
CHANGED
@@ -17,16 +17,6 @@ static VALUE Thread_setup_fiber_scheduling(VALUE self) {
|
|
17
17
|
return self;
|
18
18
|
}
|
19
19
|
|
20
|
-
int Thread_fiber_ref_count(VALUE self) {
|
21
|
-
VALUE backend = rb_ivar_get(self, ID_ivar_backend);
|
22
|
-
return NUM2INT(__BACKEND__.ref_count(backend));
|
23
|
-
}
|
24
|
-
|
25
|
-
inline void Thread_fiber_reset_ref_count(VALUE self) {
|
26
|
-
VALUE backend = rb_ivar_get(self, ID_ivar_backend);
|
27
|
-
__BACKEND__.reset_ref_count(backend);
|
28
|
-
}
|
29
|
-
|
30
20
|
static VALUE SYM_scheduled_fibers;
|
31
21
|
static VALUE SYM_pending_watchers;
|
32
22
|
|
@@ -39,7 +29,7 @@ static VALUE Thread_fiber_scheduling_stats(VALUE self) {
|
|
39
29
|
long scheduled_count = Runqueue_len(runqueue);
|
40
30
|
rb_hash_aset(stats, SYM_scheduled_fibers, INT2NUM(scheduled_count));
|
41
31
|
|
42
|
-
pending_count =
|
32
|
+
pending_count = Backend_pending_count(backend);
|
43
33
|
rb_hash_aset(stats, SYM_pending_watchers, INT2NUM(pending_count));
|
44
34
|
|
45
35
|
return stats;
|
@@ -64,7 +54,7 @@ void schedule_fiber(VALUE self, VALUE fiber, VALUE value, int prioritize) {
|
|
64
54
|
// happen, not knowing that it there's already a fiber ready to run in its
|
65
55
|
// run queue.
|
66
56
|
VALUE backend = rb_ivar_get(self,ID_ivar_backend);
|
67
|
-
|
57
|
+
Backend_wakeup(backend);
|
68
58
|
}
|
69
59
|
}
|
70
60
|
}
|
@@ -84,25 +74,24 @@ VALUE Thread_switch_fiber(VALUE self) {
|
|
84
74
|
VALUE runqueue = rb_ivar_get(self, ID_ivar_runqueue);
|
85
75
|
runqueue_entry next;
|
86
76
|
VALUE backend = rb_ivar_get(self, ID_ivar_backend);
|
87
|
-
int
|
88
|
-
int backend_was_polled = 0;
|
77
|
+
unsigned int pending_count = Backend_pending_count(backend);
|
78
|
+
unsigned int backend_was_polled = 0;
|
89
79
|
|
90
80
|
if (__tracing_enabled__ && (rb_ivar_get(current_fiber, ID_ivar_running) != Qfalse))
|
91
81
|
TRACE(2, SYM_fiber_switchpoint, current_fiber);
|
92
82
|
|
93
|
-
ref_count = __BACKEND__.ref_count(backend);
|
94
83
|
while (1) {
|
95
84
|
next = Runqueue_shift(runqueue);
|
96
85
|
if (next.fiber != Qnil) {
|
97
|
-
if (backend_was_polled
|
86
|
+
if (!backend_was_polled && pending_count) {
|
98
87
|
// this prevents event starvation in case the run queue never empties
|
99
|
-
|
88
|
+
Backend_poll(backend, Qtrue, current_fiber, runqueue);
|
100
89
|
}
|
101
90
|
break;
|
102
91
|
}
|
103
|
-
if (
|
92
|
+
if (pending_count == 0) break;
|
104
93
|
|
105
|
-
|
94
|
+
Backend_poll(backend, Qnil, current_fiber, runqueue);
|
106
95
|
backend_was_polled = 1;
|
107
96
|
}
|
108
97
|
|
@@ -118,20 +107,13 @@ VALUE Thread_switch_fiber(VALUE self) {
|
|
118
107
|
next.value : FIBER_TRANSFER(next.fiber, next.value);
|
119
108
|
}
|
120
109
|
|
121
|
-
VALUE Thread_reset_fiber_scheduling(VALUE self) {
|
122
|
-
VALUE queue = rb_ivar_get(self, ID_ivar_runqueue);
|
123
|
-
Runqueue_clear(queue);
|
124
|
-
Thread_fiber_reset_ref_count(self);
|
125
|
-
return self;
|
126
|
-
}
|
127
|
-
|
128
110
|
VALUE Thread_fiber_schedule_and_wakeup(VALUE self, VALUE fiber, VALUE resume_obj) {
|
129
111
|
VALUE backend = rb_ivar_get(self, ID_ivar_backend);
|
130
112
|
if (fiber != Qnil) {
|
131
113
|
Thread_schedule_fiber_with_priority(self, fiber, resume_obj);
|
132
114
|
}
|
133
115
|
|
134
|
-
if (
|
116
|
+
if (Backend_wakeup(backend) == Qnil) {
|
135
117
|
// we're not inside the ev_loop, so we just do a switchpoint
|
136
118
|
Thread_switch_fiber(self);
|
137
119
|
}
|
@@ -146,7 +128,6 @@ VALUE Thread_debug(VALUE self) {
|
|
146
128
|
|
147
129
|
void Init_Thread() {
|
148
130
|
rb_define_method(rb_cThread, "setup_fiber_scheduling", Thread_setup_fiber_scheduling, 0);
|
149
|
-
rb_define_method(rb_cThread, "reset_fiber_scheduling", Thread_reset_fiber_scheduling, 0);
|
150
131
|
rb_define_method(rb_cThread, "fiber_scheduling_stats", Thread_fiber_scheduling_stats, 0);
|
151
132
|
rb_define_method(rb_cThread, "schedule_and_wakeup", Thread_fiber_schedule_and_wakeup, 2);
|
152
133
|
|
@@ -11,7 +11,7 @@ module ::PG
|
|
11
11
|
|
12
12
|
def self.connect_async(conn)
|
13
13
|
socket_io = conn.socket_io
|
14
|
-
|
14
|
+
while true
|
15
15
|
res = conn.connect_poll
|
16
16
|
case res
|
17
17
|
when PGRES_POLLING_FAILED then raise Error, conn.error_message
|
@@ -23,7 +23,7 @@ module ::PG
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def self.connect_sync(conn)
|
26
|
-
|
26
|
+
while true
|
27
27
|
res = conn.connect_poll
|
28
28
|
case res
|
29
29
|
when PGRES_POLLING_FAILED
|
@@ -96,7 +96,7 @@ class ::PG::Connection
|
|
96
96
|
def wait_for_notify(timeout = nil, &block)
|
97
97
|
return move_on_after(timeout) { wait_for_notify(&block) } if timeout
|
98
98
|
|
99
|
-
|
99
|
+
while true
|
100
100
|
Thread.current.backend.wait_io(socket_io, false)
|
101
101
|
consume_input
|
102
102
|
notice = notifies
|
@@ -59,7 +59,15 @@ module Polyphony
|
|
59
59
|
throttled_loop(rate: rate, interval: interval, &block)
|
60
60
|
end
|
61
61
|
else
|
62
|
-
|
62
|
+
spin_looped_block(tag, caller, block)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def spin_looped_block(tag, caller, block)
|
67
|
+
Fiber.current.spin(tag, caller) do
|
68
|
+
block.call while true
|
69
|
+
rescue LocalJumpError, StopIteration
|
70
|
+
# break called or StopIteration raised
|
63
71
|
end
|
64
72
|
end
|
65
73
|
|
@@ -133,8 +141,12 @@ module Polyphony
|
|
133
141
|
if opts[:count]
|
134
142
|
opts[:count].times { |_i| throttler.(&block) }
|
135
143
|
else
|
136
|
-
|
144
|
+
while true
|
145
|
+
throttler.(&block)
|
146
|
+
end
|
137
147
|
end
|
148
|
+
rescue LocalJumpError, StopIteration
|
149
|
+
# break called or StopIteration raised
|
138
150
|
ensure
|
139
151
|
throttler&.stop
|
140
152
|
end
|
@@ -34,9 +34,18 @@ module Polyphony
|
|
34
34
|
schedule Polyphony::Cancel.new
|
35
35
|
end
|
36
36
|
|
37
|
-
def
|
37
|
+
def graceful_shutdown=(graceful)
|
38
|
+
@graceful_shutdown = graceful
|
39
|
+
end
|
40
|
+
|
41
|
+
def graceful_shutdown?
|
42
|
+
@graceful_shutdown
|
43
|
+
end
|
44
|
+
|
45
|
+
def terminate(graceful = false)
|
38
46
|
return if @running == false
|
39
47
|
|
48
|
+
@graceful_shutdown = graceful
|
40
49
|
schedule Polyphony::Terminate.new
|
41
50
|
end
|
42
51
|
|
@@ -66,7 +75,9 @@ module Polyphony
|
|
66
75
|
@on_child_done = proc do |fiber, result|
|
67
76
|
self << fiber unless result.is_a?(Exception)
|
68
77
|
end
|
69
|
-
|
78
|
+
while true
|
79
|
+
supervise_perform(opts)
|
80
|
+
end
|
70
81
|
rescue Polyphony::MoveOn
|
71
82
|
# generated in #supervise_perform to stop supervisor
|
72
83
|
ensure
|
@@ -210,11 +221,14 @@ module Polyphony
|
|
210
221
|
@on_child_done&.(child_fiber, result)
|
211
222
|
end
|
212
223
|
|
213
|
-
def terminate_all_children
|
224
|
+
def terminate_all_children(graceful = false)
|
214
225
|
return unless @children
|
215
226
|
|
216
227
|
e = Polyphony::Terminate.new
|
217
|
-
@children.each_key
|
228
|
+
@children.each_key do |c|
|
229
|
+
c.graceful_shutdown = true if graceful
|
230
|
+
c.raise e
|
231
|
+
end
|
218
232
|
end
|
219
233
|
|
220
234
|
def await_all_children
|
@@ -230,8 +244,8 @@ module Polyphony
|
|
230
244
|
results.values
|
231
245
|
end
|
232
246
|
|
233
|
-
def shutdown_all_children
|
234
|
-
terminate_all_children
|
247
|
+
def shutdown_all_children(graceful = false)
|
248
|
+
terminate_all_children(graceful)
|
235
249
|
await_all_children
|
236
250
|
end
|
237
251
|
end
|
@@ -119,18 +119,11 @@ class ::IO
|
|
119
119
|
end
|
120
120
|
|
121
121
|
alias_method :orig_readpartial, :read
|
122
|
-
def readpartial(len, str =
|
123
|
-
|
124
|
-
result = Thread.current.backend.read(self, @read_buffer, len, false)
|
122
|
+
def readpartial(len, str = +'')
|
123
|
+
result = Thread.current.backend.read(self, str, len, false)
|
125
124
|
raise EOFError unless result
|
126
125
|
|
127
|
-
|
128
|
-
str << @read_buffer
|
129
|
-
else
|
130
|
-
str = @read_buffer
|
131
|
-
end
|
132
|
-
@read_buffer = +''
|
133
|
-
str
|
126
|
+
result
|
134
127
|
end
|
135
128
|
|
136
129
|
alias_method :orig_write, :write
|
@@ -154,15 +147,16 @@ class ::IO
|
|
154
147
|
|
155
148
|
@read_buffer ||= +''
|
156
149
|
|
157
|
-
|
150
|
+
while true
|
158
151
|
idx = @read_buffer.index(sep)
|
159
152
|
return @read_buffer.slice!(0, idx + sep_size) if idx
|
160
153
|
|
161
|
-
data = readpartial(8192)
|
154
|
+
data = readpartial(8192, +'')
|
155
|
+
return nil unless data
|
162
156
|
@read_buffer << data
|
163
|
-
rescue EOFError
|
164
|
-
return nil
|
165
157
|
end
|
158
|
+
rescue EOFError
|
159
|
+
return nil
|
166
160
|
end
|
167
161
|
|
168
162
|
# def print(*args)
|
@@ -25,7 +25,7 @@ class ::OpenSSL::SSL::SSLSocket
|
|
25
25
|
|
26
26
|
alias_method :orig_accept, :accept
|
27
27
|
def accept
|
28
|
-
|
28
|
+
while true
|
29
29
|
result = accept_nonblock(exception: false)
|
30
30
|
case result
|
31
31
|
when :wait_readable then Thread.current.backend.wait_io(io, false)
|
@@ -37,14 +37,14 @@ class ::OpenSSL::SSL::SSLSocket
|
|
37
37
|
end
|
38
38
|
|
39
39
|
def accept_loop
|
40
|
-
|
40
|
+
while true
|
41
41
|
yield accept
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
45
|
alias_method :orig_sysread, :sysread
|
46
46
|
def sysread(maxlen, buf = +'')
|
47
|
-
|
47
|
+
while true
|
48
48
|
case (result = read_nonblock(maxlen, buf, exception: false))
|
49
49
|
when :wait_readable then Thread.current.backend.wait_io(io, false)
|
50
50
|
when :wait_writable then Thread.current.backend.wait_io(io, true)
|
@@ -55,7 +55,7 @@ class ::OpenSSL::SSL::SSLSocket
|
|
55
55
|
|
56
56
|
alias_method :orig_syswrite, :syswrite
|
57
57
|
def syswrite(buf)
|
58
|
-
|
58
|
+
while true
|
59
59
|
case (result = write_nonblock(buf, exception: false))
|
60
60
|
when :wait_readable then Thread.current.backend.wait_io(io, false)
|
61
61
|
when :wait_writable then Thread.current.backend.wait_io(io, true)
|
data/lib/polyphony/version.rb
CHANGED
data/polyphony.gemspec
CHANGED
@@ -6,7 +6,7 @@ Gem::Specification.new do |s|
|
|
6
6
|
s.licenses = ['MIT']
|
7
7
|
s.summary = 'Fine grained concurrency for Ruby'
|
8
8
|
s.author = 'Sharon Rosner'
|
9
|
-
s.email = '
|
9
|
+
s.email = 'sharon@noteflakes.com'
|
10
10
|
s.files = `git ls-files`.split
|
11
11
|
s.homepage = 'https://digital-fabric.github.io/polyphony'
|
12
12
|
s.metadata = {
|
data/test/helper.rb
CHANGED
data/test/test_backend.rb
CHANGED
@@ -105,7 +105,7 @@ class BackendTest < MiniTest::Test
|
|
105
105
|
|
106
106
|
clients = []
|
107
107
|
server_fiber = spin do
|
108
|
-
@backend.accept_loop(server) { |c| clients << c }
|
108
|
+
@backend.accept_loop(server, TCPSocket) { |c| clients << c }
|
109
109
|
end
|
110
110
|
|
111
111
|
c1 = TCPSocket.new('127.0.0.1', 1234)
|
data/test/test_fiber.rb
CHANGED
@@ -1038,3 +1038,67 @@ class RestartTest < MiniTest::Test
|
|
1038
1038
|
assert_equal [f, 'foo', 'bar', :done, f2, 'baz', 42, :done], buffer
|
1039
1039
|
end
|
1040
1040
|
end
|
1041
|
+
|
1042
|
+
class GracefulTerminationTest < MiniTest::Test
|
1043
|
+
def test_graceful_termination
|
1044
|
+
buffer = []
|
1045
|
+
f = spin do
|
1046
|
+
buffer << 1
|
1047
|
+
snooze
|
1048
|
+
buffer << 2
|
1049
|
+
sleep 3
|
1050
|
+
buffer << 3
|
1051
|
+
ensure
|
1052
|
+
buffer << 4 if Fiber.current.graceful_shutdown?
|
1053
|
+
end
|
1054
|
+
|
1055
|
+
3.times { snooze }
|
1056
|
+
f.terminate(false)
|
1057
|
+
f.await
|
1058
|
+
assert_equal [1, 2], buffer
|
1059
|
+
|
1060
|
+
buffer = []
|
1061
|
+
f = spin do
|
1062
|
+
buffer << 1
|
1063
|
+
snooze
|
1064
|
+
buffer << 2
|
1065
|
+
sleep 3
|
1066
|
+
buffer << 3
|
1067
|
+
ensure
|
1068
|
+
buffer << 4 if Fiber.current.graceful_shutdown?
|
1069
|
+
end
|
1070
|
+
|
1071
|
+
3.times { snooze }
|
1072
|
+
f.terminate(true)
|
1073
|
+
f.await
|
1074
|
+
assert_equal [1, 2, 4], buffer
|
1075
|
+
end
|
1076
|
+
|
1077
|
+
def test_graceful_child_shutdown
|
1078
|
+
buffer = []
|
1079
|
+
f0 = spin do
|
1080
|
+
f1 = spin do
|
1081
|
+
sleep
|
1082
|
+
ensure
|
1083
|
+
buffer << 1 if Fiber.current.graceful_shutdown?
|
1084
|
+
end
|
1085
|
+
|
1086
|
+
f2 = spin do
|
1087
|
+
sleep
|
1088
|
+
ensure
|
1089
|
+
buffer << 2 if Fiber.current.graceful_shutdown?
|
1090
|
+
end
|
1091
|
+
|
1092
|
+
sleep
|
1093
|
+
ensure
|
1094
|
+
Fiber.current.terminate_all_children(true) if Fiber.current.graceful_shutdown?
|
1095
|
+
Fiber.current.await_all_children
|
1096
|
+
end
|
1097
|
+
|
1098
|
+
3.times { snooze }
|
1099
|
+
f0.terminate(true)
|
1100
|
+
f0.await
|
1101
|
+
|
1102
|
+
assert_equal [1, 2], buffer
|
1103
|
+
end
|
1104
|
+
end
|
data/test/test_global_api.rb
CHANGED
@@ -307,6 +307,36 @@ class SpinLoopTest < MiniTest::Test
|
|
307
307
|
f.stop
|
308
308
|
assert_in_range 1..3, counter
|
309
309
|
end
|
310
|
+
|
311
|
+
def test_spin_loop_break
|
312
|
+
i = 0
|
313
|
+
f = spin_loop do
|
314
|
+
i += 1
|
315
|
+
snooze
|
316
|
+
break if i >= 5
|
317
|
+
end
|
318
|
+
f.await
|
319
|
+
assert_equal 5, i
|
320
|
+
|
321
|
+
i = 0
|
322
|
+
f = spin_loop do
|
323
|
+
i += 1
|
324
|
+
snooze
|
325
|
+
raise StopIteration if i >= 5
|
326
|
+
end
|
327
|
+
f.await
|
328
|
+
assert_equal 5, i
|
329
|
+
end
|
330
|
+
|
331
|
+
def test_throttled_spin_loop_break
|
332
|
+
i = 0
|
333
|
+
f = spin_loop(rate: 100) do
|
334
|
+
i += 1
|
335
|
+
break if i >= 5
|
336
|
+
end
|
337
|
+
f.await
|
338
|
+
assert_equal 5, i
|
339
|
+
end
|
310
340
|
end
|
311
341
|
|
312
342
|
class SpinScopeTest < MiniTest::Test
|
data/test/test_io.rb
CHANGED
@@ -92,6 +92,32 @@ class IOTest < MiniTest::Test
|
|
92
92
|
assert_raises(EOFError) { i.readpartial(1) }
|
93
93
|
end
|
94
94
|
|
95
|
+
def test_gets
|
96
|
+
i, o = IO.pipe
|
97
|
+
|
98
|
+
buf = []
|
99
|
+
f = spin do
|
100
|
+
while (l = i.gets)
|
101
|
+
buf << l
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
snooze
|
106
|
+
assert_equal [], buf
|
107
|
+
|
108
|
+
o << 'fab'
|
109
|
+
snooze
|
110
|
+
assert_equal [], buf
|
111
|
+
|
112
|
+
o << "ulous\n"
|
113
|
+
10.times { snooze }
|
114
|
+
assert_equal ["fabulous\n"], buf
|
115
|
+
|
116
|
+
o.close
|
117
|
+
f.await
|
118
|
+
assert_equal ["fabulous\n"], buf
|
119
|
+
end
|
120
|
+
|
95
121
|
def test_getc
|
96
122
|
i, o = IO.pipe
|
97
123
|
|
data/test/test_socket.rb
CHANGED
data/test/test_supervise.rb
CHANGED
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.48.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:
|
11
|
+
date: 2021-01-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake-compiler
|
@@ -269,7 +269,7 @@ dependencies:
|
|
269
269
|
- !ruby/object:Gem::Version
|
270
270
|
version: 0.3.0
|
271
271
|
description:
|
272
|
-
email:
|
272
|
+
email: sharon@noteflakes.com
|
273
273
|
executables: []
|
274
274
|
extensions:
|
275
275
|
- ext/polyphony/extconf.rb
|
@@ -383,6 +383,7 @@ files:
|
|
383
383
|
- examples/io/raw.rb
|
384
384
|
- examples/io/reline.rb
|
385
385
|
- examples/io/system.rb
|
386
|
+
- examples/io/tcp_proxy.rb
|
386
387
|
- examples/io/tcpserver.rb
|
387
388
|
- examples/io/tcpsocket.rb
|
388
389
|
- examples/io/tunnel.rb
|
@@ -391,6 +392,8 @@ files:
|
|
391
392
|
- examples/performance/fiber_resume.rb
|
392
393
|
- examples/performance/fiber_transfer.rb
|
393
394
|
- examples/performance/fs_read.rb
|
395
|
+
- examples/performance/line_splitting.rb
|
396
|
+
- examples/performance/loop.rb
|
394
397
|
- examples/performance/mem-usage.rb
|
395
398
|
- examples/performance/messaging.rb
|
396
399
|
- examples/performance/multi_snooze.rb
|
@@ -433,7 +436,6 @@ files:
|
|
433
436
|
- ext/liburing/setup.c
|
434
437
|
- ext/liburing/syscall.c
|
435
438
|
- ext/liburing/syscall.h
|
436
|
-
- ext/polyphony/backend.h
|
437
439
|
- ext/polyphony/backend_common.h
|
438
440
|
- ext/polyphony/backend_io_uring.c
|
439
441
|
- ext/polyphony/backend_io_uring_context.c
|
data/ext/polyphony/backend.h
DELETED
@@ -1,26 +0,0 @@
|
|
1
|
-
#ifndef BACKEND_H
|
2
|
-
#define BACKEND_H
|
3
|
-
|
4
|
-
#include "ruby.h"
|
5
|
-
|
6
|
-
typedef VALUE (* backend_pending_count_t)(VALUE self);
|
7
|
-
typedef VALUE (*backend_poll_t)(VALUE self, VALUE nowait, VALUE current_fiber, VALUE runqueue);
|
8
|
-
typedef VALUE (* backend_ref_t)(VALUE self);
|
9
|
-
typedef int (* backend_ref_count_t)(VALUE self);
|
10
|
-
typedef void (* backend_reset_ref_count_t)(VALUE self);
|
11
|
-
typedef VALUE (* backend_unref_t)(VALUE self);
|
12
|
-
typedef VALUE (* backend_wait_event_t)(VALUE self, VALUE raise_on_exception);
|
13
|
-
typedef VALUE (* backend_wakeup_t)(VALUE self);
|
14
|
-
|
15
|
-
typedef struct backend_interface {
|
16
|
-
backend_pending_count_t pending_count;
|
17
|
-
backend_poll_t poll;
|
18
|
-
backend_ref_t ref;
|
19
|
-
backend_ref_count_t ref_count;
|
20
|
-
backend_reset_ref_count_t reset_ref_count;
|
21
|
-
backend_unref_t unref;
|
22
|
-
backend_wait_event_t wait_event;
|
23
|
-
backend_wakeup_t wakeup;
|
24
|
-
} backend_interface_t;
|
25
|
-
|
26
|
-
#endif /* BACKEND_H */
|