polyphony 0.47.4 → 0.49.1

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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +321 -296
  3. data/Gemfile.lock +1 -1
  4. data/LICENSE +1 -1
  5. data/TODO.md +38 -29
  6. data/examples/core/supervisor.rb +3 -3
  7. data/examples/core/worker-thread.rb +3 -4
  8. data/examples/io/tcp_proxy.rb +32 -0
  9. data/examples/performance/line_splitting.rb +34 -0
  10. data/examples/performance/loop.rb +32 -0
  11. data/examples/performance/thread-vs-fiber/polyphony_server.rb +6 -0
  12. data/ext/polyphony/backend_common.h +2 -22
  13. data/ext/polyphony/backend_io_uring.c +38 -78
  14. data/ext/polyphony/backend_libev.c +22 -67
  15. data/ext/polyphony/event.c +1 -1
  16. data/ext/polyphony/polyphony.c +0 -2
  17. data/ext/polyphony/polyphony.h +5 -4
  18. data/ext/polyphony/queue.c +2 -2
  19. data/ext/polyphony/thread.c +9 -28
  20. data/lib/polyphony.rb +2 -1
  21. data/lib/polyphony/adapters/postgres.rb +3 -3
  22. data/lib/polyphony/adapters/process.rb +2 -0
  23. data/lib/polyphony/core/global_api.rb +14 -2
  24. data/lib/polyphony/core/thread_pool.rb +3 -1
  25. data/lib/polyphony/core/throttler.rb +1 -1
  26. data/lib/polyphony/core/timer.rb +72 -0
  27. data/lib/polyphony/extensions/fiber.rb +32 -8
  28. data/lib/polyphony/extensions/io.rb +8 -14
  29. data/lib/polyphony/extensions/openssl.rb +4 -4
  30. data/lib/polyphony/extensions/socket.rb +13 -10
  31. data/lib/polyphony/net.rb +3 -6
  32. data/lib/polyphony/version.rb +1 -1
  33. data/polyphony.gemspec +1 -1
  34. data/test/helper.rb +1 -0
  35. data/test/test_backend.rb +1 -1
  36. data/test/test_fiber.rb +64 -1
  37. data/test/test_global_api.rb +30 -0
  38. data/test/test_io.rb +26 -0
  39. data/test/test_socket.rb +32 -6
  40. data/test/test_supervise.rb +2 -1
  41. data/test/test_timer.rb +124 -0
  42. metadata +8 -4
  43. data/ext/polyphony/backend.h +0 -26
@@ -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->running = 0;
87
- backend->ref_count = 0;
88
- backend->run_no_wait_count = 0;
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
- VALUE Backend_ref(VALUE self) {
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) {
122
+ unsigned int Backend_pending_count(VALUE self) {
136
123
  Backend_t *backend;
137
124
  GetBackend(self, backend);
138
125
 
139
- return backend->ref_count;
140
- }
141
-
142
- void Backend_reset_ref_count(VALUE self) {
143
- Backend_t *backend;
144
- GetBackend(self, backend);
145
-
146
- backend->ref_count = 0;
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->run_no_wait_count++;
164
- if (backend->run_no_wait_count < 10) return self;
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->run_no_wait_count < runnable_count) return self;
139
+ if (backend->poll_no_wait_count < runnable_count) return self;
168
140
  }
169
141
 
170
- backend->run_no_wait_count = 0;
142
+ backend->poll_no_wait_count = 0;
171
143
 
172
144
  COND_TRACE(2, SYM_fiber_event_poll_enter, current_fiber);
173
- backend->running = 1;
145
+ backend->currently_polling = 1;
174
146
  ev_run(backend->ev_loop, is_nowait ? EVRUN_NOWAIT : EVRUN_ONCE);
175
- backend->running = 0;
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->running) {
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
@@ -493,7 +465,7 @@ VALUE Backend_write_m(int argc, VALUE *argv, VALUE self) {
493
465
  Backend_writev(self, argv[0], argc - 1, argv + 1);
494
466
  }
495
467
 
496
- VALUE Backend_accept(VALUE self, VALUE server_socket) {
468
+ VALUE Backend_accept(VALUE self, VALUE server_socket, VALUE socket_class) {
497
469
  Backend_t *backend;
498
470
  struct libev_io watcher;
499
471
  rb_io_t *fptr;
@@ -501,7 +473,6 @@ VALUE Backend_accept(VALUE self, VALUE server_socket) {
501
473
  struct sockaddr addr;
502
474
  socklen_t len = (socklen_t)sizeof addr;
503
475
  VALUE switchpoint_result = Qnil;
504
- VALUE socket_class = ConnectionSocketClass(server_socket);
505
476
  VALUE underlying_sock = rb_ivar_get(server_socket, ID_ivar_io);
506
477
  if (underlying_sock != Qnil) server_socket = underlying_sock;
507
478
 
@@ -550,7 +521,7 @@ error:
550
521
  return RAISE_EXCEPTION(switchpoint_result);
551
522
  }
552
523
 
553
- VALUE Backend_accept_loop(VALUE self, VALUE server_socket) {
524
+ VALUE Backend_accept_loop(VALUE self, VALUE server_socket, VALUE socket_class) {
554
525
  Backend_t *backend;
555
526
  struct libev_io watcher;
556
527
  rb_io_t *fptr;
@@ -559,7 +530,6 @@ VALUE Backend_accept_loop(VALUE self, VALUE server_socket) {
559
530
  socklen_t len = (socklen_t)sizeof addr;
560
531
  VALUE switchpoint_result = Qnil;
561
532
  VALUE socket = Qnil;
562
- VALUE socket_class = ConnectionSocketClass(server_socket);
563
533
  VALUE underlying_sock = rb_ivar_get(server_socket, ID_ivar_io);
564
534
  if (underlying_sock != Qnil) server_socket = underlying_sock;
565
535
 
@@ -850,18 +820,12 @@ VALUE Backend_kind(VALUE self) {
850
820
  void Init_Backend() {
851
821
  ev_set_allocator(xrealloc);
852
822
 
853
- Init_SocketClasses();
854
-
855
823
  VALUE cBackend = rb_define_class_under(mPolyphony, "Backend", rb_cData);
856
824
  rb_define_alloc_func(cBackend, Backend_allocate);
857
825
 
858
826
  rb_define_method(cBackend, "initialize", Backend_initialize, 0);
859
827
  rb_define_method(cBackend, "finalize", Backend_finalize, 0);
860
828
  rb_define_method(cBackend, "post_fork", Backend_post_fork, 0);
861
- rb_define_method(cBackend, "pending_count", Backend_pending_count, 0);
862
-
863
- rb_define_method(cBackend, "ref", Backend_ref, 0);
864
- rb_define_method(cBackend, "unref", Backend_unref, 0);
865
829
 
866
830
  rb_define_method(cBackend, "poll", Backend_poll, 3);
867
831
  rb_define_method(cBackend, "break", Backend_wakeup, 0);
@@ -869,8 +833,8 @@ void Init_Backend() {
869
833
  rb_define_method(cBackend, "read", Backend_read, 4);
870
834
  rb_define_method(cBackend, "read_loop", Backend_read_loop, 1);
871
835
  rb_define_method(cBackend, "write", Backend_write_m, -1);
872
- rb_define_method(cBackend, "accept", Backend_accept, 1);
873
- rb_define_method(cBackend, "accept_loop", Backend_accept_loop, 1);
836
+ rb_define_method(cBackend, "accept", Backend_accept, 2);
837
+ rb_define_method(cBackend, "accept_loop", Backend_accept_loop, 2);
874
838
  rb_define_method(cBackend, "connect", Backend_connect, 3);
875
839
  rb_define_method(cBackend, "recv", Backend_recv, 3);
876
840
  rb_define_method(cBackend, "recv_loop", Backend_read_loop, 1);
@@ -886,15 +850,6 @@ void Init_Backend() {
886
850
 
887
851
  ID_ivar_is_nonblocking = rb_intern("@is_nonblocking");
888
852
  SYM_libev = ID2SYM(rb_intern("libev"));
889
-
890
- __BACKEND__.pending_count = Backend_pending_count;
891
- __BACKEND__.poll = Backend_poll;
892
- __BACKEND__.ref = Backend_ref;
893
- __BACKEND__.ref_count = Backend_ref_count;
894
- __BACKEND__.reset_ref_count = Backend_reset_ref_count;
895
- __BACKEND__.unref = Backend_unref;
896
- __BACKEND__.wait_event = Backend_wait_event;
897
- __BACKEND__.wakeup = Backend_wakeup;
898
853
  }
899
854
 
900
855
  #endif // POLYPHONY_BACKEND_LIBEV
@@ -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 = __BACKEND__.wait_event(backend, Qnil);
69
+ VALUE switchpoint_result = Backend_wait_event(backend, Qnil);
70
70
  event->waiting_fiber = Qnil;
71
71
 
72
72
  RAISE_IF_EXCEPTION(switchpoint_result);
@@ -22,8 +22,6 @@ ID ID_R;
22
22
  ID ID_W;
23
23
  ID ID_RW;
24
24
 
25
- backend_interface_t backend_interface;
26
-
27
25
  VALUE Polyphony_snooze(VALUE self) {
28
26
  VALUE ret;
29
27
  VALUE fiber = rb_fiber_current();
@@ -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);
@@ -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 = __BACKEND__.wait_event(backend, Qnil);
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 = __BACKEND__.wait_event(backend, Qnil);
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);
@@ -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 = __BACKEND__.pending_count(backend);
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
- __BACKEND__.wakeup(backend);
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 ref_count;
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 == 0 && ref_count > 0) {
86
+ if (!backend_was_polled && pending_count) {
98
87
  // this prevents event starvation in case the run queue never empties
99
- __BACKEND__.poll(backend, Qtrue, current_fiber, runqueue);
88
+ Backend_poll(backend, Qtrue, current_fiber, runqueue);
100
89
  }
101
90
  break;
102
91
  }
103
- if (ref_count == 0) break;
92
+ if (pending_count == 0) break;
104
93
 
105
- __BACKEND__.poll(backend, Qnil, current_fiber, runqueue);
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 (__BACKEND__.wakeup(backend) == Qnil) {
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
 
@@ -14,6 +14,7 @@ Thread.current.backend = Polyphony::Backend.new
14
14
  require_relative './polyphony/core/global_api'
15
15
  require_relative './polyphony/core/resource_pool'
16
16
  require_relative './polyphony/core/sync'
17
+ require_relative './polyphony/core/timer'
17
18
  require_relative './polyphony/net'
18
19
  require_relative './polyphony/adapters/process'
19
20
 
@@ -43,7 +44,7 @@ module Polyphony
43
44
  rescue SystemExit
44
45
  # fall through to ensure
45
46
  rescue Exception => e
46
- warn e.full_message
47
+ STDERR << e.full_message
47
48
  exit!
48
49
  ensure
49
50
  exit_forked_process
@@ -11,7 +11,7 @@ module ::PG
11
11
 
12
12
  def self.connect_async(conn)
13
13
  socket_io = conn.socket_io
14
- loop do
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
- loop do
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
- loop do
99
+ while true
100
100
  Thread.current.backend.wait_io(socket_io, false)
101
101
  consume_input
102
102
  notice = notifies
@@ -24,6 +24,8 @@ module Polyphony
24
24
  def kill_and_await(sig, pid)
25
25
  ::Process.kill(sig, pid)
26
26
  Thread.current.backend.waitpid(pid)
27
+ rescue Errno::ERSCH
28
+ # ignore
27
29
  end
28
30
  end
29
31
  end
@@ -59,7 +59,15 @@ module Polyphony
59
59
  throttled_loop(rate: rate, interval: interval, &block)
60
60
  end
61
61
  else
62
- Fiber.current.spin(tag, caller) { loop(&block) }
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
- loop { throttler.(&block) }
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
@@ -45,7 +45,9 @@ module Polyphony
45
45
  end
46
46
 
47
47
  def thread_loop
48
- loop { run_queued_task }
48
+ while true
49
+ run_queued_task
50
+ end
49
51
  end
50
52
 
51
53
  def run_queued_task
@@ -15,7 +15,7 @@ module Polyphony
15
15
  Thread.current.backend.sleep(delta) if delta > 0
16
16
  yield self
17
17
 
18
- loop do
18
+ while true
19
19
  @next_time += @min_dt
20
20
  break if @next_time > now
21
21
  end