polyphony 0.47.5 → 0.49.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +25 -0
  3. data/Gemfile.lock +1 -1
  4. data/LICENSE +1 -1
  5. data/TODO.md +34 -17
  6. data/examples/io/tcp_proxy.rb +32 -0
  7. data/examples/performance/line_splitting.rb +34 -0
  8. data/examples/performance/loop.rb +32 -0
  9. data/examples/performance/thread-vs-fiber/polyphony_server.rb +6 -2
  10. data/ext/polyphony/backend_common.h +2 -2
  11. data/ext/polyphony/backend_io_uring.c +29 -68
  12. data/ext/polyphony/backend_libev.c +18 -59
  13. data/ext/polyphony/event.c +1 -1
  14. data/ext/polyphony/polyphony.c +0 -2
  15. data/ext/polyphony/polyphony.h +5 -4
  16. data/ext/polyphony/queue.c +2 -2
  17. data/ext/polyphony/thread.c +9 -28
  18. data/lib/polyphony.rb +2 -1
  19. data/lib/polyphony/adapters/postgres.rb +3 -3
  20. data/lib/polyphony/adapters/process.rb +2 -0
  21. data/lib/polyphony/core/global_api.rb +14 -2
  22. data/lib/polyphony/core/thread_pool.rb +3 -1
  23. data/lib/polyphony/core/throttler.rb +1 -1
  24. data/lib/polyphony/core/timer.rb +72 -0
  25. data/lib/polyphony/extensions/fiber.rb +28 -13
  26. data/lib/polyphony/extensions/io.rb +8 -14
  27. data/lib/polyphony/extensions/openssl.rb +4 -4
  28. data/lib/polyphony/extensions/socket.rb +5 -1
  29. data/lib/polyphony/extensions/thread.rb +1 -2
  30. data/lib/polyphony/net.rb +3 -6
  31. data/lib/polyphony/version.rb +1 -1
  32. data/polyphony.gemspec +1 -1
  33. data/test/helper.rb +2 -2
  34. data/test/test_backend.rb +26 -1
  35. data/test/test_fiber.rb +79 -1
  36. data/test/test_global_api.rb +30 -0
  37. data/test/test_io.rb +26 -0
  38. data/test/test_signal.rb +1 -2
  39. data/test/test_socket.rb +5 -5
  40. data/test/test_supervise.rb +1 -1
  41. data/test/test_timer.rb +124 -0
  42. metadata +8 -4
  43. data/ext/polyphony/backend.h +0 -26
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4343a37f26793e76528ea8e87287e2f3fbc4c8182064e820eedfc131d4308729
4
- data.tar.gz: 89273660342f647e3e016290ef48ac10f33b040a3ca6538b1fdd29aab13519da
3
+ metadata.gz: 18278f9c013191fd5a96520a07e3b6e5c4bc088665a1b1a769b02552bd0e5448
4
+ data.tar.gz: 4e1e42fa5e2a6ddc6dcff7982415dd16246a0d75212aa2eb9ae7a97e030a2bdb
5
5
  SHA512:
6
- metadata.gz: 7aa6b9f5e9ea61b682a112b30286b1e66f71088da67f96827d6415a6be12de4874de79ccd2236f36ba4da1bf5e9c078735bd07ac32520268c12cadfe60cd8565
7
- data.tar.gz: e564dfed776907921bdceafcdb47e78fe5655d345812f3484d7493df74f3485d09bf32e756c766cc45efe45591524c6a24d55c19530fa14ededbb4f481e80977
6
+ metadata.gz: 483158709045a9dc59fa75c912067c4fafbb02885e15f3fa57f482b97903a0c919abbb5ad19bea85837222a44d5ec2778f1e1421839c2e6a595a64e84f2d707d
7
+ data.tar.gz: c1162de70de5aa2d92804e7c4ccb3a372c5839d5600a7f68ae6f84363677a52f23923dbe8e407a84607ac4f0145736c7a5b9ada8b4956fd9c742cec182ed3a70
@@ -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`
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- polyphony (0.47.5)
4
+ polyphony (0.49.2)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2020 Sharon Rosner
3
+ Copyright (c) 2021 Sharon Rosner
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/TODO.md CHANGED
@@ -1,29 +1,46 @@
1
- - Graceful shutdown again:
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
- - Fiber API:
4
- - `Fiber#terminate(graceul)` - with a graceful flag
5
- - `Fiber#terminate_all_children(graceful)` - with a graceful flag
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
- And then we have:
16
+ - Add `Backend#splice`, `Backend#splice_loop` for implementing stuff like proxying:
13
17
 
14
18
  ```ruby
15
- spin do
16
- loop { do_some_stuff }
17
- ensure
18
- shutdown_gracefully if Fiber.current.graceful_shutdown?
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
- - More tight loops
25
- - IO#gets_loop, Socket#gets_loop, OpenSSL::Socket#gets_loop (medium effort)
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', 1234)
30
- puts "pid #{Process.pid} Polyphony (#{Thread.current.backend.kind}) listening on port 1234"
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->ref_count++;
73
+ backend->pending_count++;
74
74
  ret = Thread_switch_fiber(rb_thread_current());
75
- backend->ref_count--;
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->waiting_for_cqe = 0;
69
- backend->ref_count = 0;
70
- backend->run_no_wait_count = 0;
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 = 1024;
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->waiting_for_cqe = 0;
99
- backend->ref_count = 0;
100
- backend->run_no_wait_count = 0;
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
- VALUE Backend_ref(VALUE self) {
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->ref_count--;
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 == 0) return;
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->waiting_for_cqe = 1;
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->waiting_for_cqe = 0;
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->run_no_wait_count++;
230
- if (backend->run_no_wait_count < 10) return self;
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->run_no_wait_count < runnable_count) return self;
209
+ if (backend->poll_no_wait_count < runnable_count) return self;
234
210
  }
235
211
 
236
- backend->run_no_wait_count = 0;
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->waiting_for_cqe) {
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 == 0)
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 == 0) return Qnil;
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 == 0)
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 == 0) return Qnil;
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 == 0)
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