polyphony 1.3 → 1.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 537c28ec35d12c724a5f5ef4117ada1641d0b49b780757965615665bddbe995a
4
- data.tar.gz: 5c11137a7a7dc6774ec815ec8b38791752f1beb939311b5968f0d0b242c7f8ad
3
+ metadata.gz: 91cce4703d1b930aa9b242373492a373abd6d49b5ce200f8828683538c15562b
4
+ data.tar.gz: c5c8af2ab5f4961b6379015b8d68f10c56dc5c466a44c0a0a4d6be4a93ab26b9
5
5
  SHA512:
6
- metadata.gz: 912c4a3cea0a183350c939e588bba70656fcd14a07af6bd5912e3c2208cbb286150477d050482b030c494b0f87f21da7ec350c50f5bfee8e563bc21211143adc
7
- data.tar.gz: c72b01fcef19a525cef49b277f0f1fe949956499be51f34f75fe1e87fc6efe9222a43ac96d282460610c3a82989934a0f661e78a27736120117c1ef391036dd6
6
+ metadata.gz: 5a9cb0b512efaae4b0454f3b5e99807657275a8d4c80dd8f527235c842c3131f4a38f4e4eefc174aeae1c2e7c6f879e9ccf8064e49b42dd69cb27d7780ff1780
7
+ data.tar.gz: 19977c5db508a5a5bdf1fa1f4b0db061436849c5d75f59c62764ae3bfd5426014e4e119e7306b68edf25c82cea9193f259c2e1403b1def825847614c003026dd
data/.yardopts CHANGED
@@ -18,6 +18,7 @@
18
18
  ./ext/polyphony
19
19
  -
20
20
  docs/readme.md
21
+ docs/installation.md
21
22
  docs/overview.md
22
23
  docs/tutorial.md
23
24
  docs/cancellation.md
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## 1.4 2023-07-01
2
+
3
+ - Implement concurrent `IO#close`
4
+ - Improve docs
5
+ - Use only positional arguments in `IO#read` and `IO#readpartial` (#109 @floriandejonckheere)
6
+
1
7
  ## 1.3 2023-06-23
2
8
 
3
9
  - Improve cancellation doc page
data/README.md CHANGED
@@ -23,58 +23,24 @@
23
23
  ## What is Polyphony?
24
24
 
25
25
  Polyphony is a library for building concurrent applications in Ruby. Polyphony
26
- harnesses the power of [Ruby fibers](https://ruby-doc.org/core-2.5.1/Fiber.html)
27
- to provide a cooperative, sequential coroutine-based concurrency model. Under
28
- the hood, Polyphony uses
29
- [io_uring](https://unixism.net/loti/what_is_io_uring.html) or
26
+ harnesses the power of [Ruby fibers](https://rubyapi.org/3.2/o/fiber) to provide
27
+ a cooperative, sequential coroutine-based concurrency model. Under the hood,
28
+ Polyphony uses [io_uring](https://unixism.net/loti/what_is_io_uring.html) or
30
29
  [libev](https://github.com/enki/libev) to maximize I/O performance.
31
30
 
32
31
  ## Features
33
32
 
34
- * Co-operative scheduling of concurrent tasks using Ruby fibers.
35
- * High-performance event reactor for handling I/O events and timers.
36
- * Natural, sequential programming style that makes it easy to reason about
37
- concurrent code.
38
- * Abstractions and constructs for controlling the execution of concurrent code:
39
- supervisors, cancel scopes, throttling, resource pools etc.
40
- * Code can use native networking classes and libraries, growing support for
41
- third-party gems such as `pg` and `redis`.
42
- * Use stdlib classes such as `TCPServer`, `TCPSocket` and
43
- `OpenSSL::SSL::SSLSocket`.
44
- * Competitive performance and scalability characteristics, in terms of both
45
- throughput and memory consumption.
46
-
47
- ## Installing
48
-
49
- ### System Requirements
50
-
51
- In order to use Polyphony you need to have:
52
-
53
- - Linux or MacOS (support for Windows will come at a later stage)
54
- - Ruby (MRI) 3.1 or newer
55
-
56
- ### Installing the Polyphony Gem
57
-
58
- Add this line to your application's Gemfile:
59
-
60
- ```ruby
61
- gem 'polyphony'
62
- ```
63
-
64
- And then execute:
65
-
66
- ```bash
67
- $ bundle
68
- ```
69
-
70
- Or install it yourself as:
71
-
72
- ```bash
73
- $ gem install polyphony
74
- ```
33
+ * Ruby fibers as the main unit of concurrency.
34
+ * [Structured concurrency](https://en.wikipedia.org/wiki/Structured_concurrency)
35
+ coupled with robust exception handling.
36
+ * Message passing between fibers, even across threads!
37
+ * High-performance I/O using the core Ruby I/O classes and
38
+ [io_uring](https://unixism.net/loti/what_is_io_uring.html) with support for
39
+ [advanced I/O patterns](docs/advanced-io.md).
75
40
 
76
41
  ## Usage
77
42
 
43
+ - [Installation](docs/installation.md)
78
44
  - [Overview](docs/overview.md)
79
45
  - [Tutorial](docs/tutorial.md)
80
46
  - [All About Cancellation: How to Stop Concurrent Operations](docs/cancellation.md)
@@ -0,0 +1,30 @@
1
+ # @title Installation
2
+
3
+ # Installation
4
+
5
+ ## System Requirements
6
+
7
+ In order to use Polyphony you need to have:
8
+
9
+ - Linux or MacOS (support for Windows will come at a later stage)
10
+ - Ruby (MRI) 3.1 or newer
11
+
12
+ ## Installing the Polyphony Gem
13
+
14
+ Add this line to your application's Gemfile:
15
+
16
+ ```ruby
17
+ gem 'polyphony'
18
+ ```
19
+
20
+ And then execute:
21
+
22
+ ```bash
23
+ $ bundle
24
+ ```
25
+
26
+ Or install it yourself as:
27
+
28
+ ```bash
29
+ $ gem install polyphony
30
+ ```
data/docs/readme.md CHANGED
@@ -25,58 +25,24 @@
25
25
  ## What is Polyphony?
26
26
 
27
27
  Polyphony is a library for building concurrent applications in Ruby. Polyphony
28
- harnesses the power of [Ruby fibers](https://ruby-doc.org/core-2.5.1/Fiber.html)
29
- to provide a cooperative, sequential coroutine-based concurrency model. Under
30
- the hood, Polyphony uses
31
- [io_uring](https://unixism.net/loti/what_is_io_uring.html) or
28
+ harnesses the power of [Ruby fibers](https://rubyapi.org/3.2/o/fiber) to provide
29
+ a cooperative, sequential coroutine-based concurrency model. Under the hood,
30
+ Polyphony uses [io_uring](https://unixism.net/loti/what_is_io_uring.html) or
32
31
  [libev](https://github.com/enki/libev) to maximize I/O performance.
33
32
 
34
33
  ## Features
35
34
 
36
- * Co-operative scheduling of concurrent tasks using Ruby fibers.
37
- * High-performance event reactor for handling I/O events and timers.
38
- * Natural, sequential programming style that makes it easy to reason about
39
- concurrent code.
40
- * Abstractions and constructs for controlling the execution of concurrent code:
41
- supervisors, cancel scopes, throttling, resource pools etc.
42
- * Code can use native networking classes and libraries, growing support for
43
- third-party gems such as `pg` and `redis`.
44
- * Use stdlib classes such as `TCPServer`, `TCPSocket` and
45
- `OpenSSL::SSL::SSLSocket`.
46
- * Competitive performance and scalability characteristics, in terms of both
47
- throughput and memory consumption.
48
-
49
- ## Installing
50
-
51
- ### System Requirements
52
-
53
- In order to use Polyphony you need to have:
54
-
55
- - Linux or MacOS (support for Windows will come at a later stage)
56
- - Ruby (MRI) 3.1 or newer
57
-
58
- ### Installing the Polyphony Gem
59
-
60
- Add this line to your application's Gemfile:
61
-
62
- ```ruby
63
- gem 'polyphony'
64
- ```
65
-
66
- And then execute:
67
-
68
- ```bash
69
- $ bundle
70
- ```
71
-
72
- Or install it yourself as:
73
-
74
- ```bash
75
- $ gem install polyphony
76
- ```
35
+ * Ruby fibers as the main unit of concurrency.
36
+ * [Structured concurrency](https://en.wikipedia.org/wiki/Structured_concurrency)
37
+ coupled with robust exception handling.
38
+ * Message passing between fibers, even across threads!
39
+ * High-performance I/O using the core Ruby I/O classes and
40
+ [io_uring](https://unixism.net/loti/what_is_io_uring.html) with support for
41
+ {file:/docs/advanced-io.md advanced I/O patterns}.
77
42
 
78
43
  ## Usage
79
44
 
45
+ - {file:/docs/installation.md Installation}
80
46
  - {file:/docs/overview.md Overview}
81
47
  - {file:/docs/tutorial.md Tutorial}
82
48
  - {file:/docs/cancellation.md All About Cancellation: How to Stop Concurrent Operations}
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ # Based on the design of Elixir's GenStage
7
+
8
+ class Producer
9
+ def initialize(mod, *a, **b)
10
+ extend(mod)
11
+ setup(*a, **b)
12
+ @_fiber = spin do
13
+ receive_loop do |msg|
14
+ case msg[:kind]
15
+ when :demand
16
+ items = handle_demand(msg[:limit])
17
+ msg[:peer] << items
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ def <<(msg)
24
+ @_fiber << msg
25
+ end
26
+ end
27
+
28
+ module Counter
29
+ def setup(counter = 0)
30
+ @counter = counter
31
+ end
32
+
33
+ def handle_demand(demand)
34
+ events = (@counter...@counter + demand).to_a
35
+ @counter += demand
36
+ events
37
+ end
38
+ end
39
+
40
+ counter = Producer.new(Counter, 0)
41
+
42
+ class Consumer
43
+ def initialize(mod, *a, **b)
44
+ extend(mod)
45
+ setup(*a, **b) if respond_to?(:setup)
46
+ @_fiber = spin do
47
+ while true
48
+ items = get_items
49
+ handle_items(items)
50
+ end
51
+ end
52
+
53
+ @max_demand = 10
54
+ @min_demand = 5
55
+ end
56
+
57
+ def subscribe(upstream)
58
+ @upstream = upstream
59
+ end
60
+
61
+ private
62
+
63
+ def get_items
64
+ send_demand(@max_demand) if !@sent_demand
65
+ items = receive
66
+ send_demand(@min_demand)
67
+ items
68
+ end
69
+
70
+ def send_demand(demand)
71
+ if @upstream
72
+ @upstream << { peer: Fiber.current, kind: :demand, limit: demand }
73
+ @sent_demand = true
74
+ else
75
+ sleep 0.1
76
+ end
77
+ end
78
+ end
79
+
80
+ module Printer
81
+ def handle_items(items)
82
+ sleep 1
83
+ puts "got: #{items.join(' ')}"
84
+ end
85
+ end
86
+
87
+ # counter << { peer: Fiber.current, kind: :demand, limit: 10 }
88
+ # r = receive
89
+
90
+ # p r: r
91
+
92
+ # counter << { peer: Fiber.current, kind: :demand, limit: 10 }
93
+ # r = receive
94
+ # p r: r
95
+
96
+ printer = Consumer.new(Printer)
97
+ printer.subscribe(counter)
98
+
99
+ sleep
@@ -458,7 +458,7 @@ VALUE Backend_stats(VALUE self) {
458
458
 
459
459
  VALUE Backend_verify_blocking_mode(VALUE self, VALUE io, VALUE blocking) {
460
460
  io_verify_blocking_mode(io, rb_io_descriptor(io), blocking);
461
- return self;
461
+ return io;
462
462
  }
463
463
 
464
464
  void backend_setup_stats_symbols(void) {
@@ -1363,34 +1363,33 @@ VALUE Backend_wait_io(VALUE self, VALUE io, VALUE write) {
1363
1363
  return self;
1364
1364
  }
1365
1365
 
1366
- // VALUE Backend_close(VALUE self, VALUE io) {
1367
- // Backend_t *backend;
1368
- // rb_io_t *fptr;
1369
- // VALUE resume_value = Qnil;
1370
- // op_context_t *ctx;
1371
- // struct io_uring_sqe *sqe;
1372
- // int result;
1373
- // int completed;
1374
-
1375
- // if (fd < 0) return Qnil;
1376
-
1377
- // io_unset_nonblock(io, fd);
1378
-
1379
- // ctx = context_store_acquire(&backend->store, OP_CLOSE);
1380
- // sqe = io_uring_backend_get_sqe(backend);
1381
- // io_uring_prep_close(sqe, fd);
1382
- // result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
1383
- // completed = context_store_release(&backend->store, ctx);
1384
- // RAISE_IF_EXCEPTION(resume_value);
1385
- // if (!completed) return resume_value;
1386
- // RB_GC_GUARD(resume_value);
1387
-
1388
- // if (result < 0) rb_syserr_fail(-result, strerror(-result));
1389
-
1390
- // fptr_finalize(fptr);
1391
- // // fd = -1;
1392
- // return io;
1393
- // }
1366
+ VALUE Backend_close(VALUE self, VALUE io) {
1367
+ Backend_t *backend;
1368
+ rb_io_t *fptr;
1369
+ VALUE resume_value = Qnil;
1370
+ op_context_t *ctx;
1371
+ struct io_uring_sqe *sqe;
1372
+ int result;
1373
+ int completed;
1374
+ int fd = fd_from_io(io, &fptr, 0, 0);
1375
+ if (fd < 0) return Qnil;
1376
+
1377
+ GetBackend(self, backend);
1378
+ ctx = context_store_acquire(&backend->store, OP_CLOSE);
1379
+ sqe = io_uring_backend_get_sqe(backend);
1380
+ io_uring_prep_close(sqe, fd);
1381
+ result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
1382
+ completed = context_store_release(&backend->store, ctx);
1383
+ RAISE_IF_EXCEPTION(resume_value);
1384
+ if (!completed) return resume_value;
1385
+ RB_GC_GUARD(resume_value);
1386
+
1387
+ if (result < 0) rb_syserr_fail(-result, strerror(-result));
1388
+
1389
+ fptr_finalize(fptr);
1390
+ // fd = -1;
1391
+ return io;
1392
+ }
1394
1393
 
1395
1394
  inline struct __kernel_timespec double_to_timespec(double duration) {
1396
1395
  double duration_integral;
@@ -2015,7 +2014,7 @@ void Init_Backend(void) {
2015
2014
  rb_define_method(cBackend, "wait_io", Backend_wait_io, 2);
2016
2015
  rb_define_method(cBackend, "waitpid", Backend_waitpid, 1);
2017
2016
  rb_define_method(cBackend, "write", Backend_write_m, -1);
2018
- // rb_define_method(cBackend, "close", Backend_close, 1);
2017
+ rb_define_method(cBackend, "close", Backend_close, 1);
2019
2018
 
2020
2019
  SYM_io_uring = ID2SYM(rb_intern("io_uring"));
2021
2020
  SYM_send = ID2SYM(rb_intern("send"));
@@ -1135,6 +1135,31 @@ VALUE Backend_wait_io(VALUE self, VALUE io, VALUE write) {
1135
1135
  return libev_wait_fd(backend, fd, events, 1);
1136
1136
  }
1137
1137
 
1138
+ VALUE Backend_close(VALUE self, VALUE io) {
1139
+ Backend_t *backend;
1140
+ rb_io_t *fptr;
1141
+ VALUE resume_value = Qnil;
1142
+ int result;
1143
+ int fd = fd_from_io(io, &fptr, 0, 0);
1144
+ if (fd < 0) return Qnil;
1145
+
1146
+ GetBackend(self, backend);
1147
+
1148
+ result = close(fd);
1149
+ if (result == -1) {
1150
+ int err = errno;
1151
+ rb_syserr_fail(err, strerror(err));
1152
+ }
1153
+
1154
+ resume_value = backend_snooze(&backend->base);
1155
+ RAISE_IF_EXCEPTION(resume_value);
1156
+ RB_GC_GUARD(resume_value);
1157
+
1158
+ fptr_finalize(fptr);
1159
+ // fd = -1;
1160
+ return io;
1161
+ }
1162
+
1138
1163
  struct libev_timer {
1139
1164
  struct ev_timer timer;
1140
1165
  VALUE fiber;
@@ -1654,6 +1679,7 @@ void Init_Backend(void) {
1654
1679
  rb_define_method(cBackend, "wait_io", Backend_wait_io, 2);
1655
1680
  rb_define_method(cBackend, "waitpid", Backend_waitpid, 1);
1656
1681
  rb_define_method(cBackend, "write", Backend_write_m, -1);
1682
+ rb_define_method(cBackend, "close", Backend_close, 1);
1657
1683
 
1658
1684
  SYM_libev = ID2SYM(rb_intern("libev"));
1659
1685
 
@@ -404,9 +404,26 @@ VALUE Polyphony_raw_buffer_size(VALUE self, VALUE buffer) {
404
404
  return INT2FIX(buffer_spec->len);
405
405
  }
406
406
 
407
- // VALUE Polyphony_backend_close(VALUE self, VALUE io) {
408
- // return Backend_close(BACKEND(), io);
409
- // }
407
+ /* Closes the given IO.
408
+ *
409
+ * @param io [IO, Polyphony::Pipe] IO instance
410
+ * @return [IO, Polyphony::Pipe] given IO
411
+ */
412
+
413
+ VALUE Polyphony_backend_close(VALUE self, VALUE io) {
414
+ return Backend_close(BACKEND(), io);
415
+ }
416
+
417
+ /* Ensures the given IO is in blocking/non-blocking mode.
418
+ *
419
+ * @param io [IO, Polyphony::Pipe] IO instance
420
+ * @param blocking [boolean] true for blocking, false for non-blocking mode
421
+ * @return [IO, Polyphony::Pipe] given IO
422
+ */
423
+
424
+ VALUE Polyphony_backend_verify_blocking_mode(VALUE self, VALUE io, VALUE blocking) {
425
+ return Backend_verify_blocking_mode(BACKEND(), io, blocking);
426
+ }
410
427
 
411
428
  void Init_Polyphony(void) {
412
429
  mPolyphony = rb_define_module("Polyphony");
@@ -448,8 +465,8 @@ void Init_Polyphony(void) {
448
465
  rb_define_singleton_method(mPolyphony, "backend_wait_io", Polyphony_backend_wait_io, 2);
449
466
  rb_define_singleton_method(mPolyphony, "backend_waitpid", Polyphony_backend_waitpid, 1);
450
467
  rb_define_singleton_method(mPolyphony, "backend_write", Polyphony_backend_write, -1);
451
- // rb_define_singleton_method(mPolyphony, "backend_close", Polyphony_backend_close, 1);
452
- rb_define_singleton_method(mPolyphony, "backend_verify_blocking_mode", Backend_verify_blocking_mode, 2);
468
+ rb_define_singleton_method(mPolyphony, "backend_close", Polyphony_backend_close, 1);
469
+ rb_define_singleton_method(mPolyphony, "backend_verify_blocking_mode", Polyphony_backend_verify_blocking_mode, 2);
453
470
 
454
471
  rb_define_singleton_method(mPolyphony, "__with_raw_buffer__", Polyphony_with_raw_buffer, 1);
455
472
  rb_define_singleton_method(mPolyphony, "__raw_buffer_get__", Polyphony_raw_buffer_get, -1);
@@ -133,7 +133,7 @@ VALUE Backend_wait_io(VALUE self, VALUE io, VALUE write);
133
133
  VALUE Backend_waitpid(VALUE self, VALUE pid);
134
134
  VALUE Backend_write(VALUE self, VALUE io, VALUE str);
135
135
  VALUE Backend_write_m(int argc, VALUE *argv, VALUE self);
136
- // VALUE Backend_close(VALUE self, VALUE io);
136
+ VALUE Backend_close(VALUE self, VALUE io);
137
137
 
138
138
  VALUE Backend_poll(VALUE self, VALUE blocking);
139
139
  VALUE Backend_wait_event(VALUE self, VALUE raise_on_exception);
@@ -52,7 +52,7 @@ module Polyphony
52
52
 
53
53
  # Converts an event (expressed as an array) to a hash.
54
54
  #
55
- # @param e [Array] event as emitted by the backend
55
+ # @param event [Array] event as emitted by the backend
56
56
  # @return [Hash] event hash
57
57
  def trace_event_info(event)
58
58
  {
@@ -65,7 +65,7 @@ module Polyphony
65
65
 
66
66
  # Returns an event hash for a `:block` event.
67
67
  #
68
- # @param e [Array] event array
68
+ # @param event [Array] event array
69
69
  # @return [Hash] event hash
70
70
  def event_props_block(event)
71
71
  {
@@ -76,7 +76,7 @@ module Polyphony
76
76
 
77
77
  # Returns an event hash for a `:enter_poll` event.
78
78
  #
79
- # @param e [Array] event array
79
+ # @param _event [Array] event array
80
80
  # @return [Hash] event hash
81
81
  def event_props_enter_poll(_event)
82
82
  {}
@@ -84,7 +84,7 @@ module Polyphony
84
84
 
85
85
  # Returns an event hash for a `:leave_poll` event.
86
86
  #
87
- # @param e [Array] event array
87
+ # @param _event [Array] event array
88
88
  # @return [Hash] event hash
89
89
  def event_props_leave_poll(_event)
90
90
  {}
@@ -92,7 +92,7 @@ module Polyphony
92
92
 
93
93
  # Returns an event hash for a `:schedule` event.
94
94
  #
95
- # @param e [Array] event array
95
+ # @param event [Array] event array
96
96
  # @return [Hash] event hash
97
97
  def event_props_schedule(event)
98
98
  {
@@ -105,7 +105,7 @@ module Polyphony
105
105
 
106
106
  # Returns an event hash for a `:spin` event.
107
107
  #
108
- # @param e [Array] event array
108
+ # @param event [Array] event array
109
109
  # @return [Hash] event hash
110
110
  def event_props_spin(event)
111
111
  {
@@ -117,7 +117,7 @@ module Polyphony
117
117
 
118
118
  # Returns an event hash for a `:terminate` event.
119
119
  #
120
- # @param e [Array] event array
120
+ # @param event [Array] event array
121
121
  # @return [Hash] event hash
122
122
  def event_props_terminate(event)
123
123
  {
@@ -128,7 +128,7 @@ module Polyphony
128
128
 
129
129
  # Returns an event hash for a `:unblock` event.
130
130
  #
131
- # @param e [Array] event array
131
+ # @param event [Array] event array
132
132
  # @return [Hash] event hash
133
133
  def event_props_unblock(event)
134
134
  {
@@ -197,7 +197,7 @@ class ::IO
197
197
  alias_method :orig_read, :read
198
198
 
199
199
  # @!visibility private
200
- def read(len = nil, buf = nil, buffer_pos: 0)
200
+ def read(len = nil, buf = nil, buffer_pos = 0)
201
201
  return '' if len == 0
202
202
  return Polyphony.backend_read(self, buf, len, true, buffer_pos) if buf
203
203
 
@@ -214,7 +214,7 @@ class ::IO
214
214
  alias_method :orig_readpartial, :read
215
215
 
216
216
  # @!visibility private
217
- def readpartial(len, str = +'', buffer_pos: 0, raise_on_eof: true)
217
+ def readpartial(len, str = +'', buffer_pos = 0, raise_on_eof = true)
218
218
  result = Polyphony.backend_read(self, str, len, false, buffer_pos)
219
219
  raise EOFError if !result && raise_on_eof
220
220
 
@@ -255,7 +255,7 @@ class ::IO
255
255
  idx = @read_buffer.index(sep)
256
256
  return @read_buffer.slice!(0, idx + sep_size) if idx
257
257
 
258
- result = readpartial(8192, @read_buffer, buffer_pos: -1)
258
+ result = readpartial(8192, @read_buffer, -1)
259
259
  return nil unless result
260
260
  end
261
261
  rescue EOFError
@@ -280,7 +280,7 @@ class ::IO
280
280
  yield line
281
281
  end
282
282
 
283
- result = readpartial(8192, @read_buffer, buffer_pos: -1)
283
+ result = readpartial(8192, @read_buffer, -1)
284
284
  return self if !result
285
285
  end
286
286
  rescue EOFError
@@ -427,6 +427,19 @@ class ::IO
427
427
  Polyphony.backend_splice(src, self, maxlen)
428
428
  end
429
429
 
430
+ # @!visibility private
431
+ alias_method :orig_close, :close
432
+
433
+ # Closes the IO instance
434
+ #
435
+ # @return [void]
436
+ def close
437
+ return if closed?
438
+
439
+ Polyphony.backend_close(self)
440
+ nil
441
+ end
442
+
430
443
  if RUBY_PLATFORM =~ /linux/
431
444
  # Tees data from the given IO.
432
445
  #
@@ -5,6 +5,22 @@ require 'open3'
5
5
  module Polyphony
6
6
  # Intercepts calls to #trap
7
7
  module TrapInterceptor
8
+ # Installs a signal handler. If a block is given (or the command parameter
9
+ # is a Proc or a callable), it is executed inside an out-of-band,
10
+ # prioritized fiber.
11
+ #
12
+ # If the command is the string “IGNORE” or “SIG_IGN”, the signal will be
13
+ # ignored. If the command is “DEFAULT” or “SIG_DFL”, the Ruby’s default
14
+ # handler will be invoked. If the command is “EXIT”, the script will be
15
+ # terminated by the signal. If the command is “SYSTEM_DEFAULT”, the
16
+ # operating system’s default handler will be invoked. Otherwise, the given
17
+ # command or block will be run. The special signal name “EXIT” or signal
18
+ # number zero will be invoked just prior to program termination.
19
+ #
20
+ # trap returns the previous handler for the given signal.
21
+ #
22
+ # @param sig [String, Symbol, Integer] signal name or number
23
+ # @param command [String, Proc] command to perform
8
24
  def trap(sig, command = nil, &block)
9
25
  return super(sig, command) if command.is_a? String
10
26
 
@@ -90,7 +90,7 @@ class ::OpenSSL::SSL::SSLSocket
90
90
 
91
91
  # Reads from the socket. If `maxlen` is given, reads up to `maxlen` bytes from
92
92
  # the socket, otherwise reads to `EOF`. If `buf` is given, it is used as the
93
- # buffer to read into, otherwise a new string is allocated. If `buf_pos` is
93
+ # buffer to read into, otherwise a new string is allocated. If `buffer_pos` is
94
94
  # given, reads into the given offset (in bytes) in the given buffer. If the
95
95
  # given buffer offset is negative, it is calculated from the current end of
96
96
  # the buffer (`-1` means the read data will be appended to the end of the
@@ -101,21 +101,21 @@ class ::OpenSSL::SSL::SSLSocket
101
101
  #
102
102
  # @param maxlen [Integer, nil] maximum bytes to read from socket
103
103
  # @param buf [String, nil] buffer to read into
104
- # @param buf_pos [Number] buffer position to read into
104
+ # @param buffer_pos [Number] buffer position to read into
105
105
  # @return [String] buffer used for reading
106
- def read(maxlen = nil, buf = nil, buffer_pos: 0)
107
- return readpartial(maxlen, buf, buffer_pos:) if buf
106
+ def read(maxlen = nil, buf = nil, buffer_pos = 0)
107
+ return readpartial(maxlen, buf, buffer_pos) if buf
108
108
 
109
109
  buf = +''
110
110
  return readpartial(maxlen, buf) if maxlen
111
111
 
112
- readpartial(4096, buf, buffer_pos: -1) while true
112
+ readpartial(4096, buf, -1) while true
113
113
  rescue EOFError
114
114
  buf
115
115
  end
116
116
 
117
117
  # Reads up to `maxlen` from the socket. If `buf` is given, it is used as the
118
- # buffer to read into, otherwise a new string is allocated. If `buf_pos` is
118
+ # buffer to read into, otherwise a new string is allocated. If `buffer_pos` is
119
119
  # given, reads into the given offset (in bytes) in the given buffer. If the
120
120
  # given buffer offset is negative, it is calculated from the current end of
121
121
  # the buffer (`-1` means the read data will be appended to the end of the
@@ -127,16 +127,16 @@ class ::OpenSSL::SSL::SSLSocket
127
127
  #
128
128
  # @param maxlen [Integer, nil] maximum bytes to read from socket
129
129
  # @param buf [String, nil] buffer to read into
130
- # @param buf_pos [Number] buffer position to read into
130
+ # @param buffer_pos [Number] buffer position to read into
131
131
  # @param raise_on_eof [bool] whether to raise an exception on `EOF`
132
132
  # @return [String, nil] buffer used for reading or nil on `EOF`
133
- def readpartial(maxlen, buf = +'', buffer_pos: 0, raise_on_eof: true)
134
- if buf_pos != 0
133
+ def readpartial(maxlen, buf = +'', buffer_pos = 0, raise_on_eof = true)
134
+ if buffer_pos != 0
135
135
  if (result = sysread(maxlen, +''))
136
- if buf_pos == -1
136
+ if buffer_pos == -1
137
137
  result = buf + result
138
138
  else
139
- result = buf[0...buf_pos] + result
139
+ result = buf[0...buffer_pos] + result
140
140
  end
141
141
  end
142
142
  else
@@ -41,9 +41,9 @@ class Polyphony::Pipe
41
41
  #
42
42
  # @param len [Integer, nil] maximum bytes to read
43
43
  # @param buf [String, nil] buffer to read into
44
- # @param buf_pos [Integer] buffer position to read into
44
+ # @param buffer_pos [Integer] buffer position to read into
45
45
  # @return [String] read data
46
- def read(len = nil, buf = nil, buffer_pos: 0)
46
+ def read(len = nil, buf = nil, buffer_pos = 0)
47
47
  return Polyphony.backend_read(self, buf, len, true, buffer_pos) if buf
48
48
 
49
49
  @read_buffer ||= +''
@@ -59,10 +59,10 @@ class Polyphony::Pipe
59
59
  #
60
60
  # @param len [Integer, nil] maximum bytes to read
61
61
  # @param buf [String, nil] buffer to read into
62
- # @param buf_pos [Integer] buffer position to read into
62
+ # @param buffer_pos [Integer] buffer position to read into
63
63
  # @param raise_on_eof [boolean] whether to raise an error if EOF is detected
64
64
  # @return [String] read data
65
- def readpartial(len, buf = +'', buffer_pos: 0, raise_on_eof: true)
65
+ def readpartial(len, buf = +'', buffer_pos = 0, raise_on_eof = true)
66
66
  result = Polyphony.backend_read(self, buf, len, false, buffer_pos)
67
67
  raise EOFError if !result && raise_on_eof
68
68
 
@@ -104,7 +104,7 @@ class Polyphony::Pipe
104
104
  idx = @read_buffer.index(sep)
105
105
  return @read_buffer.slice!(0, idx + sep_size) if idx
106
106
 
107
- result = readpartial(8192, @read_buffer, buffer_pos: -1)
107
+ result = readpartial(8192, @read_buffer, -1)
108
108
  return nil unless result
109
109
  end
110
110
  rescue EOFError
@@ -59,7 +59,7 @@ class ::Socket < ::BasicSocket
59
59
 
60
60
  # Reads from the socket. If `maxlen` is given, reads up to `maxlen` bytes from
61
61
  # the socket, otherwise reads to `EOF`. If `buf` is given, it is used as the
62
- # buffer to read into, otherwise a new string is allocated. If `buf_pos` is
62
+ # buffer to read into, otherwise a new string is allocated. If `buffer_pos` is
63
63
  # given, reads into the given offset (in bytes) in the given buffer. If the
64
64
  # given buffer offset is negative, it is calculated from the current end of
65
65
  # the buffer (`-1` means the read data will be appended to the end of the
@@ -70,9 +70,9 @@ class ::Socket < ::BasicSocket
70
70
  #
71
71
  # @param len [Integer, nil] maximum bytes to read from socket
72
72
  # @param buf [String, nil] buffer to read into
73
- # @param buf_pos [Number] buffer position to read into
73
+ # @param buffer_pos [Number] buffer position to read into
74
74
  # @return [String] buffer used for reading
75
- def read(len = nil, buf = nil, buffer_pos: 0)
75
+ def read(len = nil, buf = nil, buffer_pos = 0)
76
76
  return '' if len == 0
77
77
  return Polyphony.backend_read(self, buf, len, true, buffer_pos) if buf
78
78
 
@@ -148,7 +148,7 @@ class ::Socket < ::BasicSocket
148
148
  end
149
149
 
150
150
  # Reads up to `maxlen` from the socket. If `buf` is given, it is used as the
151
- # buffer to read into, otherwise a new string is allocated. If `buf_pos` is
151
+ # buffer to read into, otherwise a new string is allocated. If `buffer_pos` is
152
152
  # given, reads into the given offset (in bytes) in the given buffer. If the
153
153
  # given buffer offset is negative, it is calculated from the current end of
154
154
  # the buffer (`-1` means the read data will be appended to the end of the
@@ -160,10 +160,10 @@ class ::Socket < ::BasicSocket
160
160
  #
161
161
  # @param maxlen [Integer, nil] maximum bytes to read from socket
162
162
  # @param buf [String, nil] buffer to read into
163
- # @param buf_pos [Number] buffer position to read into
163
+ # @param buffer_pos [Number] buffer position to read into
164
164
  # @param raise_on_eof [bool] whether to raise an exception on `EOF`
165
165
  # @return [String, nil] buffer used for reading or nil on `EOF`
166
- def readpartial(maxlen, buf = +'', buffer_pos: 0, raise_on_eof: true)
166
+ def readpartial(maxlen, buf = +'', buffer_pos = 0, raise_on_eof = true)
167
167
  result = Polyphony.backend_recv(self, buf, maxlen, buffer_pos)
168
168
  raise EOFError if !result && raise_on_eof
169
169
 
@@ -320,7 +320,7 @@ class ::TCPSocket < ::IPSocket
320
320
 
321
321
  # Reads from the socket. If `maxlen` is given, reads up to `maxlen` bytes from
322
322
  # the socket, otherwise reads to `EOF`. If `buf` is given, it is used as the
323
- # buffer to read into, otherwise a new string is allocated. If `buf_pos` is
323
+ # buffer to read into, otherwise a new string is allocated. If `buffer_pos` is
324
324
  # given, reads into the given offset (in bytes) in the given buffer. If the
325
325
  # given buffer offset is negative, it is calculated from the current end of
326
326
  # the buffer (`-1` means the read data will be appended to the end of the
@@ -331,9 +331,9 @@ class ::TCPSocket < ::IPSocket
331
331
  #
332
332
  # @param len [Integer, nil] maximum bytes to read from socket
333
333
  # @param buf [String, nil] buffer to read into
334
- # @param buf_pos [Number] buffer position to read into
334
+ # @param buffer_pos [Number] buffer position to read into
335
335
  # @return [String] buffer used for reading
336
- def read(len = nil, buf = nil, buffer_pos: 0)
336
+ def read(len = nil, buf = nil, buffer_pos = 0)
337
337
  return '' if len == 0
338
338
  return Polyphony.backend_read(self, buf, len, true, buffer_pos) if buf
339
339
 
@@ -391,7 +391,7 @@ class ::TCPSocket < ::IPSocket
391
391
  end
392
392
 
393
393
  # Reads up to `maxlen` from the socket. If `buf` is given, it is used as the
394
- # buffer to read into, otherwise a new string is allocated. If `buf_pos` is
394
+ # buffer to read into, otherwise a new string is allocated. If `buffer_pos` is
395
395
  # given, reads into the given offset (in bytes) in the given buffer. If the
396
396
  # given buffer offset is negative, it is calculated from the current end of
397
397
  # the buffer (`-1` means the read data will be appended to the end of the
@@ -403,10 +403,10 @@ class ::TCPSocket < ::IPSocket
403
403
  #
404
404
  # @param maxlen [Integer, nil] maximum bytes to read from socket
405
405
  # @param buf [String, nil] buffer to read into
406
- # @param buf_pos [Number] buffer position to read into
406
+ # @param buffer_pos [Number] buffer position to read into
407
407
  # @param raise_on_eof [bool] whether to raise an exception on `EOF`
408
408
  # @return [String, nil] buffer used for reading or nil on `EOF`
409
- def readpartial(maxlen, buf = +'', buffer_pos: 0, raise_on_eof: true)
409
+ def readpartial(maxlen, buf = +'', buffer_pos = 0, raise_on_eof = true)
410
410
  result = Polyphony.backend_recv(self, buf, maxlen, buffer_pos)
411
411
  raise EOFError if !result && raise_on_eof
412
412
 
@@ -526,7 +526,7 @@ class ::UNIXSocket < ::BasicSocket
526
526
 
527
527
  # Reads from the socket. If `maxlen` is given, reads up to `maxlen` bytes from
528
528
  # the socket, otherwise reads to `EOF`. If `buf` is given, it is used as the
529
- # buffer to read into, otherwise a new string is allocated. If `buf_pos` is
529
+ # buffer to read into, otherwise a new string is allocated. If `buffer_pos` is
530
530
  # given, reads into the given offset (in bytes) in the given buffer. If the
531
531
  # given buffer offset is negative, it is calculated from the current end of
532
532
  # the buffer (`-1` means the read data will be appended to the end of the
@@ -537,9 +537,9 @@ class ::UNIXSocket < ::BasicSocket
537
537
  #
538
538
  # @param len [Integer, nil] maximum bytes to read from socket
539
539
  # @param buf [String, nil] buffer to read into
540
- # @param buf_pos [Number] buffer position to read into
540
+ # @param buffer_pos [Number] buffer position to read into
541
541
  # @return [String] buffer used for reading
542
- def read(len = nil, buf = nil, buffer_pos: 0)
542
+ def read(len = nil, buf = nil, buffer_pos = 0)
543
543
  return '' if len == 0
544
544
  return Polyphony.backend_read(self, buf, len, true, buffer_pos) if buf
545
545
 
@@ -623,7 +623,7 @@ class ::UNIXSocket < ::BasicSocket
623
623
  end
624
624
 
625
625
  # Reads up to `maxlen` from the socket. If `buf` is given, it is used as the
626
- # buffer to read into, otherwise a new string is allocated. If `buf_pos` is
626
+ # buffer to read into, otherwise a new string is allocated. If `buffer_pos` is
627
627
  # given, reads into the given offset (in bytes) in the given buffer. If the
628
628
  # given buffer offset is negative, it is calculated from the current end of
629
629
  # the buffer (`-1` means the read data will be appended to the end of the
@@ -635,10 +635,10 @@ class ::UNIXSocket < ::BasicSocket
635
635
  #
636
636
  # @param maxlen [Integer, nil] maximum bytes to read from socket
637
637
  # @param buf [String, nil] buffer to read into
638
- # @param buf_pos [Number] buffer position to read into
638
+ # @param buffer_pos [Number] buffer position to read into
639
639
  # @param raise_on_eof [bool] whether to raise an exception on `EOF`
640
640
  # @return [String, nil] buffer used for reading or nil on `EOF`
641
- def readpartial(maxlen, buf = +'', buffer_pos: 0, raise_on_eof: true)
641
+ def readpartial(maxlen, buf = +'', buffer_pos = 0, raise_on_eof = true)
642
642
  result = Polyphony.backend_recv(self, buf, maxlen, buffer_pos)
643
643
  raise EOFError if !result && raise_on_eof
644
644
 
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Polyphony
4
4
  # @!visibility private
5
- VERSION = '1.3'
5
+ VERSION = '1.4'
6
6
  end
data/test/stress.rb CHANGED
@@ -3,11 +3,16 @@
3
3
  count = ARGV[0] ? ARGV[0].to_i : 100
4
4
  test_name = ARGV[1]
5
5
 
6
- $test_cmd = +'ruby test/test_scenarios.rb'
6
+ $test_cmd = +'ruby test/run.rb'
7
7
  if test_name
8
8
  $test_cmd << " --name #{test_name}"
9
9
  end
10
10
 
11
+ puts '*' * 40
12
+ puts
13
+ puts $test_cmd
14
+ puts
15
+
11
16
  def run_test(count)
12
17
  puts "#{count}: running tests..."
13
18
  # sleep 1
data/test/test_backend.rb CHANGED
@@ -544,4 +544,11 @@ class BackendChainTest < MiniTest::Test
544
544
  # released properly before raising the error (for the time being this has
545
545
  # been verified manually).
546
546
  end
547
+
548
+ def test_backend_close
549
+ r, w = IO.pipe
550
+ w << 'abc'
551
+ Thread.backend.close(w)
552
+ assert w.closed?
553
+ end
547
554
  end
@@ -137,7 +137,7 @@ class MoveOnAfterTest < MiniTest::Test
137
137
  t1 = monotonic_clock
138
138
 
139
139
  assert_nil v
140
- assert_in_range 0.012..0.025, t1 - t0 if IS_LINUX
140
+ assert_in_range 0.012..0.030, t1 - t0 if IS_LINUX
141
141
  end
142
142
 
143
143
  def test_nested_move_on_after
@@ -161,7 +161,7 @@ class MoveOnAfterTest < MiniTest::Test
161
161
  end
162
162
  t1 = monotonic_clock
163
163
  assert_equal 2, o
164
- assert_in_range 0.008..0.025, t1 - t0 if IS_LINUX
164
+ assert_in_range 0.008..0.035, t1 - t0 if IS_LINUX
165
165
  end
166
166
  end
167
167
 
data/test/test_io.rb CHANGED
@@ -89,7 +89,7 @@ class IOTest < MiniTest::Test
89
89
 
90
90
  buf = +'def'
91
91
  o << 'foobar'
92
- assert_equal 'deffoobar', i.read(6, buf, buffer_pos: -1)
92
+ assert_equal 'deffoobar', i.read(6, buf, -1)
93
93
  assert_equal 'deffoobar', buf
94
94
  end
95
95
 
data/test/test_signal.rb CHANGED
@@ -4,6 +4,10 @@ require_relative 'helper'
4
4
 
5
5
  class SignalTrapTest < Minitest::Test
6
6
  def test_signal_handler_trace
7
+ if Thread.current.backend.kind != :io_uring
8
+ skip "Skipping signal handler trace because Backend_close on libev behaves differently"
9
+ end
10
+
7
11
  i1, o1 = IO.pipe
8
12
  i2, o2 = IO.pipe
9
13
  pid = Process.pid
@@ -39,16 +43,21 @@ class SignalTrapTest < Minitest::Test
39
43
  end
40
44
 
41
45
  expected = [
42
- [:block, :main],
46
+ [:block, :main],
47
+ [:enter_poll, :main],
48
+ [:schedule, :main],
49
+ [:leave_poll, :main],
50
+ [:unblock, :main],
51
+ [:block, :main],
43
52
  [:enter_poll, :main],
44
53
  [:leave_poll, :main],
45
- [:unblock, :oob],
46
- [:terminate, :oob],
47
- [:block, :oob],
54
+ [:unblock, :oob],
55
+ [:terminate, :oob],
56
+ [:block, :oob],
48
57
  [:enter_poll, :oob],
49
- [:schedule, :main],
58
+ [:schedule, :main],
50
59
  [:leave_poll, :oob],
51
- [:unblock, :main]
60
+ [:unblock, :main]
52
61
  ]
53
62
  if Thread.backend.kind == :libev
54
63
  expected += [
data/test/test_socket.rb CHANGED
@@ -73,12 +73,8 @@ class TCPSocketTest < MiniTest::Test
73
73
  def test_read
74
74
  port, server = start_tcp_server_on_random_port
75
75
  server_fiber = spin do
76
- while (socket = server.accept)
77
- spin do
78
- while (data = socket.readpartial(8192))
79
- socket << data
80
- end
81
- end
76
+ server.accept_loop do |socket|
77
+ spin { socket.recv_loop { |data| socket << data } }
82
78
  end
83
79
  end
84
80
 
@@ -98,7 +94,7 @@ class TCPSocketTest < MiniTest::Test
98
94
 
99
95
  buf = +'def'
100
96
  client << 'foobar'
101
- assert_equal 'deffoobar', client.read(6, buf, buffer_pos: -1)
97
+ assert_equal 'deffoobar', client.read(6, buf, -1)
102
98
  assert_equal 'deffoobar', buf
103
99
 
104
100
  client.close
data/test/test_timer.rb CHANGED
@@ -73,7 +73,7 @@ class TimerCancelAfterTest < MiniTest::Test
73
73
  end
74
74
  end
75
75
  t1 = Time.now
76
- assert_in_range 0.01..0.03, t1 - t0 if IS_LINUX
76
+ assert_in_range 0.01..0.04, t1 - t0 if IS_LINUX
77
77
  end
78
78
 
79
79
  def test_timer_cancel_after_with_reset
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.3'
4
+ version: '1.4'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-06-23 00:00:00.000000000 Z
11
+ date: 2023-07-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake-compiler
@@ -191,6 +191,7 @@ files:
191
191
  - docs/extending.md
192
192
  - docs/faq.md
193
193
  - docs/fiber-scheduling.md
194
+ - docs/installation.md
194
195
  - docs/overview.md
195
196
  - docs/readme.md
196
197
  - docs/tutorial.md
@@ -234,6 +235,7 @@ files:
234
235
  - examples/core/shutdown_all_children.rb
235
236
  - examples/core/spin.rb
236
237
  - examples/core/spin_error_backtrace.rb
238
+ - examples/core/stages.rb
237
239
  - examples/core/stream_mockup.rb
238
240
  - examples/core/supervise-process.rb
239
241
  - examples/core/supervisor.rb