Skip to content

Commit

Permalink
Add IO#timeout attribute and use it for blocking IO operations.
Browse files Browse the repository at this point in the history
  • Loading branch information
ioquatix committed Sep 21, 2022
1 parent 796069b commit acc6e61
Show file tree
Hide file tree
Showing 17 changed files with 386 additions and 97 deletions.
2 changes: 2 additions & 0 deletions error.c
Original file line number Diff line number Diff line change
Expand Up @@ -1094,6 +1094,7 @@ VALUE rb_eRuntimeError;
VALUE rb_eFrozenError;
VALUE rb_eTypeError;
VALUE rb_eArgError;
VALUE rb_eTimeoutError;
VALUE rb_eIndexError;
VALUE rb_eKeyError;
VALUE rb_eRangeError;
Expand Down Expand Up @@ -2991,6 +2992,7 @@ Init_Exception(void)
rb_eStandardError = rb_define_class("StandardError", rb_eException);
rb_eTypeError = rb_define_class("TypeError", rb_eStandardError);
rb_eArgError = rb_define_class("ArgumentError", rb_eStandardError);
rb_eTimeoutError = rb_define_class("TimeoutError", rb_eStandardError);
rb_eIndexError = rb_define_class("IndexError", rb_eStandardError);
rb_eKeyError = rb_define_class("KeyError", rb_eIndexError);
rb_define_method(rb_eKeyError, "initialize", key_err_initialize, -1);
Expand Down
4 changes: 2 additions & 2 deletions ext/openssl/ossl_ssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -1645,7 +1645,7 @@ static void
io_wait_writable(rb_io_t *fptr)
{
#ifdef HAVE_RB_IO_MAYBE_WAIT
rb_io_maybe_wait_writable(errno, fptr->self, Qnil);
rb_io_maybe_wait_writable(errno, fptr->self, fptr->timeout);
#else
rb_io_wait_writable(fptr->fd);
#endif
Expand All @@ -1655,7 +1655,7 @@ static void
io_wait_readable(rb_io_t *fptr)
{
#ifdef HAVE_RB_IO_MAYBE_WAIT
rb_io_maybe_wait_readable(errno, fptr->self, Qnil);
rb_io_maybe_wait_readable(errno, fptr->self, fptr->timeout);
#else
rb_io_wait_readable(fptr->fd);
#endif
Expand Down
4 changes: 2 additions & 2 deletions ext/socket/ancdata.c
Original file line number Diff line number Diff line change
Expand Up @@ -1285,7 +1285,7 @@ bsock_sendmsg_internal(VALUE sock, VALUE data, VALUE vflags,

if (ss == -1) {
int e;
if (!nonblock && rb_io_maybe_wait_writable(errno, fptr->self, Qnil)) {
if (!nonblock && rb_io_maybe_wait_writable(errno, fptr->self, fptr->timeout)) {
rb_io_check_closed(fptr);
goto retry;
}
Expand Down Expand Up @@ -1557,7 +1557,7 @@ bsock_recvmsg_internal(VALUE sock,

if (ss == -1) {
int e;
if (!nonblock && rb_io_maybe_wait_readable(errno, fptr->self, Qnil)) {
if (!nonblock && rb_io_maybe_wait_readable(errno, fptr->self, fptr->timeout)) {
rb_io_check_closed(fptr);
goto retry;
}
Expand Down
2 changes: 1 addition & 1 deletion ext/socket/basicsocket.c
Original file line number Diff line number Diff line change
Expand Up @@ -601,7 +601,7 @@ rsock_bsock_send(int argc, VALUE *argv, VALUE socket)

if (n >= 0) return SSIZET2NUM(n);

if (rb_io_maybe_wait_writable(errno, socket, Qnil)) {
if (rb_io_maybe_wait_writable(errno, socket, fptr->timeout)) {
continue;
}

Expand Down
4 changes: 2 additions & 2 deletions ext/socket/init.c
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ rsock_s_recvfrom(VALUE socket, int argc, VALUE *argv, enum sock_recv_type from)

if (slen >= 0) break;

if (!rb_io_maybe_wait_readable(errno, socket, Qnil))
if (!rb_io_maybe_wait_readable(errno, socket, fptr->timeout))
rb_sys_fail("recvfrom(2)");
}

Expand Down Expand Up @@ -705,7 +705,7 @@ rsock_s_accept(VALUE klass, VALUE io, struct sockaddr *sockaddr, socklen_t *len)
retry = 1;
goto retry;
default:
if (!rb_io_maybe_wait_readable(error, io, Qnil)) break;
if (!rb_io_maybe_wait_readable(error, io, fptr->timeout)) break;
retry = 0;
goto retry;
}
Expand Down
4 changes: 4 additions & 0 deletions ext/socket/socket.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ rsock_syserr_fail_host_port(int err, const char *mesg, VALUE host, VALUE port)
message = rb_sprintf("%s for %+"PRIsVALUE" port % "PRIsVALUE"",
mesg, host, port);

if (err == ETIMEDOUT) {
rb_exc_raise(rb_exc_new3(rb_eIOTimeoutError, message));
}

rb_syserr_fail_str(err, message);
}

Expand Down
2 changes: 1 addition & 1 deletion ext/socket/udpsocket.c
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ udp_send_internal(VALUE v)

if (n >= 0) return RB_SSIZE2NUM(n);

if (rb_io_maybe_wait_writable(errno, fptr->self, Qnil)) {
if (rb_io_maybe_wait_writable(errno, fptr->self, fptr->timeout)) {
goto retry;
}
}
Expand Down
1 change: 1 addition & 0 deletions gc.c
Original file line number Diff line number Diff line change
Expand Up @@ -7303,6 +7303,7 @@ gc_mark_children(rb_objspace_t *objspace, VALUE obj)
gc_mark(objspace, any->as.file.fptr->writeconv_pre_ecopts);
gc_mark(objspace, any->as.file.fptr->encs.ecopts);
gc_mark(objspace, any->as.file.fptr->write_lock);
gc_mark(objspace, any->as.file.fptr->timeout);
}
break;

Expand Down
1 change: 1 addition & 0 deletions include/ruby/internal/globals.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ RUBY_EXTERN VALUE rb_eInterrupt; /**< `Interrupt` exception. */
RUBY_EXTERN VALUE rb_eSignal; /**< `SignalException` exception. */
RUBY_EXTERN VALUE rb_eFatal; /**< `fatal` exception. */
RUBY_EXTERN VALUE rb_eArgError; /**< `ArgumentError` exception. */
RUBY_EXTERN VALUE rb_eTimeoutError; /**< `TimeoutError` exception. */
RUBY_EXTERN VALUE rb_eEOFError; /**< `EOFError` exception. */
RUBY_EXTERN VALUE rb_eIndexError; /**< `IndexError` exception. */
RUBY_EXTERN VALUE rb_eStopIteration; /**< `StopIteration` exception. */
Expand Down
46 changes: 32 additions & 14 deletions include/ruby/io.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ RBIMPL_SYMBOL_EXPORT_BEGIN()
struct stat;
struct timeval;

/**
* Indicates that a timeout has occurred while performing an IO operation.
*/
RUBY_EXTERN VALUE rb_eIOTimeoutError;

/**
* Type of events that an IO can wait.
*
Expand Down Expand Up @@ -214,6 +219,11 @@ typedef struct rb_io_t {
* This of course doesn't help inter-process IO interleaves, though.
*/
VALUE write_lock;

/**
* The timeout associated with this IO when performing blocking operations.
*/
VALUE timeout;
} rb_io_t;

/** @alias{rb_io_enc_t} */
Expand Down Expand Up @@ -844,6 +854,24 @@ int rb_io_wait_writable(int fd);
*/
int rb_wait_for_single_fd(int fd, int events, struct timeval *tv);

/**
* Get the timeout associated with the specified io object.
*
* @param[in] io An IO object.
* @retval RUBY_Qnil There is no associated timeout.
* @retval Otherwise The timeout value.
*/
VALUE rb_io_timeout(VALUE io);

/**
* Set the timeout associated with the specified io object.
*
* @param[in] io An IO object.
* @param[in] timeout A timeout value. Must respond to #to_f.
* @
*/
VALUE rb_io_set_timeout(VALUE io, VALUE timeout);

/**
* Blocks until the passed IO is ready for the passed events. The "events"
* here is a Ruby level integer, which is an OR-ed value of `IO::READABLE`,
Expand Down Expand Up @@ -903,13 +931,8 @@ VALUE rb_io_maybe_wait(int error, VALUE io, VALUE events, VALUE timeout);
* @exception rb_eIOError `io` is not open.
* @exception rb_eRangeError `timeout` is out of range.
* @exception rb_eSystemCallError `select(2)` failed for some reason.
* @exception rb_eTypeError Operation timed out.
* @return Always returns ::RUBY_IO_READABLE.
*
* @internal
*
* Because rb_io_maybe_wait() returns ::RUBY_Qfalse on timeout, this function
* fails to convert that value to `int`, and raises ::rb_eTypeError.
* @retval 0 Operation timed out.
* @retval Otherwise Always returns ::RUBY_IO_READABLE.
*/
int rb_io_maybe_wait_readable(int error, VALUE io, VALUE timeout);

Expand All @@ -924,13 +947,8 @@ int rb_io_maybe_wait_readable(int error, VALUE io, VALUE timeout);
* @exception rb_eIOError `io` is not open.
* @exception rb_eRangeError `timeout` is out of range.
* @exception rb_eSystemCallError `select(2)` failed for some reason.
* @exception rb_eTypeError Operation timed out.
* @return Always returns ::RUBY_IO_WRITABLE.
*
* @internal
*
* Because rb_io_maybe_wait() returns ::RUBY_Qfalse on timeout, this function
* fails to convert that value to `int`, and raises ::rb_eTypeError.
* @retval 0 Operation timed out.
* @retval Otherwise Always returns ::RUBY_IO_WRITABLE.
*/
int rb_io_maybe_wait_writable(int error, VALUE io, VALUE timeout);

Expand Down
Loading

0 comments on commit acc6e61

Please sign in to comment.