polyphony 0.47.4 → 0.49.1

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 +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