polyphony 1.0.2 → 1.1

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: '09f35eaafb1ff569e8a36e1b1e2350e52ce3048b814fb8ccca935ed1a45d1dbd'
4
- data.tar.gz: 0e2296097fdb1d6604c6a41a08880d0cab62052cab4b1b4ebe2ac2ce92bbb737
3
+ metadata.gz: 61fa840f595a0e75da1d6e1b761506b18fa90d5a94b25ce5832d22aae2e08fbd
4
+ data.tar.gz: 7d3a79225f68bb889ff0491ced1249c2679a222a3f79a5c4cf48f9571865ebc4
5
5
  SHA512:
6
- metadata.gz: 80004fb81f991c8cca2c65bf89ed642f90f0e4947a47ea7ac34b59200aebc28e5a73215e8c80e837cd22edf92cf8026332a7df4722a5f5555956648ad5e8c9ee
7
- data.tar.gz: 3eb8022b4fab6b344b422de7791260b1036bd147f7ad8f9a4942e398f0071d8c3468ece6f7d576bb167b4333477643fe1e1b3348601d6fb9db164d8d49775328
6
+ metadata.gz: 32d61cf7e0858e704fc39fe317048e3e531afcf97da70b9b3d1e4b59a078e2f5c8c65a3f72c656b61285df9e5c99d7430938403560ca6e8e7aa5d920dada274e
7
+ data.tar.gz: c5c1488b1cec55810ffccf8116d70e8312ccb7d93875c1047ab7608595eb81fda9601f44ef308b3e1f35319d683b6ceda423284c9cda76c6f44882a7341e681a
@@ -8,7 +8,7 @@ jobs:
8
8
  fail-fast: false
9
9
  matrix:
10
10
  os: [ubuntu-latest, macos-latest]
11
- ruby: ['3.0', '3.1', '3.2']
11
+ ruby: ['3.0', '3.1', '3.2', 'head']
12
12
 
13
13
  name: >-
14
14
  ${{matrix.os}}, ${{matrix.ruby}}
@@ -8,7 +8,7 @@ jobs:
8
8
  fail-fast: false
9
9
  matrix:
10
10
  os: [ubuntu-latest]
11
- ruby: ['3.0', '3.1', '3.2']
11
+ ruby: ['3.0', '3.1', '3.2', 'head']
12
12
 
13
13
  name: >-
14
14
  ${{matrix.os}}, ${{matrix.ruby}}
data/.yardopts CHANGED
@@ -20,6 +20,7 @@
20
20
  docs/readme.md
21
21
  docs/overview.md
22
22
  docs/tutorial.md
23
+ docs/advanced-io.md
23
24
  docs/cheat-sheet.md
24
25
  docs/faq.md
25
26
  docs/concurrency.md
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## 1.1 2023-06-08
2
+
3
+ - Add advanced I/O doc page
4
+ - Add `Fiber#receive_loop` API
5
+
1
6
  ## 1.0.2 2023-05-28
2
7
 
3
8
  - Remove liburing man files from gemspec (#103)
data/README.md CHANGED
@@ -77,6 +77,7 @@ $ gem install polyphony
77
77
 
78
78
  - [Overview](docs/overview.md)
79
79
  - [Tutorial](docs/tutorial.md)
80
+ - [Advanced I/O with Polyphony](docs/advanced-io.md)
80
81
  - [Cheat-Sheet](docs/cheat-sheet.md)
81
82
  - [FAQ](docs/faq.md)
82
83
 
data/TODO.md CHANGED
@@ -1,4 +1,3 @@
1
- - issue #102 - test and see what this is about
2
1
  - Look at RPC benchmark more closely: is there a way to reduce the overhead of
3
2
  the `backend_base_switch_fiber` function?
4
3
 
@@ -15,24 +14,14 @@
15
14
  - Add support for IPv6:
16
15
  https://www.reddit.com/r/ruby/comments/lyen23/understanding_ipv6_and_why_its_important_to_you/
17
16
 
18
- - Check why `throttled_loop` inside of `move_on_after` fails to stop
19
-
20
17
  - Override stock `::SizedQueue` impl with Queue with capacity
21
18
 
22
- - Add support for `break` and `StopIteration` in all loops (with tests)
23
-
24
19
  - More tight loops
25
20
  - `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
21
 
28
22
  - Add support for `close` to io_uring backend
29
23
 
30
- ## Roadmap for Polyphony 1.0
31
-
32
- - Add test that mimics the original design for Monocrono:
33
- - 256 fibers each waiting for a message
34
- - When message received do some blocking work using a `ThreadPool`
35
- - Send messages, collect responses, check for correctness
24
+ ## Roadmap for Polyphony 1.1
36
25
 
37
26
  - io_uring
38
27
  - Use playground.c to find out why we when submitting and waiting for
@@ -117,7 +106,7 @@
117
106
 
118
107
  - Allow locking the scheduler on to one fiber
119
108
  - Add instance var `@fiber_lock`
120
- - API is `Thread#fiber_lock` which sets the fiber_lock instance varwhile
109
+ - API is `Thread#fiber_lock` which sets the fiber_lock instance var while
121
110
  running the block:
122
111
 
123
112
  ```ruby
@@ -127,6 +116,7 @@
127
116
  end
128
117
  end
129
118
  ```
119
+
130
120
  - When `@fiber_lock` is set, it is considered as the only one in the run
131
121
  queue:
132
122
 
@@ -0,0 +1,224 @@
1
+ # @title Advanced I/O with Polyphony
2
+
3
+ ## Using splice for moving data between files and sockets
4
+
5
+ Splice is linux-specific API that lets you move data between two file
6
+ descriptors without copying data between kernel-space and user-space. This is
7
+ not only useful for copying data between two files, but also for implementing
8
+ things such as web servers, where you might need to serve files of an arbitrary
9
+ size. Using splice, you can avoid the cost of having to load a file's content
10
+ into memory, in order to send it to a TCP connection.
11
+
12
+ In order to use `splice`, at least one of the file descriptors involved needs to
13
+ be a pipe. This is because in Linux, pipes are actually kernel buffers. The
14
+ normal way of using splice is that first you splice data from the source fd to
15
+ the pipe (to its *write* fd), and then you splice data from the pipe (from its
16
+ *read* fd) to the destination fd.
17
+
18
+ Here's how we do splicing using Polyphony:
19
+
20
+ ```ruby
21
+ def send_file_using_splice(src, dest)
22
+ # create a pipe. Polyphony::Pipe encapsulates a kernel pipe in a single
23
+ # IO-like object, but we can also use the stock IO.pipe method call that
24
+ # returns two separate pipe fds.
25
+ pipe = Polyphony::Pipe.new
26
+ loop do
27
+ # splices data from src to the pipe
28
+ bytes_spliced = IO.splice(src, pipe, 2**14)
29
+ break if bytes_spliced == 0 # EOF
30
+
31
+ # splices data from the pipe to the dest
32
+ IO.splice(pipe, dest, bytes_spliced)
33
+ end
34
+ end
35
+ ```
36
+
37
+ Let's examine the code above. First of all, we have a loop that repeatedly
38
+ splices data in chunks of 16KB. We break from the loop once EOF is encountered.
39
+ Secondly, on each iteration of the loop we perform two splice operations
40
+ sequentially. So, we need to repeatedly perform two splice operations, one after
41
+ the other. Would there be a better way to do this?
42
+
43
+ Fortunately, Polyphony provides just the tools needed to do that. Firstly, we
44
+ can tell Polyphony to splice data repeatedly until EOF is encountered by passing
45
+ a negative max size:
46
+
47
+ ```ruby
48
+ IO.splice(src, pipe, -2**14)
49
+ ```
50
+
51
+ Secondly, we can perform the two splice operations concurrently, by spinning up
52
+ a separate fiber that performs one of the splice operations, which gives us the
53
+ following:
54
+
55
+ ```ruby
56
+ def send_file_using_splice(src, dest)
57
+ pipe = Polyphony::Pipe.new
58
+ spin do
59
+ IO.splice(src, pipe, -2**14)
60
+ # We need to close the pipe in order to signal EOF for the 2nd splice call.
61
+ pipe.close
62
+ end
63
+ IO.splice(pipe, dest, -2**14)
64
+ end
65
+ ```
66
+
67
+ There are a few things to notice here: While we have two concurrent operations
68
+ running in two separate fibers, their are still inter-dependent in their
69
+ individual progress, as one is filling a kernel buffer, and the other is
70
+ flushing it, and thus the progress of whole will be bound by the slowest
71
+ operation.
72
+
73
+ Imagine an HTTP server that serves a large file to a slow client, or a client
74
+ with a bad network connection. The web server is perfectly capable of reading
75
+ the file from its disk very fast, but sending data to the HTTP client can be
76
+ much much slower. The second splice operation, splicing from the pipe to the
77
+ destination, will flush the kernel much more slowly that it is being filled. At
78
+ a certain point, the buffer is full, and the first splice operation from the
79
+ source to the pipe cannot continue. It will need to wait for the other splice
80
+ operation to progress, in order to continue filling the buffer. This is called
81
+ back-pressure propagation, and we get it automatically.
82
+
83
+ So let's look at all the things we didn't need to do: we didn't need to read
84
+ data into a Ruby string (which is costly in CPU time, in memory, and eventually
85
+ in GC pressure), we didn't need to manage a buffer and take care of
86
+ synchronizing access to the buffer. We got to move data from the source to the
87
+ destination concurrently, and we got back-pressure propagation for free. Can we
88
+ do any better than that?
89
+
90
+ Actually, we can! Polyphony also provides an API that does all of the above in a
91
+ single method call:
92
+
93
+ ```ruby
94
+ def send_file_using_splice(src, dest)
95
+ IO.double_splice(src, dest)
96
+ end
97
+ ```
98
+
99
+ The `IO.double_splice` creates a pipe and repeatedly splices data concurrently
100
+ from the source to pipe and from the pipe to the destination until the source is
101
+ exhausted. All this, without needing to instantiate a `Polyphony::Pipe` object,
102
+ and without needing to spin up a second fiber, further minimizing memory use and
103
+ GC pressure.
104
+
105
+ ## Compressing and decompressing in-flight data
106
+
107
+ You might be familiar with Ruby's [zlib](https://github.com/ruby/zlib) gem (docs
108
+ [here](https://rubyapi.org/3.2/o/zlib)), which can be used to compress and
109
+ uncompress data using the popular gzip format. Imagine we want to implement an
110
+ HTTP server that can serve files compresszed using gzip:
111
+
112
+ ```ruby
113
+ def serve_compressed_file(socket, file)
114
+ # we leave aside sending the HTTP headers and dealing with transfer encoding
115
+ compressed = Zlib.gzip(file.read)
116
+ socket << compressed
117
+ end
118
+ ```
119
+
120
+ In the above example, we have read the file contents into a Ruby string, then
121
+ passed the contents to `Zlib.gzip`, which returned the compressed contents in
122
+ another Ruby string, then wrote the compressed data to the socket. We can see
123
+ how this can lead to large allocations of memory (if the file is large), and
124
+ more pressure on the Ruby GC. How can we improve this?
125
+
126
+ One way would be to utilise Zlib's `GzipWriter` class:
127
+
128
+ ```ruby
129
+ def serve_compressed_file(socket, file)
130
+ # we leave aside sending the HTTP headers and dealing with transfer encoding
131
+ compressor = Zlib::GzipWriter.new(socket)
132
+ while (data = file.read(2**14))
133
+ compressor << data
134
+ end
135
+ end
136
+ ```
137
+
138
+ In the above code, we instantiate a `Zlib::GzipWriter`, which we then feed with
139
+ data from the file, with the compressor object writing the compressed data to
140
+ the socket. Notice how we still need to read the file contents into a Ruby
141
+ string and then pass it to the compressor. Could we avoid this? With Polyphony
142
+ the answer is yes we can!
143
+
144
+ Polyphony provides a number of APIs for compressing and decompressing data on
145
+ the fly between two file descriptors (i.e. `IO` instances), namely: `IO.gzip`,
146
+ `IO.gunzip`, `IO.deflate` and `IO.inflate`. Let's see how this can be used to
147
+ serve gzipped data to an HTTP client:
148
+
149
+ ```ruby
150
+ def serve_compressed_file(socket, file)
151
+ IO.gzip(file, socket) # and that's it!
152
+ end
153
+ ```
154
+
155
+ Using the `IO.gzip` API provided by Polyphony, we completely avoid instantiating
156
+ Ruby strings into which data is read, and in fact we avoid allocating any
157
+ buffers on the heap (apart from what `zlib` might be doing). *And* we get to
158
+ move data *and compress it* between the given file and the socket using a single
159
+ method call!
160
+
161
+ ## Feeding data from a file descriptor to a parser
162
+
163
+ Some times we want to process data from a given file or socket by passing
164
+ through some object that parses the data, or otherwise manipulates it. Normally,
165
+ we would write a loop that repeatedly reads the data from the source, then
166
+ passes it to the parser object. Imagine we have data transmitted using the
167
+ `MessagePack` format that we need to convert back into its original form. We
168
+ might do something like this:
169
+
170
+ ```ruby
171
+ def with_message_pack_data_from_io(io, &block)
172
+ unpacker = MessagePack::Unpacker.new
173
+ while (data = io.read(2**14))
174
+ unpacker.feed_each(data, &block)
175
+ end
176
+ end
177
+
178
+ # Which we can use as follows:
179
+ with_message_pack_data_from_io(socket) do |o|
180
+ puts "got: #{o.inspect}"
181
+ end
182
+ ```
183
+
184
+ Polyphony provides some APIs that help us write less code, and even optimize the
185
+ performance of our code. Let's look at the `IO#read_loop` (or `IO#recv_loop` for
186
+ sockets) API:
187
+
188
+ ```ruby
189
+ def with_message_pack_data_from_io(io, &block)
190
+ unpacker = MessagePack::Unpacker.new
191
+ io.read_loop do |data|
192
+ unpacker.feed_each(data, &block)
193
+ end
194
+ end
195
+ ```
196
+
197
+ In the above code, we replaced our `while` loop with a call to `IO#read_loop`,
198
+ which yields read data to the block given to it. In the block, we pass the data
199
+ to the MessagePack unpacker. While this does not like much different than the
200
+ previous implementation, the `IO#read_loop` API implements a tight loop at the
201
+ C-extension level, that provides slightly better performance.
202
+
203
+ But Polyphony goes even further than that and provides a `IO#feed_loop` API that
204
+ lets us feed read data to a given parser or processor object. Here's how we can
205
+ use it:
206
+
207
+ ```ruby
208
+ def with_message_pack_data_from_io(io, &block)
209
+ unpacker = MessagePack::Unpacker.new
210
+ io.feed_loop(unpacker, :feed_each, &block)
211
+ end
212
+ ```
213
+
214
+ With `IO#feed_loop` we get to write even less code, and as with `IO#read_loop`,
215
+ `IO#feed_loop` is implemented at the C-extension level using a tight loop that
216
+ maximizes performance.
217
+
218
+ ## Conclusion
219
+
220
+ In this article we have looked at some of the advanced I/O functionality
221
+ provided by Polyphony, which lets us write less code, have it run faster, and
222
+ minimize memory allocations and pressure on the Ruby GC. Feel free to browse the
223
+ [IO examples](https://github.com/digital-fabric/polyphony/tree/master/examples/io)
224
+ included in Polyphony.
data/docs/cheat-sheet.md CHANGED
@@ -71,7 +71,7 @@ def calculate_some_stuff(n)
71
71
  acc += big_calc(acc, i)
72
72
  snooze if (i % 1000) == 0
73
73
  end
74
- end
74
+ end
75
75
  ```
76
76
 
77
77
  ### Suspend fiber
@@ -191,7 +191,7 @@ dest2.tee_from(source, 8192)
191
191
  dest1.splice_from(source, 8192)
192
192
  # or:
193
193
  IO.tee(src, dest2)
194
- IO.splice(src, dest2)
194
+ IO.splice(src, dest1)
195
195
  ```
196
196
 
197
197
  ### Splice data between two arbitrary file descriptors, without creating a pipe
data/docs/readme.md CHANGED
@@ -79,6 +79,7 @@ $ gem install polyphony
79
79
 
80
80
  - {file:/docs/overview.md Overview}
81
81
  - {file:/docs/tutorial.md Tutorial}
82
+ - {file:/docs/advanced-io.md Advanced I/O with Polyphony}
82
83
  - {file:/docs/cheat-sheet.md Cheat-Sheet}
83
84
  - {file:/docs/faq.md FAQ}
84
85
 
@@ -94,11 +94,11 @@ def bm_fiber_raw
94
94
  $server_raw.transfer 3
95
95
  end
96
96
 
97
- p bm_raw
98
- p bm_send
99
- p bm_fiber
100
- p bm_fiber_optimized
101
- p bm_fiber_single
97
+ # p bm_raw
98
+ # p bm_send
99
+ # p bm_fiber
100
+ # p bm_fiber_optimized
101
+ # p bm_fiber_single
102
102
  p bm_fiber_raw
103
103
  p bm_fiber_schedule
104
104
 
@@ -116,17 +116,17 @@ end
116
116
 
117
117
  puts "warming up JIT..."
118
118
 
119
- 3.times do
120
- warmup_jit
121
- sleep 1
122
- end
119
+ # 3.times do
120
+ # warmup_jit
121
+ # sleep 1
122
+ # end
123
123
 
124
124
  Benchmark.ips do |x|
125
- x.report("raw") { bm_raw }
126
- x.report("send") { bm_send }
127
- x.report("fiber") { bm_fiber }
128
- x.report("fiber_optimized") { bm_fiber_optimized }
129
- x.report("fiber_single") { bm_fiber_single }
125
+ # x.report("raw") { bm_raw }
126
+ # x.report("send") { bm_send }
127
+ # x.report("fiber") { bm_fiber }
128
+ # x.report("fiber_optimized") { bm_fiber_optimized }
129
+ # x.report("fiber_single") { bm_fiber_single }
130
130
  x.report("fiber_raw") { bm_fiber_raw }
131
131
  x.report("fiber_schedule") { bm_fiber_schedule }
132
132
  x.compare!
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ class Stream
7
+ def initialize(io)
8
+ @io = io
9
+ @buffer = +''
10
+ @length = 0
11
+ @pos = 0
12
+ end
13
+
14
+ def getbyte
15
+ if @pos == @length
16
+ return nil if !fill_buffer
17
+ end
18
+ byte = @buffer[@pos].getbyte(0)
19
+ @pos += 1
20
+ byte
21
+ end
22
+
23
+ def getc
24
+ if @pos == @length
25
+ return nil if !fill_buffer
26
+ end
27
+ char = @buffer[@pos]
28
+ @pos += 1
29
+ char
30
+ end
31
+
32
+ def ungetc(c)
33
+ @buffer.insert(@pos, c)
34
+ @length += 1
35
+ c
36
+ end
37
+
38
+ def gets
39
+ end
40
+
41
+ def read
42
+ end
43
+
44
+ def readpartial
45
+ end
46
+
47
+ private
48
+
49
+ def fill_buffer
50
+ Polyphony.backend_read(@io, @buffer, 8192, false, -1)
51
+ @length = @buffer.size
52
+ end
53
+ end
54
+
55
+ i, o = IO.pipe
56
+ s = Stream.new(i)
57
+
58
+ f = spin do
59
+ loop do
60
+ b = s.getbyte
61
+ p getbyte: b
62
+ s.ungetc(b.to_s) if rand > 0.5
63
+ end
64
+ end
65
+
66
+ o << 'hello'
67
+ sleep 0.1
68
+
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ i = 0
7
+ value = move_on_after(1, with_value: 42) do
8
+ throttled_loop(20) do
9
+ p (i += 1)
10
+ end
11
+ end
12
+
13
+ p value: value
@@ -389,12 +389,12 @@ inline void set_fd_blocking_mode(int fd, int blocking) {
389
389
  #endif
390
390
  }
391
391
 
392
- inline void io_verify_blocking_mode(rb_io_t *fptr, VALUE io, VALUE blocking) {
392
+ inline void io_verify_blocking_mode(VALUE io, int fd, VALUE blocking) {
393
393
  VALUE blocking_mode = rb_ivar_get(io, ID_ivar_blocking_mode);
394
394
  if (blocking == blocking_mode) return;
395
395
 
396
396
  rb_ivar_set(io, ID_ivar_blocking_mode, blocking);
397
- set_fd_blocking_mode(fptr->fd, blocking == Qtrue);
397
+ set_fd_blocking_mode(fd, blocking == Qtrue);
398
398
  }
399
399
 
400
400
  inline void backend_run_idle_tasks(struct Backend_base *base) {
@@ -455,9 +455,7 @@ VALUE Backend_stats(VALUE self) {
455
455
  }
456
456
 
457
457
  VALUE Backend_verify_blocking_mode(VALUE self, VALUE io, VALUE blocking) {
458
- rb_io_t *fptr;
459
- GetOpenFile(io, fptr);
460
- io_verify_blocking_mode(fptr, io, blocking);
458
+ io_verify_blocking_mode(io, rb_io_descriptor(io), blocking);
461
459
  return self;
462
460
  }
463
461
 
@@ -10,6 +10,15 @@
10
10
  #include "ruby/io.h"
11
11
  #include "runqueue.h"
12
12
 
13
+ #ifndef HAVE_RB_IO_DESCRIPTOR
14
+ static int rb_io_descriptor_fallback(VALUE io) {
15
+ rb_io_t *fptr;
16
+ GetOpenFile(io, fptr);
17
+ return fptr->fd;
18
+ }
19
+ #define rb_io_descriptor rb_io_descriptor_fallback
20
+ #endif
21
+
13
22
  struct backend_stats {
14
23
  unsigned int runqueue_size;
15
24
  unsigned int runqueue_length;
@@ -145,7 +154,7 @@ VALUE Backend_stats(VALUE self);
145
154
  VALUE Backend_verify_blocking_mode(VALUE self, VALUE io, VALUE blocking);
146
155
  void backend_run_idle_tasks(struct Backend_base *base);
147
156
  void set_fd_blocking_mode(int fd, int blocking);
148
- void io_verify_blocking_mode(rb_io_t *fptr, VALUE io, VALUE blocking);
157
+ void io_verify_blocking_mode(VALUE io, int fd, VALUE blocking);
149
158
  void backend_setup_stats_symbols();
150
159
  int backend_getaddrinfo(VALUE host, VALUE port, struct sockaddr **ai_addr);
151
160
  VALUE name_to_addrinfo(void *name, socklen_t len);
@@ -28,9 +28,9 @@ VALUE SYM_write;
28
28
  VALUE eArgumentError;
29
29
 
30
30
  #ifdef POLYPHONY_UNSET_NONBLOCK
31
- #define io_unset_nonblock(fptr, io) io_verify_blocking_mode(fptr, io, Qtrue)
31
+ #define io_unset_nonblock(io, fd) io_verify_blocking_mode(io, fd, Qtrue)
32
32
  #else
33
- #define io_unset_nonblock(fptr, io)
33
+ #define io_unset_nonblock(io, fd)
34
34
  #endif
35
35
 
36
36
  typedef struct Backend_t {
@@ -389,10 +389,10 @@ static inline int fd_from_io(VALUE io, rb_io_t **fptr, int write_mode, int recti
389
389
  if (underlying_io != Qnil) io = underlying_io;
390
390
 
391
391
  GetOpenFile(io, *fptr);
392
- io_unset_nonblock(*fptr, io);
392
+ int fd = rb_io_descriptor(io);
393
+ io_unset_nonblock(io, fd);
393
394
  if (rectify_file_pos) rectify_io_file_pos(*fptr);
394
-
395
- return (*fptr)->fd;
395
+ return fd;
396
396
  }
397
397
  }
398
398
 
@@ -1376,7 +1376,7 @@ VALUE Backend_wait_io(VALUE self, VALUE io, VALUE write) {
1376
1376
 
1377
1377
  // if (fd < 0) return Qnil;
1378
1378
 
1379
- // io_unset_nonblock(fptr, io);
1379
+ // io_unset_nonblock(io, fd);
1380
1380
 
1381
1381
  // ctx = context_store_acquire(&backend->store, OP_CLOSE);
1382
1382
  // sqe = io_uring_backend_get_sqe(backend);
@@ -277,10 +277,10 @@ static inline int fd_from_io(VALUE io, rb_io_t **fptr, int write_mode, int recti
277
277
  if (underlying_io != Qnil) io = underlying_io;
278
278
 
279
279
  GetOpenFile(io, *fptr);
280
- io_verify_blocking_mode(*fptr, io, Qfalse);
280
+ int fd = rb_io_descriptor(io);
281
+ io_verify_blocking_mode(io, fd, Qfalse);
281
282
  if (rectify_file_pos) rectify_io_file_pos(*fptr);
282
-
283
- return (*fptr)->fd;
283
+ return fd;
284
284
  }
285
285
  }
286
286
 
@@ -681,7 +681,7 @@ VALUE Backend_accept(VALUE self, VALUE server_socket, VALUE socket_class) {
681
681
  fp->fd = fd;
682
682
  fp->mode = FMODE_READWRITE | FMODE_DUPLEX;
683
683
  rb_io_ascii8bit_binmode(socket);
684
- io_verify_blocking_mode(fp, socket, Qfalse);
684
+ io_verify_blocking_mode(socket, fd, Qfalse);
685
685
  rb_io_synchronized(fp);
686
686
 
687
687
  // if (rsock_do_not_reverse_lookup) {
@@ -736,7 +736,7 @@ VALUE Backend_accept_loop(VALUE self, VALUE server_socket, VALUE socket_class) {
736
736
  fp->fd = fd;
737
737
  fp->mode = FMODE_READWRITE | FMODE_DUPLEX;
738
738
  rb_io_ascii8bit_binmode(socket);
739
- io_verify_blocking_mode(fp, socket, Qfalse);
739
+ io_verify_blocking_mode(socket, fd, Qfalse);
740
740
  rb_io_synchronized(fp);
741
741
 
742
742
  rb_yield(socket);
@@ -98,4 +98,10 @@ end
98
98
 
99
99
  have_header('ruby/io/buffer.h')
100
100
 
101
+ have_func('rb_io_path')
102
+ have_func('rb_io_descriptor')
103
+ have_func('rb_io_get_write_io')
104
+ have_func('rb_io_closed_p')
105
+ have_func('rb_io_open_descriptor')
106
+
101
107
  create_makefile 'polyphony_ext'
@@ -1,3 +1,4 @@
1
+ #include <stdnoreturn.h>
1
2
  #include "polyphony.h"
2
3
 
3
4
  ID ID_ivar_auto_watcher;
@@ -129,7 +130,7 @@ VALUE Fiber_send(VALUE self, VALUE msg) {
129
130
  return self;
130
131
  }
131
132
 
132
- /* Receive's a message from the fiber's mailbox. If no message is available,
133
+ /* Receives a message from the fiber's mailbox. If no message is available,
133
134
  * waits for a message to be sent to it.
134
135
  *
135
136
  * @return [any] received message
@@ -144,6 +145,24 @@ VALUE Fiber_receive(VALUE self) {
144
145
  return Queue_shift(0, 0, mailbox);
145
146
  }
146
147
 
148
+ /* Receives messages from the fiber's mailbox in an infinite loop.
149
+ *
150
+ */
151
+
152
+ noreturn VALUE Fiber_receive_loop(VALUE self) {
153
+ VALUE mailbox = rb_ivar_get(self, ID_ivar_mailbox);
154
+ if (mailbox == Qnil) {
155
+ mailbox = rb_funcall(cQueue, ID_new, 0);
156
+ rb_ivar_set(self, ID_ivar_mailbox, mailbox);
157
+ }
158
+
159
+ while (1) {
160
+ VALUE msg = Queue_shift(0, 0,mailbox);
161
+ rb_yield(msg);
162
+ RB_GC_GUARD(msg);
163
+ }
164
+ }
165
+
147
166
  /* Returns the fiber's mailbox.
148
167
  *
149
168
  * @return [Queue]
@@ -201,6 +220,7 @@ void Init_Fiber(void) {
201
220
  rb_define_method(cFiber, "<<", Fiber_send, 1);
202
221
  rb_define_method(cFiber, "send", Fiber_send, 1);
203
222
  rb_define_method(cFiber, "receive", Fiber_receive, 0);
223
+ rb_define_method(cFiber, "receive_loop", Fiber_receive_loop, 0);
204
224
  rb_define_method(cFiber, "receive_all_pending", Fiber_receive_all_pending, 0);
205
225
  rb_define_method(cFiber, "mailbox", Fiber_mailbox, 0);
206
226