Skip to content

Commit

Permalink
Add support for IO#timeout in io_read, io_write and io_wait. (#296
Browse files Browse the repository at this point in the history
)
  • Loading branch information
ioquatix committed Jan 2, 2024
1 parent 087f78f commit 176fca1
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 3 deletions.
43 changes: 40 additions & 3 deletions lib/async/scheduler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -165,29 +165,66 @@ def address_resolve(hostname)
::Resolv.getaddresses(hostname)
end


if IO.method_defined?(:timeout)
private def get_timeout(io)
io.timeout
end
else
private def get_timeout(io)
nil
end
end

# @asynchronous May be non-blocking..
def io_wait(io, events, timeout = nil)
fiber = Fiber.current

if timeout
# If an explicit timeout is specified, we expect that the user will handle it themselves:
timer = @timers.after(timeout) do
fiber.transfer
end
elsif timeout = get_timeout(io)
# Otherwise, if we default to the io's timeout, we raise an exception:
timer = @timers.after(timeout) do
fiber.raise(::IO::TimeoutError, "Timeout while waiting for IO to become ready!")
end
end

return @selector.io_wait(fiber, io, events)
ensure
timer&.cancel
end

if ::IO::Event::Support.buffer?
def io_read(io, buffer, length, offset = 0)
@selector.io_read(Fiber.current, io, buffer, length, offset)
fiber = Fiber.current

if timeout = get_timeout(io)
timer = @timers.after(timeout) do
fiber.raise(::IO::TimeoutError, "execution expired")
end
end

@selector.io_read(fiber, io, buffer, length, offset)
ensure
timer&.cancel
end

if RUBY_ENGINE != "ruby" || RUBY_VERSION >= "3.3.0"
def io_write(io, buffer, length, offset = 0)
@selector.io_write(Fiber.current, io, buffer, length, offset)
fiber = Fiber.current

if timeout = get_timeout(io)
timer = @timers.after(timeout) do
fiber.raise(::IO::TimeoutError, "execution expired")
end
end

@selector.io_write(fiber, io, buffer, length, offset)
ensure
timer&.cancel
end
end
end
Expand Down
30 changes: 30 additions & 0 deletions test/io.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,35 @@
input.close
output.close
end

it "can read with timeout" do
skip_unless_constant_defined(:TimeoutError, IO)

input, output = IO.pipe
input.timeout = 0.001

expect do
line = input.gets
end.to raise_exception(::IO::TimeoutError)
end

it "can wait readable with default timeout" do
skip_unless_constant_defined(:TimeoutError, IO)

input, output = IO.pipe
input.timeout = 0.001

expect do
# This behaviour is not consistent with non-fiber scheduler IO.
# However, this is the best we can do without fixing CRuby.
input.wait_readable
end.to raise_exception(::IO::TimeoutError)
end

it "can wait readable with explicit timeout" do
input, output = IO.pipe

expect(input.wait_readable(0)).to be_nil
end
end
end

0 comments on commit 176fca1

Please sign in to comment.