polyphony 1.0.2 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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