polyphony 1.4 → 1.5

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.
@@ -42,21 +42,24 @@ thread.
42
42
  #define _GNU_SOURCE 1
43
43
  #endif
44
44
 
45
- #include <netdb.h>
46
- #include <sys/socket.h>
47
- #include <sys/uio.h>
48
45
  #include <unistd.h>
49
- #include <netinet/in.h>
50
- #include <arpa/inet.h>
51
46
  #include <stdnoreturn.h>
52
47
  #include <sys/types.h>
48
+
49
+ #ifdef POLYPHONY_USE_PIDFD_OPEN
53
50
  #include <sys/wait.h>
51
+ #endif
52
+
54
53
  #include <fcntl.h>
55
54
 
56
55
  #include "polyphony.h"
57
56
  #include "../libev/ev.h"
58
57
  #include "ruby/io.h"
59
58
 
59
+ #ifndef POLYPHONY_WINDOWS
60
+ #include <sys/uio.h>
61
+ #endif
62
+
60
63
  #include "../libev/ev.h"
61
64
  #include "backend_common.h"
62
65
 
@@ -267,21 +270,25 @@ VALUE libev_wait_fd(Backend_t *backend, int fd, int events, int raise_exception)
267
270
  }
268
271
 
269
272
  static inline int fd_from_io(VALUE io, rb_io_t **fptr, int write_mode, int rectify_file_pos) {
273
+ if (TYPE(io) == T_FIXNUM) {
274
+ *fptr = NULL;
275
+ return FIX2INT(io);
276
+ }
277
+
270
278
  if (rb_obj_class(io) == cPipe) {
271
279
  *fptr = NULL;
272
280
  Pipe_verify_blocking_mode(io, Qfalse);
273
281
  return Pipe_get_fd(io, write_mode);
274
282
  }
275
- else {
276
- VALUE underlying_io = rb_ivar_get(io, ID_ivar_io);
277
- if (underlying_io != Qnil) io = underlying_io;
278
-
279
- GetOpenFile(io, *fptr);
280
- int fd = rb_io_descriptor(io);
281
- io_verify_blocking_mode(io, fd, Qfalse);
282
- if (rectify_file_pos) rectify_io_file_pos(*fptr);
283
- return fd;
284
- }
283
+
284
+ VALUE underlying_io = rb_ivar_get(io, ID_ivar_io);
285
+ if (underlying_io != Qnil) io = underlying_io;
286
+
287
+ GetOpenFile(io, *fptr);
288
+ int fd = rb_io_descriptor(io);
289
+ io_verify_blocking_mode(io, fd, Qfalse);
290
+ if (rectify_file_pos) rectify_io_file_pos(*fptr);
291
+ return fd;
285
292
  }
286
293
 
287
294
  VALUE Backend_read(VALUE self, VALUE io, VALUE buffer, VALUE length, VALUE to_eof, VALUE pos) {
@@ -558,6 +565,7 @@ error:
558
565
  return RAISE_EXCEPTION(switchpoint_result);
559
566
  }
560
567
 
568
+ #ifndef POLYPHONY_WINDOWS
561
569
  VALUE Backend_writev(VALUE self, VALUE io, int argc, VALUE *argv) {
562
570
  Backend_t *backend;
563
571
  struct libev_io watcher;
@@ -629,15 +637,23 @@ error:
629
637
  free(iov);
630
638
  return RAISE_EXCEPTION(switchpoint_result);
631
639
  }
640
+ #endif
632
641
 
633
642
  VALUE Backend_write_m(int argc, VALUE *argv, VALUE self) {
634
643
  if (argc < 2)
635
644
  // TODO: raise ArgumentError
636
645
  rb_raise(rb_eRuntimeError, "(wrong number of arguments (expected 2 or more))");
637
646
 
647
+ #ifdef POLYPHONY_WINDOWS
648
+ int total = 0;
649
+ for (int i = 1; i < argc; i++)
650
+ total += FIX2INT(Backend_write(self, argv[0], argv[i]));
651
+ return INT2FIX(total);
652
+ #else
638
653
  return (argc == 2) ?
639
654
  Backend_write(self, argv[0], argv[1]) :
640
655
  Backend_writev(self, argv[0], argc - 1, argv + 1);
656
+ #endif
641
657
  }
642
658
 
643
659
  VALUE Backend_accept(VALUE self, VALUE server_socket, VALUE socket_class) {
@@ -1155,7 +1171,7 @@ VALUE Backend_close(VALUE self, VALUE io) {
1155
1171
  RAISE_IF_EXCEPTION(resume_value);
1156
1172
  RB_GC_GUARD(resume_value);
1157
1173
 
1158
- fptr_finalize(fptr);
1174
+ if (fptr) fptr_finalize(fptr);
1159
1175
  // fd = -1;
1160
1176
  return io;
1161
1177
  }
@@ -1317,6 +1333,7 @@ struct libev_child {
1317
1333
  VALUE fiber;
1318
1334
  };
1319
1335
 
1336
+ #ifndef POLYPHONY_WINDOWS
1320
1337
  void Backend_child_callback(EV_P_ ev_child *w, int revents) {
1321
1338
  struct libev_child *watcher = (struct libev_child *)w;
1322
1339
  int exit_status = WEXITSTATUS(w->rstatus);
@@ -1325,8 +1342,12 @@ void Backend_child_callback(EV_P_ ev_child *w, int revents) {
1325
1342
  status = rb_ary_new_from_args(2, INT2FIX(w->rpid), INT2FIX(exit_status));
1326
1343
  Fiber_make_runnable(watcher->fiber, status);
1327
1344
  }
1345
+ #endif
1328
1346
 
1329
1347
  VALUE Backend_waitpid(VALUE self, VALUE pid) {
1348
+ #ifdef POLYPHONY_WINDOWS
1349
+ rb_raise(rb_eStandardError, "Not implemented");
1350
+ #else
1330
1351
  Backend_t *backend;
1331
1352
  struct libev_child watcher;
1332
1353
  VALUE switchpoint_result = Qnil;
@@ -1344,6 +1365,7 @@ VALUE Backend_waitpid(VALUE self, VALUE pid) {
1344
1365
  RB_GC_GUARD(watcher.fiber);
1345
1366
  RB_GC_GUARD(switchpoint_result);
1346
1367
  return switchpoint_result;
1368
+ #endif
1347
1369
  }
1348
1370
  #endif
1349
1371
 
@@ -1497,6 +1519,7 @@ done:
1497
1519
  #endif
1498
1520
  }
1499
1521
 
1522
+ #ifdef POLYPHONY_LINUX
1500
1523
  VALUE Backend_splice_chunks(VALUE self, VALUE src, VALUE dest, VALUE prefix, VALUE postfix, VALUE chunk_prefix, VALUE chunk_postfix, VALUE chunk_size) {
1501
1524
  Backend_t *backend;
1502
1525
  GetBackend(self, backend);
@@ -1586,6 +1609,7 @@ error:
1586
1609
  if (pipefd[1] != -1) close(pipefd[1]);
1587
1610
  return RAISE_EXCEPTION(result);
1588
1611
  }
1612
+ #endif
1589
1613
 
1590
1614
  VALUE Backend_trace(int argc, VALUE *argv, VALUE self) {
1591
1615
  Backend_t *backend;
@@ -1651,7 +1675,10 @@ void Init_Backend(void) {
1651
1675
  rb_define_method(cBackend, "chain", Backend_chain, -1);
1652
1676
  rb_define_method(cBackend, "idle_gc_period=", Backend_idle_gc_period_set, 1);
1653
1677
  rb_define_method(cBackend, "idle_proc=", Backend_idle_proc_set, 1);
1678
+
1679
+ #ifdef POLYPHONY_LINUX
1654
1680
  rb_define_method(cBackend, "splice_chunks", Backend_splice_chunks, 7);
1681
+ #endif
1655
1682
 
1656
1683
  rb_define_method(cBackend, "accept", Backend_accept, 2);
1657
1684
  rb_define_method(cBackend, "accept_loop", Backend_accept_loop, 2);
@@ -2,12 +2,14 @@
2
2
 
3
3
  require 'rubygems'
4
4
  require 'mkmf'
5
+ require 'rbconfig'
5
6
 
6
7
  dir_config 'polyphony_ext'
7
8
 
8
9
  KERNEL_INFO_RE = /Linux (\d)\.(\d+)(?:\.)?((?:\d+\.?)*)(?:\-)?([\w\-]+)?/
9
10
  def get_config
10
11
  config = { linux: !!(RUBY_PLATFORM =~ /linux/) }
12
+ config[:windows] = !!(RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/)
11
13
  return config if !config[:linux]
12
14
 
13
15
  kernel_info = `uname -sr`
@@ -17,7 +19,8 @@ def get_config
17
19
  version, major_revision, distribution = m[1].to_i, m[2].to_i, m[4]
18
20
 
19
21
  combined_version = version.to_i * 100 + major_revision.to_i
20
-
22
+
23
+ config[:kernel_version] = combined_version
21
24
  config[:pidfd_open] = combined_version > 503
22
25
  config[:multishot_recv] = combined_version >= 600
23
26
  config[:multishot_recvmsg] = combined_version >= 600
@@ -31,7 +34,7 @@ def get_config
31
34
  end
32
35
 
33
36
  config = get_config
34
- puts "Building Polyphony... (#{config.inspect})"
37
+ puts "Building Polyphony (\n#{config.map { |(k, v)| " #{k}: #{v}\n"}.join})"
35
38
 
36
39
  require_relative 'zlib_conf'
37
40
 
@@ -71,6 +74,7 @@ if config[:io_uring]
71
74
  else
72
75
  $defs << "-DPOLYPHONY_BACKEND_LIBEV"
73
76
  $defs << "-DPOLYPHONY_LINUX" if config[:linux]
77
+ $defs << "-DPOLYPHONY_WINDOWS" if config[:windows]
74
78
 
75
79
  $defs << "-DEV_STANDALONE" # prevent libev from assuming "config.h" exists
76
80
 
data/ext/polyphony/pipe.c CHANGED
@@ -97,8 +97,8 @@ VALUE Pipe_close(VALUE self) {
97
97
  if (pipe->w_closed)
98
98
  rb_raise(rb_eRuntimeError, "Pipe is already closed for writing");
99
99
 
100
+ Backend_close(BACKEND(), INT2FIX(pipe->fds[1]));
100
101
  pipe->w_closed = 1;
101
- close(pipe->fds[1]);
102
102
  return self;
103
103
  }
104
104
 
@@ -80,20 +80,6 @@ VALUE Polyphony_backend_accept_loop(VALUE self, VALUE server_socket, VALUE socke
80
80
  return Backend_accept_loop(BACKEND(), server_socket, socket_class);
81
81
  }
82
82
 
83
- #ifdef HAVE_IO_URING_PREP_MULTISHOT_ACCEPT
84
- /* Starts a multishot accept operation on the given server socket. This API is
85
- * available only for the io_uring backend.
86
- *
87
- * @param server_socket [Socket] socket to accept on
88
- * @return [any] block return value
89
- */
90
-
91
- VALUE Polyphony_backend_multishot_accept(VALUE self, VALUE server_socket) {
92
- return Backend_multishot_accept(BACKEND(), server_socket);
93
- }
94
- #endif
95
-
96
-
97
83
  /* Connects the given socket to the given address and port.
98
84
  *
99
85
  * @param io [Socket] socket to connect
@@ -433,12 +419,6 @@ void Init_Polyphony(void) {
433
419
  rb_define_singleton_method(mPolyphony, "backend_accept_loop", Polyphony_backend_accept_loop, 2);
434
420
  rb_define_singleton_method(mPolyphony, "backend_connect", Polyphony_backend_connect, 3);
435
421
  rb_define_singleton_method(mPolyphony, "backend_feed_loop", Polyphony_backend_feed_loop, 3);
436
-
437
- #ifdef HAVE_IO_URING_PREP_MULTISHOT_ACCEPT
438
- rb_define_singleton_method(mPolyphony, "backend_multishot_accept", Polyphony_backend_multishot_accept, 1);
439
- #endif
440
-
441
-
442
422
  rb_define_singleton_method(mPolyphony, "backend_read", Polyphony_backend_read, 5);
443
423
  rb_define_singleton_method(mPolyphony, "backend_read_loop", Polyphony_backend_read_loop, 2);
444
424
  rb_define_singleton_method(mPolyphony, "backend_recv", Polyphony_backend_recv, 4);
@@ -101,11 +101,6 @@ VALUE Backend_accept(VALUE self, VALUE server_socket, VALUE socket_class);
101
101
  VALUE Backend_accept_loop(VALUE self, VALUE server_socket, VALUE socket_class);
102
102
  VALUE Backend_connect(VALUE self, VALUE io, VALUE addr, VALUE port);
103
103
  VALUE Backend_feed_loop(VALUE self, VALUE io, VALUE receiver, VALUE method);
104
-
105
- #ifdef HAVE_IO_URING_PREP_MULTISHOT_ACCEPT
106
- VALUE Backend_multishot_accept(VALUE self, VALUE io);
107
- #endif
108
-
109
104
  VALUE Backend_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof, VALUE pos);
110
105
  VALUE Backend_read_loop(VALUE self, VALUE io, VALUE maxlen);
111
106
  VALUE Backend_recv(VALUE self, VALUE io, VALUE str, VALUE length, VALUE pos);
@@ -0,0 +1,18 @@
1
+ // source: https://stackoverflow.com/questions/57897314/fatal-error-when-compiling-include-sys-uio-h-on-project-windows
2
+
3
+ #ifndef SYS_UIO_H
4
+ #define SYS_UIO_H
5
+
6
+ #include <inttypes.h>
7
+ #include <unistd.h>
8
+
9
+ struct iovec
10
+ {
11
+ void *iov_base; /* Base address of a memory region for input or output */
12
+ size_t iov_len; /* The size of the memory pointed to by iov_base */
13
+ };
14
+
15
+ ssize_t readv(int fildes, const struct iovec *iov, int iovcnt);
16
+ ssize_t writev(int fildes, const struct iovec *iov, int iovcnt);
17
+
18
+ #endif /* SYS_UIO_H */
@@ -464,20 +464,6 @@ class ::TCPServer < ::TCPSocket
464
464
  Polyphony.backend_accept(@io, TCPSocket)
465
465
  end
466
466
 
467
- if Polyphony.instance_methods(false).include?(:backend_multishot_accept)
468
- # Starts a multishot accept operation (only available with io_uring
469
- # backend). Example usage:
470
- #
471
- # server.multishot_accept do
472
- # server.accept_loop { |c| handle_connection(c) }
473
- # end
474
- #
475
- # @return [any] return value of code block
476
- def multishot_accept(&block)
477
- Polyphony.backend_multishot_accept(@io, &block)
478
- end
479
- end
480
-
481
467
  # Accepts incoming connections in an infinite loop.
482
468
  #
483
469
  # @yield [TCPSocket] accepted socket
@@ -14,6 +14,10 @@ module ::Timeout
14
14
  # @param message [String] exception message
15
15
  # @return [any] block's return value
16
16
  def self.timeout(sec, klass = Timeout::Error, message = 'execution expired', &block)
17
- cancel_after(sec, with_exception: [klass, message], &block)
17
+ if sec.nil? || sec == 0
18
+ block.call
19
+ else
20
+ cancel_after(sec, with_exception: [klass, message], &block)
21
+ end
18
22
  end
19
23
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Polyphony
4
4
  # @!visibility private
5
- VERSION = '1.4'
5
+ VERSION = '1.5'
6
6
  end
data/test/test_backend.rb CHANGED
@@ -317,6 +317,8 @@ class BackendTest < MiniTest::Test
317
317
  end
318
318
 
319
319
  def test_splice_chunks
320
+ skip if !Thread.current.backend.respond_to?(:splice_chunks)
321
+
320
322
  body = 'abcd' * 4
321
323
  chunk_size = 12
322
324
 
@@ -346,8 +348,8 @@ class BackendTest < MiniTest::Test
346
348
  expected = "Content-Type: foo\r\n\r\n#{12.to_s(16)}\r\n#{body[0..11]}\r\n#{4.to_s(16)}\r\n#{body[12..15]}\r\n0\r\n\r\n"
347
349
  assert_equal expected, buf
348
350
  ensure
349
- o.close
350
- w.close
351
+ o&.close
352
+ w&.close
351
353
  end
352
354
 
353
355
  def test_idle_gc
data/test/test_ext.rb CHANGED
@@ -242,6 +242,20 @@ class TimeoutTest < MiniTest::Test
242
242
  assert_equal [0, 1, 2], buffer
243
243
  end
244
244
 
245
+ def test_timeout_with_nil_or_zero
246
+ v = Timeout.timeout(nil) do
247
+ sleep 0.01
248
+ :foo
249
+ end
250
+ assert_equal :foo, v
251
+
252
+ v = Timeout.timeout(0) do
253
+ sleep 0.01
254
+ :bar
255
+ end
256
+ assert_equal :bar, v
257
+ end
258
+
245
259
  class MyTimeout < Exception
246
260
  end
247
261
 
@@ -128,16 +128,16 @@ class MoveOnAfterTest < MiniTest::Test
128
128
 
129
129
  def test_move_on_after_with_reset
130
130
  t0 = monotonic_clock
131
- v = move_on_after(0.01, with_value: :moved_on) do |timeout|
132
- sleep 0.006
131
+ v = move_on_after(0.1, with_value: :moved_on) do |timeout|
132
+ sleep 0.06
133
133
  timeout.reset
134
- sleep 0.006
134
+ sleep 0.06
135
135
  nil
136
136
  end
137
137
  t1 = monotonic_clock
138
138
 
139
139
  assert_nil v
140
- assert_in_range 0.012..0.030, t1 - t0 if IS_LINUX
140
+ assert_in_range 0.12..0.30, t1 - t0 if IS_LINUX
141
141
  end
142
142
 
143
143
  def test_nested_move_on_after
data/test/test_socket.rb CHANGED
@@ -390,98 +390,3 @@ class SSLSocketTest < MiniTest::Test
390
390
  assert_kind_of OpenSSL::SSL::SSLError, errors.first
391
391
  end
392
392
  end
393
-
394
- class MultishotAcceptTest < MiniTest::Test
395
- def setup
396
- skip if !TCPServer.instance_methods(false).include?(:multishot_accept)
397
- end
398
-
399
- def start_tcp_server_on_random_port(host = '127.0.0.1')
400
- port = rand(1100..60000)
401
- server = TCPServer.new(host, port)
402
- [port, server]
403
- rescue Errno::EADDRINUSE
404
- retry
405
- end
406
-
407
- def test_multishot_accept_while_loop
408
- port, server = start_tcp_server_on_random_port
409
- server_fiber = spin do
410
- server.multishot_accept do
411
- while (socket = server.accept)
412
- spin do
413
- while (data = socket.gets(8192))
414
- socket << data
415
- end
416
- end
417
- end
418
- end
419
- end
420
-
421
- snooze
422
- client = TCPSocket.new('127.0.0.1', port)
423
- client.write("1234\n")
424
- assert_equal "1234\n", client.recv(8192)
425
- client.close
426
-
427
- client = TCPSocket.new('127.0.0.1', port)
428
- client.write("5678\n")
429
- assert_equal "5678\n", client.recv(8192)
430
- client.close
431
-
432
- ensure
433
- server_fiber&.stop
434
- server_fiber&.await
435
- server&.close
436
- end
437
-
438
- def spin_client(socket)
439
- spin do
440
- while (data = socket.gets(8192))
441
- socket << data
442
- end
443
- end
444
- end
445
-
446
- def test_multishot_accept_loop
447
- port, server = start_tcp_server_on_random_port
448
- server_fiber = spin do
449
- server.multishot_accept do
450
- server.accept_loop { |s| spin_client(s) }
451
- end
452
- end
453
-
454
- snooze
455
- client = TCPSocket.new('127.0.0.1', port)
456
- client.write("1234\n")
457
- assert_equal "1234\n", client.recv(8192)
458
- client.close
459
-
460
- client = TCPSocket.new('127.0.0.1', port)
461
- client.write("5678\n")
462
- assert_equal "5678\n", client.recv(8192)
463
- client.close
464
-
465
- ensure
466
- server_fiber&.stop
467
- server_fiber&.await
468
- server&.close
469
- end
470
-
471
- def test_multishot_accept_error
472
- port, server = start_tcp_server_on_random_port
473
- error = nil
474
- server_fiber = spin do
475
- server.multishot_accept do
476
- server.accept_loop { |s| spin_client(s) }
477
- rescue SystemCallError => e
478
- error = e
479
- end
480
- end
481
- snooze
482
- server.close
483
- snooze
484
- server_fiber.await
485
- assert_kind_of Errno::EBADF, error
486
- end
487
- end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: polyphony
3
3
  version: !ruby/object:Gem::Version
4
- version: '1.4'
4
+ version: '1.5'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-07-01 00:00:00.000000000 Z
11
+ date: 2023-07-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake-compiler
@@ -150,7 +150,7 @@ dependencies:
150
150
  - - '='
151
151
  - !ruby/object:Gem::Version
152
152
  version: 2.10.0
153
- description:
153
+ description:
154
154
  email: sharon@noteflakes.com
155
155
  executables: []
156
156
  extensions:
@@ -312,6 +312,7 @@ files:
312
312
  - examples/pipes/gzip.rb
313
313
  - examples/pipes/gzip_http_server.rb
314
314
  - examples/pipes/http_server.rb
315
+ - examples/pipes/http_server2.rb
315
316
  - examples/pipes/tcp_proxy.rb
316
317
  - examples/pipes/tee.rb
317
318
  - ext/libev/Changes
@@ -356,6 +357,7 @@ files:
356
357
  - ext/polyphony/runqueue_ring_buffer.h
357
358
  - ext/polyphony/socket_extensions.c
358
359
  - ext/polyphony/thread.c
360
+ - ext/polyphony/win_uio.h
359
361
  - ext/polyphony/zlib_conf.rb
360
362
  - ext/test_eintr.c
361
363
  - lib/polyphony.rb
@@ -658,7 +660,7 @@ metadata:
658
660
  source_code_uri: https://github.com/digital-fabric/polyphony
659
661
  documentation_uri: https://www.rubydoc.info/gems/polyphony
660
662
  changelog_uri: https://github.com/digital-fabric/polyphony/blob/master/CHANGELOG.md
661
- post_install_message:
663
+ post_install_message:
662
664
  rdoc_options:
663
665
  - "--title"
664
666
  - polyphony
@@ -678,7 +680,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
678
680
  version: '0'
679
681
  requirements: []
680
682
  rubygems_version: 3.4.1
681
- signing_key:
683
+ signing_key:
682
684
  specification_version: 4
683
685
  summary: Fine grained concurrency for Ruby
684
686
  test_files: []