polyphony 0.47.5 → 0.49.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +25 -0
- data/Gemfile.lock +1 -1
- data/LICENSE +1 -1
- data/TODO.md +34 -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 -2
- data/ext/polyphony/backend_common.h +2 -2
- data/ext/polyphony/backend_io_uring.c +29 -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.rb +2 -1
- data/lib/polyphony/adapters/postgres.rb +3 -3
- data/lib/polyphony/adapters/process.rb +2 -0
- 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/core/timer.rb +72 -0
- data/lib/polyphony/extensions/fiber.rb +28 -13
- data/lib/polyphony/extensions/io.rb +8 -14
- data/lib/polyphony/extensions/openssl.rb +4 -4
- data/lib/polyphony/extensions/socket.rb +5 -1
- data/lib/polyphony/extensions/thread.rb +1 -2
- data/lib/polyphony/net.rb +3 -6
- data/lib/polyphony/version.rb +1 -1
- data/polyphony.gemspec +1 -1
- data/test/helper.rb +2 -2
- data/test/test_backend.rb +26 -1
- data/test/test_fiber.rb +79 -1
- data/test/test_global_api.rb +30 -0
- data/test/test_io.rb +26 -0
- data/test/test_signal.rb +1 -2
- data/test/test_socket.rb +5 -5
- data/test/test_supervise.rb +1 -1
- data/test/test_timer.rb +124 -0
- metadata +8 -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: 18278f9c013191fd5a96520a07e3b6e5c4bc088665a1b1a769b02552bd0e5448
|
4
|
+
data.tar.gz: 4e1e42fa5e2a6ddc6dcff7982415dd16246a0d75212aa2eb9ae7a97e030a2bdb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 483158709045a9dc59fa75c912067c4fafbb02885e15f3fa57f482b97903a0c919abbb5ad19bea85837222a44d5ec2778f1e1421839c2e6a595a64e84f2d707d
|
7
|
+
data.tar.gz: c1162de70de5aa2d92804e7c4ccb3a372c5839d5600a7f68ae6f84363677a52f23923dbe8e407a84607ac4f0145736c7a5b9ada8b4956fd9c742cec182ed3a70
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,28 @@
|
|
1
|
+
## 0.49.2
|
2
|
+
|
3
|
+
- Fix hang with 100s or more child fibers when terminating
|
4
|
+
- Fix double pending_count increment in io_uring backend
|
5
|
+
|
6
|
+
## 0.49.1
|
7
|
+
|
8
|
+
- Use `TCPSocket` instead of `Socket` in `Net.tcp_connect`
|
9
|
+
- Catch `Errno::ERSCH` in `Process.kill_and_await`
|
10
|
+
- Set io_uring queue size to 2048
|
11
|
+
|
12
|
+
## 0.49.0
|
13
|
+
|
14
|
+
- Implement `Polyphony::Timer` for performant timeouts
|
15
|
+
|
16
|
+
## 0.48.0
|
17
|
+
|
18
|
+
- Implement graceful shutdown
|
19
|
+
- Add support for `break` / `StopIteration` in `spin_loop`
|
20
|
+
- Fix `IO#gets`, `IO#readpartial`
|
21
|
+
|
22
|
+
## 0.47.5.1
|
23
|
+
|
24
|
+
- Add missing `Socket#accept_loop` method
|
25
|
+
|
1
26
|
## 0.47.5
|
2
27
|
|
3
28
|
- Add `socket_class` argument to `Backend#accept`, `Backend#accept_loop`
|
data/Gemfile.lock
CHANGED
data/LICENSE
CHANGED
data/TODO.md
CHANGED
@@ -1,29 +1,46 @@
|
|
1
|
-
-
|
1
|
+
- Check segfault when resetting a `cancel_after` timeout lots of times at very high rate
|
2
|
+
- Check why `throttled_loop` inside of `move_on_after` fails to stop
|
3
|
+
|
4
|
+
- Override stock `::SizedQueue` impl with Queue with capacity
|
5
|
+
|
6
|
+
- Add support for `break` and `StopIteration` in all loops (with tests)
|
7
|
+
|
8
|
+
- Change `IO#gets` to use `String#split` to cut into lines, much faster (see
|
9
|
+
examples/performance/line_splitting.rb)
|
2
10
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
- `Fiber#shutdown_all_children(graceful)` - with a graceful flag
|
11
|
+
- More tight loops
|
12
|
+
- `IO#gets_loop`, `Socket#gets_loop`, `OpenSSL::Socket#gets_loop` (medium effort)
|
13
|
+
- `Fiber#receive_loop` (very little effort, should be implemented in C)
|
7
14
|
|
8
|
-
- Set graceful termination in `@graceful_shutdown`
|
9
|
-
- Add `Fiber#graceful_shutdown?` method
|
10
|
-
- Returns `@graceful_shutdown`
|
11
15
|
|
12
|
-
|
16
|
+
- Add `Backend#splice`, `Backend#splice_loop` for implementing stuff like proxying:
|
13
17
|
|
14
18
|
```ruby
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
+
def two_way_proxy(socket1, socket2)
|
20
|
+
backend = Thread.current.backend
|
21
|
+
f1 = spin { backend.splice_loop(socket1, socket2) }
|
22
|
+
f2 = spin { backend.splice_loop(socket2, socket1) }
|
23
|
+
Fiber.await(f1, f2)
|
19
24
|
end
|
20
25
|
```
|
21
26
|
|
27
|
+
- Graceful shutdown again:
|
28
|
+
- What happens to children when doing a graceful shutdown?
|
29
|
+
- What are the implications of passing graceful shutdown flag to children?
|
30
|
+
- What about errors while doing a graceful shutdown?
|
31
|
+
- What about graceful restarts?
|
32
|
+
- Some interesting discussions:
|
33
|
+
- https://trio.discourse.group/search?q=graceful%20shutdown
|
34
|
+
- https://github.com/python-trio/trio/issues/147
|
35
|
+
- https://github.com/python-trio/trio/issues/143
|
36
|
+
- https://trio.discourse.group/t/graceful-shutdown/93/2
|
37
|
+
- https://250bpm.com/blog:146/
|
38
|
+
- https://www.rodrigoaraujo.me/posts/golang-pattern-graceful-shutdown-of-concurrent-events/
|
39
|
+
- https://github.com/tj/go-gracefully
|
22
40
|
- `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)
|
41
|
+
- A good use case is an HTTP server that on graceful shutdown:
|
42
|
+
- stops listening
|
43
|
+
- waits for all ongoing requests to finish, optionally with a timeout
|
27
44
|
|
28
45
|
## Roadmap for Polyphony 1.0
|
29
46
|
|
@@ -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
|
+
|
@@ -26,8 +26,12 @@ def write_response(socket)
|
|
26
26
|
socket.write "HTTP/1.1 #{status_code}\r\n#{headers}\r\n#{data}"
|
27
27
|
end
|
28
28
|
|
29
|
-
server = TCPServer.open('0.0.0.0',
|
30
|
-
puts "pid #{Process.pid} Polyphony (#{Thread.current.backend.kind}) listening on port
|
29
|
+
server = TCPServer.open('0.0.0.0', 4411)
|
30
|
+
puts "pid #{Process.pid} Polyphony (#{Thread.current.backend.kind}) listening on port 4411"
|
31
|
+
|
32
|
+
spin_loop(interval: 10) do
|
33
|
+
p Thread.current.fiber_scheduling_stats
|
34
|
+
end
|
31
35
|
|
32
36
|
server.accept_loop do |c|
|
33
37
|
spin { handle_client(c) }
|
@@ -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 = 2048;
|
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,10 @@ 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->ref_count++;
|
291
266
|
switchpoint_result = backend_await(backend);
|
292
|
-
backend->ref_count--;
|
293
267
|
|
294
268
|
if (!ctx->completed) {
|
295
269
|
ctx->result = -ECANCELED;
|
@@ -351,7 +325,7 @@ VALUE Backend_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof)
|
|
351
325
|
|
352
326
|
if (result < 0)
|
353
327
|
rb_syserr_fail(-result, strerror(-result));
|
354
|
-
else if (result
|
328
|
+
else if (!result)
|
355
329
|
break; // EOF
|
356
330
|
else {
|
357
331
|
total += result;
|
@@ -373,7 +347,7 @@ VALUE Backend_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof)
|
|
373
347
|
io_set_read_length(str, total, shrinkable);
|
374
348
|
io_enc_str(str, fptr);
|
375
349
|
|
376
|
-
if (total
|
350
|
+
if (!total) return Qnil;
|
377
351
|
|
378
352
|
return str;
|
379
353
|
}
|
@@ -410,7 +384,7 @@ VALUE Backend_read_loop(VALUE self, VALUE io) {
|
|
410
384
|
|
411
385
|
if (result < 0)
|
412
386
|
rb_syserr_fail(-result, strerror(-result));
|
413
|
-
else if (result
|
387
|
+
else if (!result)
|
414
388
|
break; // EOF
|
415
389
|
else {
|
416
390
|
total = result;
|
@@ -581,7 +555,7 @@ VALUE Backend_recv(VALUE self, VALUE io, VALUE str, VALUE length) {
|
|
581
555
|
io_set_read_length(str, total, shrinkable);
|
582
556
|
io_enc_str(str, fptr);
|
583
557
|
|
584
|
-
if (total
|
558
|
+
if (!total) return Qnil;
|
585
559
|
|
586
560
|
return str;
|
587
561
|
}
|
@@ -618,7 +592,7 @@ VALUE Backend_recv_loop(VALUE self, VALUE io) {
|
|
618
592
|
|
619
593
|
if (result < 0)
|
620
594
|
rb_syserr_fail(-result, strerror(-result));
|
621
|
-
else if (result
|
595
|
+
else if (!result)
|
622
596
|
break; // EOF
|
623
597
|
else {
|
624
598
|
total = result;
|
@@ -950,10 +924,6 @@ void Init_Backend() {
|
|
950
924
|
rb_define_method(cBackend, "initialize", Backend_initialize, 0);
|
951
925
|
rb_define_method(cBackend, "finalize", Backend_finalize, 0);
|
952
926
|
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
927
|
|
958
928
|
rb_define_method(cBackend, "poll", Backend_poll, 3);
|
959
929
|
rb_define_method(cBackend, "break", Backend_wakeup, 0);
|
@@ -977,15 +947,6 @@ void Init_Backend() {
|
|
977
947
|
rb_define_method(cBackend, "kind", Backend_kind, 0);
|
978
948
|
|
979
949
|
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
950
|
}
|
990
951
|
|
991
952
|
#endif // POLYPHONY_BACKEND_LIBURING
|