Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement changes of ruby-3.0 to IO#wait #2953

Merged
merged 6 commits into from
Mar 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ Compatibility:
* Add `Refinement#import_methods` method and add deprecation warning for `Refinement#include` and `Refinement#prepend` (#2733, @horakivo).
* Upgrading `UNICODE` version to 13.0.0 and `EMOJI` version to 13.1 (#2733, @horakivo).
* Add `rb_io_maybe_wait_readable`, `rb_io_maybe_wait_writable` and `rb_io_maybe_wait` functions (#2733, @andrykonchin).
* Implement changes of Ruby 3.0 to `IO#wait` (#2953, @larskanis).

Performance:

Expand Down
70 changes: 66 additions & 4 deletions lib/truffle/io/wait.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# truffleruby_primitives: true

# Copyright (c) 2017, 2023 Oracle and/or its affiliates. All rights reserved. This
# code is released under a tri EPL/GPL/LGPL license. You can use it,
# redistribute it and/or modify it under the terms of the:
Expand All @@ -14,17 +16,77 @@ def nread

def ready?
ensure_open_and_readable
Truffle::IOOperations.poll(self, Truffle::IOOperations::POLLIN, 0)
Truffle::IOOperations.poll(self, IO::READABLE, 0) > 0
end

def wait_readable(timeout = nil)
ensure_open_and_readable
Truffle::IOOperations.poll(self, Truffle::IOOperations::POLLIN, timeout) ? self : nil
Truffle::IOOperations.poll(self, IO::READABLE, timeout) > 0 ? self : nil
end
alias_method :wait, :wait_readable

def wait_writable(timeout = nil)
ensure_open_and_writable
Truffle::IOOperations.poll(self, Truffle::IOOperations::POLLOUT, timeout) ? self : nil
Truffle::IOOperations.poll(self, IO::WRITABLE, timeout) > 0 ? self : nil
end

def wait_priority(timeout = nil)
ensure_open_and_readable
Truffle::IOOperations.poll(self, IO::PRIORITY, timeout) > 0 ? self : nil
end


# call-seq:
# io.wait(events, timeout) -> event mask, false or nil
# io.wait(timeout = nil, mode = :read) -> self, true, or false
#
# Waits until the IO becomes ready for the specified events and returns the
# subset of events that become ready, or a falsy value when times out.
#
# The events can be a bit mask of +IO::READABLE+, +IO::WRITABLE+ or
# +IO::PRIORITY+.
#
# Returns a truthy value immediately when buffered data is available.
#
# Optional parameter +mode+ is one of +:read+, +:write+, or
# +:read_write+.
def wait(*args)
ensure_open

if args.size != 2 || Primitive.object_kind_of?(args[0], Symbol) || Primitive.object_kind_of?(args[1], Symbol)
# Slow/messy path:
timeout = :undef
events = 0
args.each do |arg|
if Primitive.object_kind_of?(arg, Symbol)
events |= case arg
when :r, :read, :readable then IO::READABLE
when :w, :write, :writable then IO::WRITABLE
when :rw, :read_write, :readable_writable then IO::READABLE | IO::WRITABLE
else
raise ArgumentError, "unsupported mode: #{mode.inspect}"
end

elsif timeout == :undef
timeout = arg
else
raise ArgumentError, 'timeout given more than once'
end
end

timeout = nil if timeout == :undef

events = IO::READABLE if events == 0

res = Truffle::IOOperations.poll(self, events, timeout)
res == 0 ? nil : self
else
# args.size == 2 and neither are symbols
# This is the fast path and the new interface:
events, timeout = *args
raise ArgumentError, 'Events must be positive integer!' if events <= 0
res = Truffle::IOOperations.poll(self, events, timeout)
# return events as bit mask
res == 0 ? nil : res
end
end
end
2 changes: 1 addition & 1 deletion src/main/c/truffleposix/truffleposix.c
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ int truffleposix_poll(int fd, int events, int timeout_ms) {
fds.fd = fd;
fds.events = events;

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

int truffleposix_select(int nread, int *readfds, int nwrite, int *writefds,
Expand Down
8 changes: 4 additions & 4 deletions src/main/ruby/truffleruby/core/truffle/io_operations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -205,9 +205,9 @@ def self.select(readables, readable_ios, writables, writable_ios, errorables, er

end

# This method will return a true if poll returned without error
# with an event within the timeout, false if the timeout expired,
# or raises an exception for an errno.
# This method will return an event mask if poll returned without error.
# The event mask is > 0 when an event occurred within the timeout, 0 if the timeout expired.
# Raises an exception for an errno.
def self.poll(io, event_mask, timeout)
if (event_mask & POLLIN) != 0
return 1 unless io.__send__(:buffer_empty?)
Expand Down Expand Up @@ -257,7 +257,7 @@ def self.poll(io, event_mask, timeout)
Errno.handle_errno(errno)
end
else
primitive_result > 0
primitive_result
end
end while result == :retry

Expand Down
16 changes: 4 additions & 12 deletions test/mri/excludes/TestIOWait.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,4 @@
exclude :test_nread, "needs investigation"
exclude :test_nread_buffered, "needs investigation"
exclude :test_wait_buffered, "needs investigation"
exclude :test_wait_readable_buffered, "needs investigation"
exclude :test_wait_readwrite, "needs investigation"
exclude :test_wait_readwrite_timeout, "needs investigation"
exclude :test_nread, "needs investigation"
exclude :test_nread_buffered, "needs investigation"
exclude :test_wait_buffered, "needs investigation"
exclude :test_wait_readable_buffered, "needs investigation"
exclude :test_wait_readwrite, "needs investigation"
exclude :test_wait_readwrite_timeout, "needs investigation"
exclude :test_nread, "buffered reads are not implemented"
exclude :test_nread_buffered, "buffered reads are not implemented"
exclude :test_wait_buffered, "buffered reads are not implemented"
exclude :test_wait_readable_buffered, "buffered reads are not implemented"
36 changes: 32 additions & 4 deletions test/mri/tests/io/wait/test_io_wait.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@
require 'test/unit'
require 'timeout'
require 'socket'
begin
require 'io/wait'
rescue LoadError
end

# For `IO#ready?` and `IO#nread`:
require 'io/wait'

class TestIOWait < Test::Unit::TestCase

Expand Down Expand Up @@ -50,6 +49,7 @@ def test_buffered_ready?
end

def test_wait
omit 'unstable on MinGW' if /mingw/ =~ RUBY_PLATFORM
assert_nil @r.wait(0)
@w.syswrite "."
sleep 0.1
Expand Down Expand Up @@ -161,6 +161,34 @@ def test_wait_readwrite_timeout
assert_equal @w, @w.wait(0.01, :read_write)
end

def test_wait_mask_writable
omit("Missing IO::WRITABLE!") unless IO.const_defined?(:WRITABLE)
assert_equal IO::WRITABLE, @w.wait(IO::WRITABLE, 0)
end

def test_wait_mask_readable
omit("Missing IO::READABLE!") unless IO.const_defined?(:READABLE)
@w.write("Hello World\n" * 3)
assert_equal IO::READABLE, @r.wait(IO::READABLE, 0)

@r.gets
assert_equal IO::READABLE, @r.wait(IO::READABLE, 0)
end

def test_wait_mask_zero
omit("Missing IO::WRITABLE!") unless IO.const_defined?(:WRITABLE)
assert_raise(ArgumentError) do
@w.wait(0, 0)
end
end

def test_wait_mask_negative
omit("Missing IO::WRITABLE!") unless IO.const_defined?(:WRITABLE)
assert_raise(ArgumentError) do
@w.wait(-6, 0)
end
end
eregon marked this conversation as resolved.
Show resolved Hide resolved

private

def fill_pipe
Expand Down