polyphony 0.47.1 → 0.47.5.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- polyphony (0.47.1)
4
+ polyphony (0.47.5.1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/TODO.md CHANGED
@@ -1,12 +1,41 @@
1
+ - Graceful shutdown again:
2
+
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
7
+
8
+ - Set graceful termination in `@graceful_shutdown`
9
+ - Add `Fiber#graceful_shutdown?` method
10
+ - Returns `@graceful_shutdown`
11
+
12
+ And then we have:
13
+
14
+ ```ruby
15
+ spin do
16
+ loop { do_some_stuff }
17
+ ensure
18
+ shutdown_gracefully if Fiber.current.graceful_shutdown?
19
+ end
20
+ ```
21
+
22
+ - `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)
27
+
1
28
  ## Roadmap for Polyphony 1.0
2
29
 
3
- - Check why worker-thread example doesn't work.
30
+ - check integration with rb-inotify
31
+
32
+ - Improve `#supervise`. It does not work as advertised, and seems to exhibit an
33
+ inconsistent behaviour (see supervisor example).
34
+
4
35
  - Add test that mimics the original design for Monocrono:
5
36
  - 256 fibers each waiting for a message
6
37
  - When message received do some blocking work using a `ThreadPool`
7
38
  - Send messages, collect responses, check for correctness
8
- - Improve `#supervise`. It does not work as advertised, and seems to exhibit an
9
- inconsistent behaviour (see supervisor example).
10
39
 
11
40
  - io_uring
12
41
  - Use playground.c to find out why we when submitting and waiting for
@@ -9,9 +9,9 @@ def my_sleep(t)
9
9
  puts "#{t} done"
10
10
  end
11
11
 
12
- spin { my_sleep(1) }
13
- spin { my_sleep(2) }
14
- spin { my_sleep(3) }
12
+ spin { my_sleep(0.1) }
13
+ spin { my_sleep(0.2) }
14
+ spin { my_sleep(0.3) }
15
15
  spin { puts "fiber count: #{Fiber.current.children.count}" }
16
16
  snooze
17
17
 
@@ -13,11 +13,9 @@ end
13
13
  $worker = Thread.new do
14
14
  Fiber.current.tag = :worker
15
15
  loop do
16
- client, block = receive
16
+ (client, block) = receive
17
17
  do_work(client, &block)
18
18
  end
19
- rescue Exception => e
20
- p e
21
19
  end
22
20
 
23
21
  def process(&block)
@@ -27,4 +25,5 @@ end
27
25
 
28
26
  sleep 0.1
29
27
 
30
- p process { 1 + 1 }
28
+ p process { 1 + 1 }
29
+ p process { 42 ** 2 }
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+ require 'fileutils'
6
+
7
+ unix_path = '/tmp/polyphony-unix-socket'
8
+
9
+ FileUtils.rm unix_path rescue nil
10
+ server = UNIXServer.new(unix_path)
11
+ spin do
12
+ server.accept_loop do |socket|
13
+ p [:accept, socket]
14
+ spin do
15
+ while (line = socket.gets)
16
+ socket.puts line
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ snooze
23
+ client = UNIXSocket.new('/tmp/polyphony-unix-socket')
24
+ p [:connected, client]
25
+ client.puts 'hello!'
26
+ p client.gets
@@ -27,7 +27,6 @@ static int pidfd_open(pid_t pid, unsigned int flags) {
27
27
  return syscall(__NR_pidfd_open, pid, flags);
28
28
  }
29
29
 
30
- VALUE cTCPSocket;
31
30
  VALUE SYM_io_uring;
32
31
 
33
32
  typedef struct Backend_t {
@@ -669,15 +668,15 @@ VALUE Backend_send(VALUE self, VALUE io, VALUE str) {
669
668
  return INT2NUM(len);
670
669
  }
671
670
 
672
- VALUE io_uring_backend_accept(Backend_t *backend, VALUE sock, int loop) {
671
+ VALUE io_uring_backend_accept(Backend_t *backend, VALUE server_socket, VALUE socket_class, int loop) {
673
672
  rb_io_t *fptr;
674
673
  struct sockaddr addr;
675
674
  socklen_t len = (socklen_t)sizeof addr;
676
- VALUE underlying_sock = rb_ivar_get(sock, ID_ivar_io);
677
675
  VALUE socket = Qnil;
678
- if (underlying_sock != Qnil) sock = underlying_sock;
676
+ VALUE underlying_sock = rb_ivar_get(server_socket, ID_ivar_io);
677
+ if (underlying_sock != Qnil) server_socket = underlying_sock;
679
678
 
680
- GetOpenFile(sock, fptr);
679
+ GetOpenFile(server_socket, fptr);
681
680
  while (1) {
682
681
  VALUE resume_value = Qnil;
683
682
  op_context_t *ctx = OP_CONTEXT_ACQUIRE(&backend->store, OP_ACCEPT);
@@ -695,7 +694,7 @@ VALUE io_uring_backend_accept(Backend_t *backend, VALUE sock, int loop) {
695
694
  else {
696
695
  rb_io_t *fp;
697
696
 
698
- socket = rb_obj_alloc(cTCPSocket);
697
+ socket = rb_obj_alloc(socket_class);
699
698
  MakeOpenFile(socket, fp);
700
699
  rb_update_max_fd(fd);
701
700
  fp->fd = fd;
@@ -718,16 +717,16 @@ VALUE io_uring_backend_accept(Backend_t *backend, VALUE sock, int loop) {
718
717
  return Qnil;
719
718
  }
720
719
 
721
- VALUE Backend_accept(VALUE self, VALUE sock) {
720
+ VALUE Backend_accept(VALUE self, VALUE server_socket, VALUE socket_class) {
722
721
  Backend_t *backend;
723
722
  GetBackend(self, backend);
724
- return io_uring_backend_accept(backend, sock, 0);
723
+ return io_uring_backend_accept(backend, server_socket, socket_class, 0);
725
724
  }
726
725
 
727
- VALUE Backend_accept_loop(VALUE self, VALUE sock) {
726
+ VALUE Backend_accept_loop(VALUE self, VALUE server_socket, VALUE socket_class) {
728
727
  Backend_t *backend;
729
728
  GetBackend(self, backend);
730
- io_uring_backend_accept(backend, sock, 1);
729
+ io_uring_backend_accept(backend, server_socket, socket_class, 1);
731
730
  return self;
732
731
  }
733
732
 
@@ -945,9 +944,6 @@ VALUE Backend_kind(VALUE self) {
945
944
  }
946
945
 
947
946
  void Init_Backend() {
948
- rb_require("socket");
949
- cTCPSocket = rb_const_get(rb_cObject, rb_intern("TCPSocket"));
950
-
951
947
  VALUE cBackend = rb_define_class_under(mPolyphony, "Backend", rb_cData);
952
948
  rb_define_alloc_func(cBackend, Backend_allocate);
953
949
 
@@ -968,8 +964,8 @@ void Init_Backend() {
968
964
  rb_define_method(cBackend, "recv", Backend_recv, 3);
969
965
  rb_define_method(cBackend, "recv_loop", Backend_recv_loop, 1);
970
966
  rb_define_method(cBackend, "send", Backend_send, 2);
971
- rb_define_method(cBackend, "accept", Backend_accept, 1);
972
- rb_define_method(cBackend, "accept_loop", Backend_accept_loop, 1);
967
+ rb_define_method(cBackend, "accept", Backend_accept, 2);
968
+ rb_define_method(cBackend, "accept_loop", Backend_accept_loop, 2);
973
969
  rb_define_method(cBackend, "connect", Backend_connect, 3);
974
970
  rb_define_method(cBackend, "wait_io", Backend_wait_io, 2);
975
971
  rb_define_method(cBackend, "sleep", Backend_sleep, 1);
@@ -13,7 +13,6 @@
13
13
  #include "../libev/ev.h"
14
14
  #include "ruby/io.h"
15
15
 
16
- VALUE cTCPSocket;
17
16
  VALUE SYM_libev;
18
17
 
19
18
  ID ID_ivar_is_nonblocking;
@@ -494,7 +493,7 @@ VALUE Backend_write_m(int argc, VALUE *argv, VALUE self) {
494
493
  Backend_writev(self, argv[0], argc - 1, argv + 1);
495
494
  }
496
495
 
497
- VALUE Backend_accept(VALUE self, VALUE sock) {
496
+ VALUE Backend_accept(VALUE self, VALUE server_socket, VALUE socket_class) {
498
497
  Backend_t *backend;
499
498
  struct libev_io watcher;
500
499
  rb_io_t *fptr;
@@ -502,12 +501,12 @@ VALUE Backend_accept(VALUE self, VALUE sock) {
502
501
  struct sockaddr addr;
503
502
  socklen_t len = (socklen_t)sizeof addr;
504
503
  VALUE switchpoint_result = Qnil;
505
- VALUE underlying_sock = rb_ivar_get(sock, ID_ivar_io);
506
- if (underlying_sock != Qnil) sock = underlying_sock;
504
+ VALUE underlying_sock = rb_ivar_get(server_socket, ID_ivar_io);
505
+ if (underlying_sock != Qnil) server_socket = underlying_sock;
507
506
 
508
507
  GetBackend(self, backend);
509
- GetOpenFile(sock, fptr);
510
- io_set_nonblock(fptr, sock);
508
+ GetOpenFile(server_socket, fptr);
509
+ io_set_nonblock(fptr, server_socket);
511
510
  watcher.fiber = Qnil;
512
511
  while (1) {
513
512
  fd = accept(fptr->fd, &addr, &len);
@@ -529,7 +528,7 @@ VALUE Backend_accept(VALUE self, VALUE sock) {
529
528
  goto error;
530
529
  }
531
530
 
532
- socket = rb_obj_alloc(cTCPSocket);
531
+ socket = rb_obj_alloc(socket_class);
533
532
  MakeOpenFile(socket, fp);
534
533
  rb_update_max_fd(fd);
535
534
  fp->fd = fd;
@@ -550,7 +549,7 @@ error:
550
549
  return RAISE_EXCEPTION(switchpoint_result);
551
550
  }
552
551
 
553
- VALUE Backend_accept_loop(VALUE self, VALUE sock) {
552
+ VALUE Backend_accept_loop(VALUE self, VALUE server_socket, VALUE socket_class) {
554
553
  Backend_t *backend;
555
554
  struct libev_io watcher;
556
555
  rb_io_t *fptr;
@@ -559,12 +558,12 @@ VALUE Backend_accept_loop(VALUE self, VALUE sock) {
559
558
  socklen_t len = (socklen_t)sizeof addr;
560
559
  VALUE switchpoint_result = Qnil;
561
560
  VALUE socket = Qnil;
562
- VALUE underlying_sock = rb_ivar_get(sock, ID_ivar_io);
563
- if (underlying_sock != Qnil) sock = underlying_sock;
561
+ VALUE underlying_sock = rb_ivar_get(server_socket, ID_ivar_io);
562
+ if (underlying_sock != Qnil) server_socket = underlying_sock;
564
563
 
565
564
  GetBackend(self, backend);
566
- GetOpenFile(sock, fptr);
567
- io_set_nonblock(fptr, sock);
565
+ GetOpenFile(server_socket, fptr);
566
+ io_set_nonblock(fptr, server_socket);
568
567
  watcher.fiber = Qnil;
569
568
 
570
569
  while (1) {
@@ -586,7 +585,7 @@ VALUE Backend_accept_loop(VALUE self, VALUE sock) {
586
585
  goto error;
587
586
  }
588
587
 
589
- socket = rb_obj_alloc(cTCPSocket);
588
+ socket = rb_obj_alloc(socket_class);
590
589
  MakeOpenFile(socket, fp);
591
590
  rb_update_max_fd(fd);
592
591
  fp->fd = fd;
@@ -849,9 +848,6 @@ VALUE Backend_kind(VALUE self) {
849
848
  void Init_Backend() {
850
849
  ev_set_allocator(xrealloc);
851
850
 
852
- rb_require("socket");
853
- cTCPSocket = rb_const_get(rb_cObject, rb_intern("TCPSocket"));
854
-
855
851
  VALUE cBackend = rb_define_class_under(mPolyphony, "Backend", rb_cData);
856
852
  rb_define_alloc_func(cBackend, Backend_allocate);
857
853
 
@@ -869,8 +865,8 @@ void Init_Backend() {
869
865
  rb_define_method(cBackend, "read", Backend_read, 4);
870
866
  rb_define_method(cBackend, "read_loop", Backend_read_loop, 1);
871
867
  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);
868
+ rb_define_method(cBackend, "accept", Backend_accept, 2);
869
+ rb_define_method(cBackend, "accept_loop", Backend_accept_loop, 2);
874
870
  rb_define_method(cBackend, "connect", Backend_connect, 3);
875
871
  rb_define_method(cBackend, "recv", Backend_recv, 3);
876
872
  rb_define_method(cBackend, "recv_loop", Backend_read_loop, 1);
@@ -53,10 +53,10 @@ module Polyphony
53
53
  Fiber.current.spin(tag, caller, &block)
54
54
  end
55
55
 
56
- def spin_loop(tag = nil, rate: nil, &block)
57
- if rate
56
+ def spin_loop(tag = nil, rate: nil, interval: nil, &block)
57
+ if rate || interval
58
58
  Fiber.current.spin(tag, caller) do
59
- throttled_loop(rate, &block)
59
+ throttled_loop(rate: rate, interval: interval, &block)
60
60
  end
61
61
  else
62
62
  Fiber.current.spin(tag, caller) { loop(&block) }
@@ -73,17 +73,8 @@ module Polyphony
73
73
  end.await
74
74
  end
75
75
 
76
- def every(interval)
77
- next_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + interval
78
- loop do
79
- now = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
80
- Thread.current.backend.sleep(next_time - now)
81
- yield
82
- loop do
83
- next_time += interval
84
- break if next_time > now
85
- end
86
- end
76
+ def every(interval, &block)
77
+ Thread.current.backend.timer_loop(interval, &block)
87
78
  end
88
79
 
89
80
  def move_on_after(interval, with_value: nil, &block)
@@ -58,18 +58,7 @@ module Polyphony
58
58
  # Discards the currently-acquired resource
59
59
  # instead of returning it to the pool when done.
60
60
  def discard!
61
- if block_given?
62
- @size.times do
63
- acquire do |r|
64
- next if yield(r)
65
-
66
- @size -= 1
67
- @acquired_resources.delete(Fiber.current)
68
- end
69
- end
70
- else
71
- @size -= 1 if @acquired_resources.delete(Fiber.current)
72
- end
61
+ @size -= 1 if @acquired_resources.delete(Fiber.current)
73
62
  end
74
63
 
75
64
  def preheat!
@@ -67,19 +67,29 @@ module Polyphony
67
67
  self << fiber unless result.is_a?(Exception)
68
68
  end
69
69
  loop { supervise_perform(opts) }
70
+ rescue Polyphony::MoveOn
71
+ # generated in #supervise_perform to stop supervisor
70
72
  ensure
71
73
  @on_child_done = nil
72
74
  end
73
75
 
74
76
  def supervise_perform(opts)
75
77
  fiber = receive
76
- restart_fiber(fiber, opts) if fiber
78
+ if fiber && opts[:restart]
79
+ restart_fiber(fiber, opts)
80
+ elsif Fiber.current.children.empty?
81
+ Fiber.current.stop
82
+ end
77
83
  rescue Polyphony::Restart
78
84
  restart_all_children
79
85
  rescue Exception => e
80
86
  Kernel.raise e if e.source_fiber.nil? || e.source_fiber == self
81
87
 
82
- restart_fiber(e.source_fiber, opts)
88
+ if opts[:restart]
89
+ restart_fiber(e.source_fiber, opts)
90
+ elsif Fiber.current.children.empty?
91
+ Fiber.current.stop
92
+ end
83
93
  end
84
94
 
85
95
  def restart_fiber(fiber, opts)
@@ -173,6 +183,7 @@ module Polyphony
173
183
  # signals (see also the patched `Kernel#trap`)
174
184
  def schedule_priority_oob_fiber(&block)
175
185
  f = Fiber.new do
186
+ Fiber.current.setup_raw
176
187
  block.call
177
188
  rescue Exception => e
178
189
  Thread.current.schedule_and_wakeup(Thread.main.main_fiber, e)
@@ -262,6 +273,7 @@ module Polyphony
262
273
  # allows the fiber to be scheduled and to receive messages.
263
274
  def setup_raw
264
275
  @thread = Thread.current
276
+ @running = true
265
277
  end
266
278
 
267
279
  def setup_main_fiber
@@ -8,7 +8,11 @@ require_relative '../core/thread_pool'
8
8
  # Socket overrides (eventually rewritten in C)
9
9
  class ::Socket
10
10
  def accept
11
- Thread.current.backend.accept(self)
11
+ Thread.current.backend.accept(self, TCPSocket)
12
+ end
13
+
14
+ def accept_loop(&block)
15
+ Thread.current.backend.accept_loop(self, TCPSocket, &block)
12
16
  end
13
17
 
14
18
  NO_EXCEPTION = { exception: false }.freeze
@@ -19,17 +23,7 @@ class ::Socket
19
23
  end
20
24
 
21
25
  def recv(maxlen, flags = 0, outbuf = nil)
22
- Thread.current.backend.recv(self, buf || +'', maxlen)
23
- # outbuf ||= +''
24
- # loop do
25
- # result = recv_nonblock(maxlen, flags, outbuf, **NO_EXCEPTION)
26
- # case result
27
- # when nil then raise IOError
28
- # when :wait_readable then Thread.current.backend.wait_io(self, false)
29
- # else
30
- # return result
31
- # end
32
- # end
26
+ Thread.current.backend.recv(self, outbuf || +'', maxlen)
33
27
  end
34
28
 
35
29
  def recv_loop(&block)
@@ -144,19 +138,24 @@ class ::TCPSocket
144
138
  end
145
139
 
146
140
  def recv(maxlen, flags = 0, outbuf = nil)
147
- Thread.current.backend.recv(self, buf || +'', maxlen)
141
+ Thread.current.backend.recv(self, outbuf || +'', maxlen)
148
142
  end
149
143
 
150
144
  def recv_loop(&block)
151
145
  Thread.current.backend.recv_loop(self, &block)
152
146
  end
147
+ alias_method :read_loop, :recv_loop
153
148
 
154
149
  def send(mesg, flags = 0)
155
150
  Thread.current.backend.send(self, mesg)
156
151
  end
157
152
 
158
- def write(str)
159
- Thread.current.backend.send(self, str)
153
+ def write(str, *args)
154
+ if args.empty?
155
+ Thread.current.backend.send(self, str)
156
+ else
157
+ Thread.current.backend.send(self, str + args.join)
158
+ end
160
159
  end
161
160
  alias_method :<<, :write
162
161
 
@@ -193,11 +192,12 @@ class ::TCPServer
193
192
 
194
193
  alias_method :orig_accept, :accept
195
194
  def accept
196
- @io.accept
195
+ Thread.current.backend.accept(@io, TCPSocket)
196
+ # @io.accept
197
197
  end
198
198
 
199
199
  def accept_loop(&block)
200
- Thread.current.backend.accept_loop(@io, &block)
200
+ Thread.current.backend.accept_loop(@io, TCPSocket, &block)
201
201
  end
202
202
 
203
203
  alias_method :orig_close, :close
@@ -205,3 +205,60 @@ class ::TCPServer
205
205
  @io.close
206
206
  end
207
207
  end
208
+
209
+ class ::UNIXServer
210
+ alias_method :orig_accept, :accept
211
+ def accept
212
+ Thread.current.backend.accept(self, UNIXSocket)
213
+ end
214
+
215
+ def accept_loop(&block)
216
+ Thread.current.backend.accept_loop(self, UNIXSocket, &block)
217
+ end
218
+ end
219
+
220
+ class ::UNIXSocket
221
+ def recv(maxlen, flags = 0, outbuf = nil)
222
+ Thread.current.backend.recv(self, outbuf || +'', maxlen)
223
+ end
224
+
225
+ def recv_loop(&block)
226
+ Thread.current.backend.recv_loop(self, &block)
227
+ end
228
+ alias_method :read_loop, :recv_loop
229
+
230
+ def send(mesg, flags = 0)
231
+ Thread.current.backend.send(self, mesg)
232
+ end
233
+
234
+ def write(str, *args)
235
+ if args.empty?
236
+ Thread.current.backend.send(self, str)
237
+ else
238
+ Thread.current.backend.send(self, str + args.join)
239
+ end
240
+ end
241
+ alias_method :<<, :write
242
+
243
+ def readpartial(maxlen, str = nil)
244
+ @read_buffer ||= +''
245
+ result = Thread.current.backend.recv(self, @read_buffer, maxlen)
246
+ raise EOFError unless result
247
+
248
+ if str
249
+ str << @read_buffer
250
+ else
251
+ str = @read_buffer
252
+ end
253
+ @read_buffer = +''
254
+ str
255
+ end
256
+
257
+ def read_nonblock(len, str = nil, exception: true)
258
+ @io.read_nonblock(len, str, exception: exception)
259
+ end
260
+
261
+ def write_nonblock(buf, exception: true)
262
+ @io.write_nonblock(buf, exception: exception)
263
+ end
264
+ end