[前][次][番号順一覧][スレッド一覧]

ruby-changes:73909

From: Samuel <ko1@a...>
Date: Fri, 7 Oct 2022 17:49:05 +0900 (JST)
Subject: [ruby-changes:73909] e4f91bbdba (master): Add IO#timeout attribute and use it for blocking IO operations. (#5653)

https://git.ruby-lang.org/ruby.git/commit/?id=e4f91bbdba

From e4f91bbdbaa6ab3125f24967414ac5300bb244f5 Mon Sep 17 00:00:00 2001
From: Samuel Williams <samuel.williams@o...>
Date: Fri, 7 Oct 2022 21:48:38 +1300
Subject: Add IO#timeout attribute and use it for blocking IO operations.
 (#5653)

---
 NEWS.md                                          |  11 +
 ext/openssl/extconf.rb                           |   1 +
 ext/openssl/ossl_ssl.c                           |  14 +-
 ext/socket/ancdata.c                             |   4 +-
 ext/socket/basicsocket.c                         |   2 +-
 ext/socket/init.c                                |   4 +-
 ext/socket/socket.c                              |   4 +
 ext/socket/udpsocket.c                           |   2 +-
 gc.c                                             |   1 +
 include/ruby/io.h                                |  50 +++-
 io.c                                             | 336 ++++++++++++++++++-----
 scheduler.c                                      |   4 +-
 spec/ruby/library/socket/tcpsocket/shared/new.rb |  10 +-
 test/ruby/test_io.rb                             |   3 +
 test/ruby/test_io_timeout.rb                     |  64 +++++
 test/socket/test_tcp.rb                          |   2 +-
 thread.c                                         |  11 +
 17 files changed, 426 insertions(+), 97 deletions(-)
 create mode 100644 test/ruby/test_io_timeout.rb

diff --git a/NEWS.md b/NEWS.md
index d1b25f9444..eb1ffbe438 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -102,6 +102,16 @@ Note that each entry is kept to a minimum, see links for details. https://github.com/ruby/ruby/blob/trunk/NEWS.md#L102
 
 Note: We're only listing outstanding class updates.
 
+* IO
+    * Introduce `IO#timeout=` and `IO#timeout` which can cause
+    `IO::TimeoutError` to be raised if a blocking operation exceeds the
+    specified timeout. [[Feature #18630]]
+
+    ```ruby
+    STDIN.timeout = 1
+    STDIN.read # => Blocking operation timed out! (IO::TimeoutError)
+    ```
+
 * Data
     * New core class to represent simple immutable value object. The class is
       similar to `Struct` and partially shares an implementation, but has more
@@ -332,3 +342,4 @@ The following deprecated APIs are removed. https://github.com/ruby/ruby/blob/trunk/NEWS.md#L342
 [Feature #19008]: https://bugs.ruby-lang.org/issues/19008
 [Feature #19026]: https://bugs.ruby-lang.org/issues/19026
 [Feature #16122]: https://bugs.ruby-lang.org/issues/16122
+[Feature #18630]: https://bugs.ruby-lang.org/issues/18630
diff --git a/ext/openssl/extconf.rb b/ext/openssl/extconf.rb
index cc2b1f8ba2..a856646fe5 100644
--- a/ext/openssl/extconf.rb
+++ b/ext/openssl/extconf.rb
@@ -27,6 +27,7 @@ if with_config("debug") or enable_config("debug") https://github.com/ruby/ruby/blob/trunk/ext/openssl/extconf.rb#L27
 end
 
 have_func("rb_io_maybe_wait") # Ruby 3.1
+have_func("rb_io_timeout") # Ruby 3.2
 
 Logging::message "=== Checking for system dependent stuff... ===\n"
 have_library("nsl", "t_open")
diff --git a/ext/openssl/ossl_ssl.c b/ext/openssl/ossl_ssl.c
index 6e1a50fd6d..605591efe5 100644
--- a/ext/openssl/ossl_ssl.c
+++ b/ext/openssl/ossl_ssl.c
@@ -1641,11 +1641,21 @@ no_exception_p(VALUE opts) https://github.com/ruby/ruby/blob/trunk/ext/openssl/ossl_ssl.c#L1641
     return 0;
 }
 
+inline static
+VALUE io_timeout()
+{
+#ifdef HAVE_RB_IO_TIMEOUT
+    return Qundef;
+#else
+    return Qnil;
+#endif
+}
+
 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, io_timeout());
 #else
     rb_io_wait_writable(fptr->fd);
 #endif
@@ -1655,7 +1665,7 @@ static void https://github.com/ruby/ruby/blob/trunk/ext/openssl/ossl_ssl.c#L1665
 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, io_timeout());
 #else
     rb_io_wait_readable(fptr->fd);
 #endif
diff --git a/ext/socket/ancdata.c b/ext/socket/ancdata.c
index 071e3323bb..0ab3a8da47 100644
--- a/ext/socket/ancdata.c
+++ b/ext/socket/ancdata.c
@@ -1285,7 +1285,7 @@ bsock_sendmsg_internal(VALUE sock, VALUE data, VALUE vflags, https://github.com/ruby/ruby/blob/trunk/ext/socket/ancdata.c#L1285
 
     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;
         }
@@ -1557,7 +1557,7 @@ bsock_recvmsg_internal(VALUE sock, https://github.com/ruby/ruby/blob/trunk/ext/socket/ancdata.c#L1557
 
     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;
         }
diff --git a/ext/socket/basicsocket.c b/ext/socket/basicsocket.c
index 93196c924d..66c2537cbb 100644
--- a/ext/socket/basicsocket.c
+++ b/ext/socket/basicsocket.c
@@ -601,7 +601,7 @@ rsock_bsock_send(int argc, VALUE *argv, VALUE socket) https://github.com/ruby/ruby/blob/trunk/ext/socket/basicsocket.c#L601
 
         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;
         }
 
diff --git a/ext/socket/init.c b/ext/socket/init.c
index 0cff3d6794..e60dd32264 100644
--- a/ext/socket/init.c
+++ b/ext/socket/init.c
@@ -189,7 +189,7 @@ rsock_s_recvfrom(VALUE socket, int argc, VALUE *argv, enum sock_recv_type from) https://github.com/ruby/ruby/blob/trunk/ext/socket/init.c#L189
 
         if (slen >= 0) break;
 
-        if (!rb_io_maybe_wait_readable(errno, socket, Qnil))
+        if (!rb_io_maybe_wait_readable(errno, socket, Qundef))
             rb_sys_fail("recvfrom(2)");
     }
 
@@ -705,7 +705,7 @@ rsock_s_accept(VALUE klass, VALUE io, struct sockaddr *sockaddr, socklen_t *len) https://github.com/ruby/ruby/blob/trunk/ext/socket/init.c#L705
             retry = 1;
             goto retry;
           default:
-            if (!rb_io_maybe_wait_readable(error, io, Qnil)) break;
+            if (!rb_io_maybe_wait_readable(error, io, Qundef)) break;
             retry = 0;
             goto retry;
         }
diff --git a/ext/socket/socket.c b/ext/socket/socket.c
index b1965deb9e..5cf0835062 100644
--- a/ext/socket/socket.c
+++ b/ext/socket/socket.c
@@ -28,6 +28,10 @@ rsock_syserr_fail_host_port(int err, const char *mesg, VALUE host, VALUE port) https://github.com/ruby/ruby/blob/trunk/ext/socket/socket.c#L28
     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);
 }
 
diff --git a/ext/socket/udpsocket.c b/ext/socket/udpsocket.c
index 3500107972..5b878b4a95 100644
--- a/ext/socket/udpsocket.c
+++ b/ext/socket/udpsocket.c
@@ -170,7 +170,7 @@ udp_send_internal(VALUE v) https://github.com/ruby/ruby/blob/trunk/ext/socket/udpsocket.c#L170
 
         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;
         }
     }
diff --git a/gc.c b/gc.c
index c1b06a76e7..916584ed35 100644
--- a/gc.c
+++ b/gc.c
@@ -7303,6 +7303,7 @@ gc_mark_children(rb_objspace_t *objspace, VALUE obj) https://github.com/ruby/ruby/blob/trunk/gc.c#L7303
             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;
 
diff --git a/include/ruby/io.h b/include/ruby/io.h
index dc4c8becf6..b91ecd00cb 100644
--- a/include/ruby/io.h
+++ b/include/ruby/io.h
@@ -63,6 +63,11 @@ RBIMPL_SYMBOL_EXPORT_BEGIN() https://github.com/ruby/ruby/blob/trunk/include/ruby/io.h#L63
 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.
  *
@@ -214,6 +219,11 @@ typedef struct rb_io_t { https://github.com/ruby/ruby/blob/trunk/include/ruby/io.h#L219
      * 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} */
@@ -844,11 +854,33 @@ int rb_io_wait_writable(int fd); https://github.com/ruby/ruby/blob/trunk/include/ruby/io.h#L854
  */
 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. This timeout is
+ * used as a best effort timeout to prevent operations from blocking forever.
+ *
+ * @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`,
  * `IO::WRITable`, and `IO::PRIORITY`.
  *
+ * If timeout is `Qundef`, it will use the default timeout as given by
+ * `rb_io_timeout(io)`.
+ *
  * @p (... truncated)

--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

[前][次][番号順一覧][スレッド一覧]