polyphony 0.47.1 → 0.47.5.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.
@@ -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