From: "ioquatix (Samuel Williams)" Date: 2022-03-14T14:52:57+00:00 Subject: [ruby-core:107891] [Ruby master Feature#18630] Introduce general `IO#timeout` and `IO#timeout=`for all (non-)blocking operations. Issue #18630 has been updated by ioquatix (Samuel Williams). > I'm not sure a timeout per IO instance makes sense, some IO operations might take longer e.g. reading many bytes at once and so it seems unclear whether any timeout value would be sensible there. I think it's reasonable that no single IO operation should take more than, say, several seconds on a healthy system. However it's definitely a hard problem. I'm also considering whether we can have a general default, e.g. set by an environment variable or global within Ruby, e.g. `IO.timeout`. We could also consider adding keyword arguments to `File.open` and so on. A timeout is really a way of protecting code from hanging indefinitely, e.g. because of deadlocks or DoS hacks. A lot of programs monkey patch such functionality but none of it is compatible with each other. I think introducing a simple, standard interface here makes sense. > The proposal should also mention this can only work for non-blocking IOs (and maybe raise if called on a blocking IO?) This only matters for general blocking Ruby. When using the fiber scheduler with io_uring, this limitation goes away. We could fall back to the blocking `Timeout.timeout` semantics in non-scheduler blocking case, I just don't know if it's a good idea to over-complexity the implementation. The vast majority of IO in Ruby now is non-blocking by default. This mostly just applies to `stdin`, `stdout`, and `stderr`. I think we could even consider making `stdin` non-blocking by default. In order to state this more clearly, we could document this limitation as "Timeouts are best effort and are not always guaranteed to be enforced or accurate." which is totally reasonable in my mind given the nature of timeouts. > I thought Timeout.timeout is already good enough when there is a scheduler and has similar semantics, why would we need this? Is there a concrete example? `Timeout.timeout` is hard to implement and I'm not sure there is any general easy implementation. There is a fiber scheduler hook for `Timeout.timeout` which can be a little bit safer in practice at the expense of only interrupting non-blocking IO. `IO#timeout` is more like the default timeout to use when internal wait mechanisms are invoked, like `nogvl_wait_for` or `rb_io_wait`. This is much more predictable and robust. ---------------------------------------- Feature #18630: Introduce general `IO#timeout` and `IO#timeout=`for all (non-)blocking operations. https://bugs.ruby-lang.org/issues/18630#change-96832 * Author: ioquatix (Samuel Williams) * Status: Open * Priority: Normal ---------------------------------------- I would like us to consider introducing a general timeout for all blocking operations. This timeout can be specified per IO instance. It's useful for ensuring programs don't stop responding or spend an unreasonable amount of time waiting for IO operations. There are effectively two kinds of interfaces that we need to address: - Those that already have a timeout argument (e.g. `wait_readable`) and we follow the existing semantics. - Those that don't have a timeout argument or timeout semantics (e.g. `puts`, `gets`), and thus probably need to raise an exception on timeout. We have three possible kinds of exceptions we could raise: - `Errno::ETIMEDOUT` - `Timeout::Error` (from `timeout.rb`) - Introduce `IO::Timeout` or something similar. Timeout isn't necessarily an error condition. There are different arguments for whether we should define: ```ruby class IO::Timeout < Exception end # or class IO::Timeout < StandardError end ``` I believe the latter (`StandardError`) is more practical but I'm open to either option. I might have more specific arguments later why one is better than the other after testing in a practical system. There is already a PR to try it out: https://github.com/ruby/ruby/pull/5653 -- https://bugs.ruby-lang.org/ Unsubscribe: