Skip to content

Commit b30a446

Browse files
committed
Implement IO#wait with recent changes to MRI
IO#wait used to be a alias to wait_readable years ago, but the interface changed a lot. Also update the related tests from MRI.
1 parent 7958712 commit b30a446

File tree

5 files changed

+100
-15
lines changed

5 files changed

+100
-15
lines changed

lib/truffle/io/wait.rb

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,75 @@ def nread
1414

1515
def ready?
1616
ensure_open_and_readable
17-
Truffle::IOOperations.poll(self, Truffle::IOOperations::POLLIN, 0)
17+
Truffle::IOOperations.poll(self, Truffle::IOOperations::POLLIN, 0) > 0
1818
end
1919

2020
def wait_readable(timeout = nil)
2121
ensure_open_and_readable
22-
Truffle::IOOperations.poll(self, Truffle::IOOperations::POLLIN, timeout) ? self : nil
22+
Truffle::IOOperations.poll(self, Truffle::IOOperations::POLLIN, timeout) > 0 ? self : nil
2323
end
24-
alias_method :wait, :wait_readable
2524

2625
def wait_writable(timeout = nil)
2726
ensure_open_and_writable
28-
Truffle::IOOperations.poll(self, Truffle::IOOperations::POLLOUT, timeout) ? self : nil
27+
Truffle::IOOperations.poll(self, Truffle::IOOperations::POLLOUT, timeout) > 0 ? self : nil
28+
end
29+
30+
31+
# call-seq:
32+
# io.wait(events, timeout) -> event mask, false or nil
33+
# io.wait(timeout = nil, mode = :read) -> self, true, or false
34+
#
35+
# Waits until the IO becomes ready for the specified events and returns the
36+
# subset of events that become ready, or a falsy value when times out.
37+
#
38+
# The events can be a bit mask of +IO::READABLE+, +IO::WRITABLE+ or
39+
# +IO::PRIORITY+.
40+
#
41+
# Returns a truthy value immediately when buffered data is available.
42+
#
43+
# Optional parameter +mode+ is one of +:read+, +:write+, or
44+
# +:read_write+.
45+
#
46+
def wait(*args)
47+
ensure_open
48+
49+
if args.size != 2 || args[0].is_a?(Symbol) || args[1].is_a?(Symbol)
50+
# Slow/messy path:
51+
52+
timeout = :undef
53+
events = 0
54+
args.each do |arg|
55+
if arg.is_a?(Symbol)
56+
events |= case arg
57+
when :r, :read, :readable then IO::READABLE
58+
when :w, :write, :writable then IO::WRITABLE
59+
when :rw, :read_write, :readable_writable then IO::READABLE | IO::WRITABLE
60+
else
61+
raise ArgumentError, "unsupported mode: #{mode.inspect}"
62+
end
63+
64+
elsif timeout == :undef
65+
timeout = arg
66+
else
67+
raise ArgumentError, 'timeout given more than once'
68+
end
69+
end
70+
71+
timeout = nil if timeout == :undef
72+
73+
events = IO::READABLE if events == 0
74+
75+
res = Truffle::IOOperations.poll(self, events, timeout)
76+
res == 0 ? nil : self
77+
else
78+
# argc == 2 and neither are symbols
79+
# This is the fast path and the new interface:
80+
81+
events, timeout = *args
82+
raise ArgumentError, 'Events must be positive integer!' if events <= 0
83+
res = Truffle::IOOperations.poll(self, events, timeout)
84+
# return events as bit mask
85+
res == 0 ? nil : res
86+
end
2987
end
3088
end

src/main/c/truffleposix/truffleposix.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ int truffleposix_poll(int fd, int events, int timeout_ms) {
117117
fds.fd = fd;
118118
fds.events = events;
119119

120-
return poll(&fds, 1, timeout_ms);
120+
return poll(&fds, 1, timeout_ms) >= 0 ? fds.revents : -1;
121121
}
122122

123123
int truffleposix_select(int nread, int *readfds, int nwrite, int *writefds,

src/main/ruby/truffleruby/core/truffle/io_operations.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -205,9 +205,9 @@ def self.select(readables, readable_ios, writables, writable_ios, errorables, er
205205

206206
end
207207

208-
# This method will return a true if poll returned without error
209-
# with an event within the timeout, false if the timeout expired,
210-
# or raises an exception for an errno.
208+
# This method will return an event mask if poll returned without error.
209+
# The event mask is >0 when an event occured within the timeout, 0 if the timeout expired.
210+
# Raises an exception for an errno.
211211
def self.poll(io, event_mask, timeout)
212212
if (event_mask & POLLIN) != 0
213213
return 1 unless io.__send__(:buffer_empty?)
@@ -257,7 +257,7 @@ def self.poll(io, event_mask, timeout)
257257
Errno.handle_errno(errno)
258258
end
259259
else
260-
primitive_result > 0
260+
primitive_result
261261
end
262262
end while result == :retry
263263

test/mri/tests/io/wait/test_io_wait.rb

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@
33
require 'test/unit'
44
require 'timeout'
55
require 'socket'
6-
begin
7-
require 'io/wait'
8-
rescue LoadError
9-
end
6+
7+
# For `IO#ready?` and `IO#nread`:
8+
require 'io/wait'
109

1110
class TestIOWait < Test::Unit::TestCase
1211

@@ -50,6 +49,7 @@ def test_buffered_ready?
5049
end
5150

5251
def test_wait
52+
omit 'unstable on MinGW' if /mingw/ =~ RUBY_PLATFORM
5353
assert_nil @r.wait(0)
5454
@w.syswrite "."
5555
sleep 0.1
@@ -161,6 +161,34 @@ def test_wait_readwrite_timeout
161161
assert_equal @w, @w.wait(0.01, :read_write)
162162
end
163163

164+
def test_wait_mask_writable
165+
omit("Missing IO::WRITABLE!") unless IO.const_defined?(:WRITABLE)
166+
assert_equal IO::WRITABLE, @w.wait(IO::WRITABLE, 0)
167+
end
168+
169+
def test_wait_mask_readable
170+
omit("Missing IO::READABLE!") unless IO.const_defined?(:READABLE)
171+
@w.write("Hello World\n" * 3)
172+
assert_equal IO::READABLE, @r.wait(IO::READABLE, 0)
173+
174+
@r.gets
175+
assert_equal IO::READABLE, @r.wait(IO::READABLE, 0)
176+
end
177+
178+
def test_wait_mask_zero
179+
omit("Missing IO::WRITABLE!") unless IO.const_defined?(:WRITABLE)
180+
assert_raise(ArgumentError) do
181+
@w.wait(0, 0)
182+
end
183+
end
184+
185+
def test_wait_mask_negative
186+
omit("Missing IO::WRITABLE!") unless IO.const_defined?(:WRITABLE)
187+
assert_raise(ArgumentError) do
188+
@w.wait(-6, 0)
189+
end
190+
end
191+
164192
private
165193

166194
def fill_pipe

test/mri/tests/io/wait/test_io_wait_uncommon.rb

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
# frozen_string_literal: true
22
require 'test/unit'
3-
require 'io/wait'
43

54
# test uncommon device types to check portability problems
65
# We may optimize IO#wait_*able for non-Linux kernels in the future
@@ -13,7 +12,7 @@ def test_tty_wait
1312
end
1413

1514
def test_fifo_wait
16-
skip 'no mkfifo' unless File.respond_to?(:mkfifo) && IO.const_defined?(:NONBLOCK)
15+
omit 'no mkfifo' unless File.respond_to?(:mkfifo) && IO.const_defined?(:NONBLOCK)
1716
require 'tmpdir'
1817
Dir.mktmpdir('rubytest-fifo') do |dir|
1918
fifo = "#{dir}/fifo"

0 commit comments

Comments
 (0)