polyphony 0.47.5.1 → 0.48.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 +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 */
|