polyphony 1.0.2 → 1.1.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.
@@ -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
 
@@ -7,7 +7,7 @@ class ::IO
7
7
  class << self
8
8
  # @!visibility private
9
9
  alias_method :orig_binread, :binread
10
-
10
+
11
11
  # @!visibility private
12
12
  def binread(name, length = nil, offset = nil)
13
13
  File.open(name, 'rb:ASCII-8BIT') do |f|
@@ -15,10 +15,10 @@ class ::IO
15
15
  length ? f.read(length) : f.read
16
16
  end
17
17
  end
18
-
18
+
19
19
  # @!visibility private
20
20
  alias_method :orig_binwrite, :binwrite
21
-
21
+
22
22
  # @!visibility private
23
23
  def binwrite(name, string, offset = nil)
24
24
  File.open(name, 'wb:ASCII-8BIT') do |f|
@@ -26,13 +26,13 @@ class ::IO
26
26
  f.write(string)
27
27
  end
28
28
  end
29
-
29
+
30
30
  # @!visibility private
31
31
  EMPTY_HASH = {}.freeze
32
-
32
+
33
33
  # @!visibility private
34
34
  alias_method :orig_foreach, :foreach
35
-
35
+
36
36
  # @!visibility private
37
37
  def foreach(name, sep = $/, limit = nil, getline_args = EMPTY_HASH, &block)
38
38
  if sep.is_a?(Integer)
@@ -43,10 +43,10 @@ class ::IO
43
43
  f.each_line(sep, limit, chomp: getline_args[:chomp], &block)
44
44
  end
45
45
  end
46
-
46
+
47
47
  # @!visibility private
48
48
  alias_method :orig_read, :read
49
-
49
+
50
50
  # @!visibility private
51
51
  def read(name, length = nil, offset = nil, opt = EMPTY_HASH)
52
52
  if length.is_a?(Hash)
@@ -58,17 +58,17 @@ class ::IO
58
58
  length ? f.read(length) : f.read
59
59
  end
60
60
  end
61
-
61
+
62
62
  # alias_method :orig_readlines, :readlines
63
63
  # def readlines(name, sep = $/, limit = nil, getline_args = EMPTY_HASH)
64
64
  # File.open(name, 'r') do |f|
65
65
  # f.readlines(sep, limit, getline_args)
66
66
  # end
67
67
  # end
68
-
68
+
69
69
  # @!visibility private
70
70
  alias_method :orig_write, :write
71
-
71
+
72
72
  # @!visibility private
73
73
  def write(name, string, offset = nil, opt = EMPTY_HASH)
74
74
  File.open(name, opt[:mode] || 'w') do |f|
@@ -76,18 +76,18 @@ class ::IO
76
76
  f.write(string)
77
77
  end
78
78
  end
79
-
79
+
80
80
  # @!visibility private
81
81
  alias_method :orig_popen, :popen
82
-
83
-
82
+
83
+
84
84
  # @!visibility private
85
85
  def popen(cmd, mode = 'r')
86
86
  return orig_popen(cmd, mode) unless block_given?
87
-
87
+
88
88
  Open3.popen2(cmd) { |_i, o, _t| yield o }
89
89
  end
90
-
90
+
91
91
  # Splices from one IO to another IO. At least one of the IOs must be a pipe.
92
92
  # If maxlen is negative, splices repeatedly using absolute value of maxlen
93
93
  # until EOF is encountered.
@@ -99,9 +99,9 @@ class ::IO
99
99
  def splice(src, dest, maxlen)
100
100
  Polyphony.backend_splice(src, dest, maxlen)
101
101
  end
102
-
103
- # Creates a pipe and splices data between the two given IOs using the
104
- # pipe, splicing until EOF.
102
+
103
+ # Creates a pipe and splices data between the two given IOs, using the pipe,
104
+ # splicing until EOF.
105
105
  #
106
106
  # @param src [IO, Polyphony::Pipe] source to splice from
107
107
  # @param dest [IO, Polyphony::Pipe] destination to splice to
@@ -109,7 +109,7 @@ class ::IO
109
109
  def double_splice(src, dest)
110
110
  Polyphony.backend_double_splice(src, dest)
111
111
  end
112
-
112
+
113
113
  # Tees data from the source to the desination.
114
114
  #
115
115
  # @param src [IO, Polyphony::Pipe] source to tee from
@@ -125,7 +125,7 @@ class ::IO
125
125
  def double_splice(src, dest)
126
126
  raise NotImplementedError
127
127
  end
128
-
128
+
129
129
  # @!visibility private
130
130
  def tee(src, dest, maxlen)
131
131
  raise NotImplementedError
@@ -140,51 +140,51 @@ class ::IO
140
140
  def __read_method__
141
141
  :backend_read
142
142
  end
143
-
143
+
144
144
  # @!visibility private
145
145
  def __write_method__
146
146
  :backend_write
147
147
  end
148
-
148
+
149
149
  # def each(sep = $/, limit = nil, chomp: nil)
150
150
  # sep, limit = $/, sep if sep.is_a?(Integer)
151
151
  # end
152
152
  # alias_method :each_line, :each
153
-
153
+
154
154
  # def each_byte
155
155
  # end
156
-
156
+
157
157
  # def each_char
158
158
  # end
159
-
159
+
160
160
  # def each_codepoint
161
161
  # end
162
-
162
+
163
163
  # @!visibility private
164
164
  alias_method :orig_getbyte, :getbyte
165
-
166
-
165
+
166
+
167
167
  # @!visibility private
168
168
  def getbyte
169
169
  char = getc
170
- char ? char.getbyte(0) : nil
170
+ char&.getbyte(0)
171
171
  end
172
-
172
+
173
173
  # @!visibility private
174
174
  alias_method :orig_getc, :getc
175
-
176
-
175
+
176
+
177
177
  # @!visibility private
178
178
  def getc
179
179
  return @read_buffer.slice!(0) if @read_buffer && !@read_buffer.empty?
180
-
180
+
181
181
  @read_buffer ||= +''
182
182
  Polyphony.backend_read(self, @read_buffer, 8192, false, -1)
183
183
  return @read_buffer.slice!(0) if !@read_buffer.empty?
184
-
184
+
185
185
  nil
186
186
  end
187
-
187
+
188
188
  # @!visibility private
189
189
  def ungetc(c)
190
190
  c = c.chr if c.is_a?(Integer)
@@ -195,59 +195,59 @@ class ::IO
195
195
  end
196
196
  end
197
197
  alias_method :ungetbyte, :ungetc
198
-
198
+
199
199
  # @!visibility private
200
200
  alias_method :orig_read, :read
201
-
202
-
201
+
202
+
203
203
  # @!visibility private
204
204
  def read(len = nil, buf = nil, buf_pos = 0)
205
205
  return '' if len == 0
206
-
206
+
207
207
  if buf
208
208
  return Polyphony.backend_read(self, buf, len, true, buf_pos)
209
209
  end
210
-
210
+
211
211
  @read_buffer ||= +''
212
212
  result = Polyphony.backend_read(self, @read_buffer, len, true, -1)
213
213
  return nil unless result
214
-
214
+
215
215
  already_read = @read_buffer
216
216
  @read_buffer = +''
217
217
  already_read
218
218
  end
219
-
219
+
220
220
  # @!visibility private
221
221
  alias_method :orig_readpartial, :read
222
-
222
+
223
223
  # @!visibility private
224
224
  def readpartial(len, str = +'', buffer_pos = 0, raise_on_eof = true)
225
225
  result = Polyphony.backend_read(self, str, len, false, buffer_pos)
226
226
  raise EOFError if !result && raise_on_eof
227
-
227
+
228
228
  result
229
229
  end
230
-
230
+
231
231
  # @!visibility private
232
232
  alias_method :orig_write, :write
233
-
233
+
234
234
  # @!visibility private
235
235
  def write(str, *args)
236
236
  Polyphony.backend_write(self, str, *args)
237
237
  end
238
-
238
+
239
239
  # @!visibility private
240
240
  alias_method :orig_write_chevron, :<<
241
-
241
+
242
242
  # @!visibility private
243
243
  def <<(str)
244
244
  Polyphony.backend_write(self, str)
245
245
  self
246
246
  end
247
-
247
+
248
248
  # @!visibility private
249
249
  alias_method :orig_gets, :gets
250
-
250
+
251
251
  # @!visibility private
252
252
  def gets(sep = $/, _limit = nil, _chomp: nil)
253
253
  if sep.is_a?(Integer)
@@ -255,20 +255,20 @@ class ::IO
255
255
  _limit = sep
256
256
  end
257
257
  sep_size = sep.bytesize
258
-
258
+
259
259
  @read_buffer ||= +''
260
-
260
+
261
261
  while true
262
262
  idx = @read_buffer.index(sep)
263
263
  return @read_buffer.slice!(0, idx + sep_size) if idx
264
-
264
+
265
265
  result = readpartial(8192, @read_buffer, -1)
266
266
  return nil unless result
267
267
  end
268
268
  rescue EOFError
269
269
  return nil
270
270
  end
271
-
271
+
272
272
  # @!visibility private
273
273
  def each_line(sep = $/, limit = nil, chomp: false)
274
274
  if sep.is_a?(Integer)
@@ -276,48 +276,48 @@ class ::IO
276
276
  sep = $/
277
277
  end
278
278
  sep_size = sep.bytesize
279
-
280
-
279
+
280
+
281
281
  @read_buffer ||= +''
282
-
282
+
283
283
  while true
284
284
  while (idx = @read_buffer.index(sep))
285
285
  line = @read_buffer.slice!(0, idx + sep_size)
286
286
  line = line.chomp if chomp
287
287
  yield line
288
288
  end
289
-
289
+
290
290
  result = readpartial(8192, @read_buffer, -1)
291
291
  return self if !result
292
292
  end
293
293
  rescue EOFError
294
294
  return self
295
295
  end
296
-
296
+
297
297
  # def print(*args)
298
298
  # end
299
-
299
+
300
300
  # def printf(format, *args)
301
301
  # end
302
-
302
+
303
303
  # def putc(obj)
304
304
  # end
305
-
305
+
306
306
  # @!visibility private
307
307
  LINEFEED = "\n"
308
308
  # @!visibility private
309
309
  LINEFEED_RE = /\n$/.freeze
310
-
310
+
311
311
  # @!visibility private
312
312
  alias_method :orig_puts, :puts
313
-
313
+
314
314
  # @!visibility private
315
315
  def puts(*args)
316
316
  if args.empty?
317
317
  write LINEFEED
318
318
  return
319
319
  end
320
-
320
+
321
321
  idx = 0
322
322
  while idx < args.size
323
323
  arg = args[idx]
@@ -329,26 +329,26 @@ class ::IO
329
329
  idx += 2
330
330
  end
331
331
  end
332
-
332
+
333
333
  write(*args)
334
334
  nil
335
335
  end
336
-
336
+
337
337
  # def readbyte
338
338
  # end
339
-
339
+
340
340
  # def readchar
341
341
  # end
342
-
342
+
343
343
  # def readline(sep = $/, limit = nil, chomp: nil)
344
344
  # end
345
-
345
+
346
346
  # def readlines(sep = $/, limit = nil, chomp: nil)
347
347
  # end
348
-
348
+
349
349
  # @!visibility private
350
350
  alias_method :orig_write_nonblock, :write_nonblock
351
-
351
+
352
352
  # @!visibility private
353
353
  def write_nonblock(string, _options = {})
354
354
  write(string)
@@ -396,7 +396,7 @@ end
396
396
  # @return [IO] self
397
397
  def wait_readable(timeout = nil)
398
398
  return self if @read_buffer && @read_buffer.size > 0
399
-
399
+
400
400
  if timeout
401
401
  move_on_after(timeout) do
402
402
  Polyphony.backend_wait_io(self, false)
@@ -171,6 +171,12 @@ class ::Object
171
171
  Fiber.current.receive
172
172
  end
173
173
 
174
+ # Receives messages in an infinite loop from the current fiber's mailbox,
175
+ # passing them to the given block.
176
+ def receive_loop(&block)
177
+ Fiber.current.receive_loop(&block)
178
+ end
179
+
174
180
  # Returns all messages currently pending on the current fiber's mailbox.
175
181
  #
176
182
  # @return [Array] array of received messages
@@ -75,15 +75,15 @@ class ::Socket < ::BasicSocket
75
75
  # @return [String] buffer used for reading
76
76
  def read(len = nil, buf = nil, buf_pos = 0)
77
77
  return '' if len == 0
78
-
78
+
79
79
  if buf
80
80
  return Polyphony.backend_read(self, buf, len, true, buf_pos)
81
81
  end
82
-
82
+
83
83
  @read_buffer ||= +''
84
84
  result = Polyphony.backend_read(self, @read_buffer, len, true, -1)
85
85
  return nil unless result
86
-
86
+
87
87
  already_read = @read_buffer
88
88
  @read_buffer = +''
89
89
  already_read
@@ -339,15 +339,15 @@ class ::TCPSocket < ::IPSocket
339
339
  # @return [String] buffer used for reading
340
340
  def read(len = nil, buf = nil, buf_pos = 0)
341
341
  return '' if len == 0
342
-
342
+
343
343
  if buf
344
344
  return Polyphony.backend_read(self, buf, len, true, buf_pos)
345
345
  end
346
-
346
+
347
347
  @read_buffer ||= +''
348
348
  result = Polyphony.backend_read(self, @read_buffer, len, true, -1)
349
349
  return nil unless result
350
-
350
+
351
351
  already_read = @read_buffer
352
352
  @read_buffer = +''
353
353
  already_read
@@ -548,15 +548,15 @@ class ::UNIXSocket < ::BasicSocket
548
548
  # @return [String] buffer used for reading
549
549
  def read(len = nil, buf = nil, buf_pos = 0)
550
550
  return '' if len == 0
551
-
551
+
552
552
  if buf
553
553
  return Polyphony.backend_read(self, buf, len, true, buf_pos)
554
554
  end
555
-
555
+
556
556
  @read_buffer ||= +''
557
557
  result = Polyphony.backend_read(self, @read_buffer, len, true, -1)
558
558
  return nil unless result
559
-
559
+
560
560
  already_read = @read_buffer
561
561
  @read_buffer = +''
562
562
  already_read
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Polyphony
4
4
  # @!visibility private
5
- VERSION = '1.0.2'
5
+ VERSION = '1.1.1'
6
6
  end
data/polyphony.gemspec CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |s|
8
8
  s.author = 'Sharon Rosner'
9
9
  s.email = 'sharon@noteflakes.com'
10
10
  s.files = `git ls-files --recurse-submodules`.split.reject { |fn| fn =~ /liburing\/man/ }
11
- s.homepage = 'https://digital-fabric.github.io/polyphony'
11
+ s.homepage = 'https://github.com/digital-fabric/polyphony'
12
12
  s.metadata = {
13
13
  "source_code_uri" => "https://github.com/digital-fabric/polyphony",
14
14
  "documentation_uri" => "https://www.rubydoc.info/gems/polyphony",
data/test/stress.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  count = ARGV[0] ? ARGV[0].to_i : 100
4
4
  test_name = ARGV[1]
5
5
 
6
- $test_cmd = +'ruby test/run.rb'
6
+ $test_cmd = +'ruby test/test_scenarios.rb'
7
7
  if test_name
8
8
  $test_cmd << " --name #{test_name}"
9
9
  end
data/test/test_fiber.rb CHANGED
@@ -975,6 +975,50 @@ class MailboxTest < MiniTest::Test
975
975
  assert_equal ['foo'] * 100, messages
976
976
  end
977
977
 
978
+ def test_receive_loop
979
+ buffer = []
980
+ f = spin do
981
+ receive_loop { |msg| buffer << msg }
982
+ ensure
983
+ buffer << :done
984
+ end
985
+
986
+ snooze
987
+ f << :foo
988
+ snooze
989
+ assert_equal [:foo], buffer
990
+
991
+ f << :bar
992
+ snooze
993
+ assert_equal [:foo, :bar], buffer
994
+
995
+ f.kill
996
+ f.join
997
+ assert_equal [:foo, :bar, :done], buffer
998
+ end
999
+
1000
+ def test_receive_loop_break
1001
+ buffer = []
1002
+ f = spin do
1003
+ receive_loop do |msg|
1004
+ buffer << msg
1005
+ break if msg == :bar
1006
+ end
1007
+ buffer << :done
1008
+ end
1009
+
1010
+ snooze
1011
+ f << :foo
1012
+ snooze
1013
+ assert_equal [:foo], buffer
1014
+
1015
+ f << :bar
1016
+ snooze
1017
+ assert_equal [:foo, :bar, :done], buffer
1018
+
1019
+ assert_equal :dead, f.state
1020
+ end
1021
+
978
1022
  def test_receive_exception
979
1023
  e = RuntimeError.new 'foo'
980
1024
  spin { Fiber.current.parent << e }
@@ -1287,4 +1331,4 @@ class DebugTest < MiniTest::Test
1287
1331
  10.times { snooze }
1288
1332
  assert_equal [0, 1, 2], buf
1289
1333
  end
1290
- end
1334
+ end
data/test/test_io.rb CHANGED
@@ -321,11 +321,36 @@ class IOTest < MiniTest::Test
321
321
  assert_equal 6, len
322
322
  end
323
323
 
324
+ def test_splice_class_method_with_eof_detection
325
+ i1, o1 = IO.pipe
326
+ i2, o2 = IO.pipe
327
+ splice_lens = []
328
+
329
+ spin {
330
+ loop {
331
+ len = IO.splice(i1, o2, 1000)
332
+ splice_lens << len
333
+ break if len == 0
334
+ }
335
+
336
+ o2.close
337
+ }
338
+
339
+ o1.write('foobar')
340
+ snooze
341
+ o1.close
342
+
343
+ result = i2.read
344
+ assert_equal 'foobar', result
345
+ assert_equal [6, 0], splice_lens
346
+ end
347
+
324
348
  def test_splice_from_to_eof
325
349
  i1, o1 = IO.pipe
326
350
  i2, o2 = IO.pipe
327
351
  len = nil
328
352
 
353
+
329
354
  f = spin {
330
355
  len = o2.splice_from(i1, -1000)
331
356
  o2.close
@@ -672,6 +697,27 @@ class IOClassMethodsTest < MiniTest::Test
672
697
  assert_equal [:ready, 'foo', 'bar', :done], buf
673
698
  end
674
699
 
700
+ def test_read_loop_break
701
+ i, o = IO.pipe
702
+
703
+ buf = []
704
+ f = spin do
705
+ buf << :ready
706
+ i.read_loop do |d|
707
+ buf << d
708
+ break if d == 'bar'
709
+ end
710
+ buf << :done
711
+ end
712
+
713
+ # writing always causes snoozing
714
+ o << 'foo'
715
+ 3.times { snooze }
716
+ o << 'bar'
717
+ f.await
718
+ assert_equal [:ready, 'foo', 'bar', :done], buf
719
+ end
720
+
675
721
  def test_read_loop_with_max_len
676
722
  r, w = IO.pipe
677
723