GSocket: add support for timeouts
authorDan Winship <danw@gnome.org>
Thu, 31 Dec 2009 15:29:23 +0000 (10:29 -0500)
committerDan Winship <danw@gnome.org>
Fri, 23 Apr 2010 16:31:31 +0000 (12:31 -0400)
Also add options for testing timeouts to socket test programs

https://bugzilla.gnome.org/show_bug.cgi?id=587898

docs/reference/gio/gio-sections.txt
gio/gio.symbols
gio/gsocket.c
gio/gsocket.h
gio/tests/socket-client.c
gio/tests/socket-server.c

index 98da988a010153a4aa12e9e89b0118dec21ec436..fd2c338284be1dca07cc6c8c0da5b99056180484 100644 (file)
@@ -1735,6 +1735,8 @@ g_socket_get_blocking
 g_socket_set_blocking
 g_socket_get_keepalive
 g_socket_set_keepalive
+g_socket_get_timeout
+g_socket_set_timeout
 g_socket_get_family
 g_socket_get_fd
 g_socket_get_local_address
index a450f6d6567f3f36611e8d24c252e9743985ae9e..6b729d3741be0d15a935ffb9171e3718b5d69ae5 100644 (file)
@@ -1202,6 +1202,7 @@ g_socket_create_source
 g_socket_get_blocking
 g_socket_get_family
 g_socket_get_fd
+g_socket_get_timeout
 g_socket_get_keepalive
 g_socket_get_listen_backlog
 g_socket_get_local_address
@@ -1220,6 +1221,7 @@ g_socket_send
 g_socket_send_message
 g_socket_send_to
 g_socket_set_blocking
+g_socket_set_timeout
 g_socket_set_keepalive
 g_socket_set_listen_backlog
 g_socket_speaks_ipv4
index e9e7b9d6de491c0c786f7a6ec899f97759221afc..1ecfd5dc3e422bc0b9b5f416969a9d7652c325ed 100644 (file)
@@ -133,7 +133,8 @@ enum
   PROP_LISTEN_BACKLOG,
   PROP_KEEPALIVE,
   PROP_LOCAL_ADDRESS,
-  PROP_REMOTE_ADDRESS
+  PROP_REMOTE_ADDRESS,
+  PROP_TIMEOUT
 };
 
 struct _GSocketPrivate
@@ -143,6 +144,7 @@ struct _GSocketPrivate
   GSocketProtocol protocol;
   gint            fd;
   gint            listen_backlog;
+  guint           timeout;
   GError         *construct_error;
   guint           inited : 1;
   guint           blocking : 1;
@@ -150,6 +152,7 @@ struct _GSocketPrivate
   guint           closed : 1;
   guint           connected : 1;
   guint           listening : 1;
+  guint           timed_out : 1;
 #ifdef G_OS_WIN32
   WSAEVENT        event;
   int             current_events;
@@ -294,6 +297,15 @@ check_socket (GSocket *socket,
                           _("Socket is already closed"));
       return FALSE;
     }
+
+  if (socket->priv->timed_out)
+    {
+      socket->priv->timed_out = FALSE;
+      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT,
+                          _("Socket I/O timed out"));
+      return FALSE;
+    }
+
   return TRUE;
 }
 
@@ -556,6 +568,10 @@ g_socket_get_property (GObject    *object,
        g_value_take_object (value, address);
        break;
 
+      case PROP_TIMEOUT:
+       g_value_set_uint (value, socket->priv->timeout);
+       break;
+
       default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
     }
@@ -599,6 +615,10 @@ g_socket_set_property (GObject      *object,
        g_socket_set_keepalive (socket, g_value_get_boolean (value));
        break;
 
+      case PROP_TIMEOUT:
+       g_socket_set_timeout (socket, g_value_get_uint (value));
+       break;
+
       default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
     }
@@ -735,6 +755,23 @@ g_socket_class_init (GSocketClass *klass)
                                                        G_TYPE_SOCKET_ADDRESS,
                                                        G_PARAM_READABLE |
                                                         G_PARAM_STATIC_STRINGS));
+
+  /**
+   * GSocket:timeout:
+   *
+   * The timeout in seconds on socket I/O
+   *
+   * Since: 2.26
+   */
+  g_object_class_install_property (gobject_class, PROP_TIMEOUT,
+                                  g_param_spec_uint ("timeout",
+                                                     P_("Timeout"),
+                                                     P_("The timeout in seconds on socket I/O"),
+                                                     0,
+                                                     G_MAXUINT,
+                                                     0,
+                                                     G_PARAM_READWRITE |
+                                                     G_PARAM_STATIC_STRINGS));
 }
 
 static void
@@ -1021,6 +1058,66 @@ g_socket_set_listen_backlog (GSocket *socket,
     }
 }
 
+/**
+ * g_socket_get_timeout:
+ * @socket: a #GSocket.
+ *
+ * Gets the timeout setting of the socket. For details on this, see
+ * g_socket_set_timeout().
+ *
+ * Returns: the timeout in seconds
+ *
+ * Since: 2.26
+ */
+guint
+g_socket_get_timeout (GSocket *socket)
+{
+  g_return_val_if_fail (G_IS_SOCKET (socket), 0);
+
+  return socket->priv->timeout;
+}
+
+/**
+ * g_socket_set_timeout:
+ * @socket: a #GSocket.
+ * @timeout: the timeout for @socket, in seconds, or 0 for none
+ *
+ * Sets the time in seconds after which I/O operations on @socket will
+ * time out if they have not yet completed.
+ *
+ * On a blocking socket, this means that any blocking #GSocket
+ * operation will time out after @timeout seconds of inactivity,
+ * returning %G_IO_ERROR_TIMED_OUT.
+ *
+ * On a non-blocking socket, calls to g_socket_condition_wait() will
+ * also fail with %G_IO_ERROR_TIMED_OUT after the given time. Sources
+ * created with g_socket_create_source() will trigger after
+ * @timeout seconds of inactivity, with the requested condition
+ * set, at which point calling g_socket_receive(), g_socket_send(),
+ * g_socket_check_connect_result(), etc, will fail with
+ * %G_IO_ERROR_TIMED_OUT.
+ *
+ * If @timeout is 0 (the default), operations will never time out
+ * on their own.
+ *
+ * Note that if an I/O operation is interrupted by a signal, this may
+ * cause the timeout to be reset.
+ *
+ * Since: 2.26
+ */
+void
+g_socket_set_timeout (GSocket *socket,
+                     guint    timeout)
+{
+  g_return_if_fail (G_IS_SOCKET (socket));
+
+  if (timeout != socket->priv->timeout)
+    {
+      socket->priv->timeout = timeout;
+      g_object_notify (G_OBJECT (socket), "timeout");
+    }
+}
+
 /**
  * g_socket_get_family:
  * @socket: a #GSocket.
@@ -1566,6 +1663,9 @@ g_socket_check_connect_result (GSocket  *socket,
   guint optlen;
   int value;
 
+  if (!check_socket (socket, error))
+    return FALSE;
+
   optlen = sizeof (value);
   if (getsockopt (socket->priv->fd, SOL_SOCKET, SO_ERROR, (void *)&value, &optlen) != 0)
     {
@@ -2199,6 +2299,7 @@ typedef struct {
   GIOCondition  condition;
   GCancellable *cancellable;
   GPollFD       cancel_pollfd;
+  GTimeVal      timeout_time;
 } GSocketSource;
 
 static gboolean
@@ -2207,13 +2308,29 @@ socket_source_prepare (GSource *source,
 {
   GSocketSource *socket_source = (GSocketSource *)source;
 
+  if (g_cancellable_is_cancelled (socket_source->cancellable))
+    return TRUE;
+
+  if (socket_source->timeout_time.tv_sec)
+    {
+      GTimeVal now;
+
+      g_source_get_current_time (source, &now);
+      *timeout = ((socket_source->timeout_time.tv_sec - now.tv_sec) * 1000 +
+                 (socket_source->timeout_time.tv_usec - now.tv_usec) / 1000);
+      if (*timeout < 0)
+       {
+         socket_source->socket->priv->timed_out = TRUE;
+         socket_source->pollfd.revents = socket_source->condition & (G_IO_IN | G_IO_OUT);
+         return TRUE;
+       }
+    }
+  else
+    *timeout = -1;
+
 #ifdef G_OS_WIN32
   socket_source->pollfd.revents = update_condition (socket_source->socket);
 #endif
-  *timeout = -1;
-
-  if (g_cancellable_is_cancelled (socket_source->cancellable))
-    return TRUE;
 
   if ((socket_source->condition & socket_source->pollfd.revents) != 0)
     return TRUE;
@@ -2315,6 +2432,17 @@ socket_source_new (GSocket      *socket,
   socket_source->pollfd.revents = 0;
   g_source_add_poll (source, &socket_source->pollfd);
 
+  if (socket->priv->timeout)
+    {
+      g_get_current_time (&socket_source->timeout_time);
+      socket_source->timeout_time.tv_sec += socket->priv->timeout;
+    }
+  else
+    {
+      socket_source->timeout_time.tv_sec = 0;
+      socket_source->timeout_time.tv_usec = 0;
+    }
+
   return source;
 }
 
@@ -2338,6 +2466,12 @@ socket_source_new (GSocket      *socket,
  * condition change). You can check for this in the callback using
  * g_cancellable_is_cancelled().
  *
+ * If @socket has a timeout set, and it is reached before @condition
+ * occurs, the source will then trigger anyway, reporting %G_IO_IN or
+ * %G_IO_OUT depending on @condition. However, @socket will have been
+ * marked as having had a timeout, and so the next #GSocket I/O method
+ * you call will then fail with a %G_IO_ERROR_TIMED_OUT.
+ *
  * Returns: a newly allocated %GSource, free with g_source_unref().
  *
  * Since: 2.22
@@ -2415,8 +2549,11 @@ g_socket_condition_check (GSocket      *socket,
  * Waits for @condition to become true on @socket. When the condition
  * is met, %TRUE is returned.
  *
- * If @cancellable is cancelled before the condition is met then %FALSE
- * is returned and @error, if non-%NULL, is set to %G_IO_ERROR_CANCELLED.
+ * If @cancellable is cancelled before the condition is met, or if the
+ * socket has a timeout set and it is reached before the condition is
+ * met, then %FALSE is returned and @error, if non-%NULL, is set to
+ * the appropriate value (%G_IO_ERROR_CANCELLED or
+ * %G_IO_ERROR_TIMED_OUT).
  *
  * Returns: %TRUE if the condition was met, %FALSE otherwise
  *
@@ -2438,7 +2575,7 @@ g_socket_condition_wait (GSocket       *socket,
   {
     GIOCondition current_condition;
     WSAEVENT events[2];
-    DWORD res;
+    DWORD res, timeout;
     GPollFD cancel_fd;
     int num_events;
 
@@ -2453,11 +2590,16 @@ g_socket_condition_wait (GSocket       *socket,
     if (g_cancellable_make_pollfd (cancellable, &cancel_fd))
       events[num_events++] = (WSAEVENT)cancel_fd.fd;
 
+    if (socket->priv->timeout)
+      timeout = socket->priv->timeout * 1000;
+    else
+      timeout = WSA_INFINITE;
+
     current_condition = update_condition (socket);
     while ((condition & current_condition) == 0)
       {
        res = WSAWaitForMultipleEvents(num_events, events,
-                                      FALSE, WSA_INFINITE, FALSE);
+                                      FALSE, timeout, FALSE);
        if (res == WSA_WAIT_FAILED)
          {
            int errsv = get_socket_errno ();
@@ -2468,6 +2610,12 @@ g_socket_condition_wait (GSocket       *socket,
                         socket_strerror (errsv));
            break;
          }
+       else if (res == WSA_WAIT_TIMEOUT)
+         {
+           g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT,
+                                _("Socket I/O timed out"));
+           break;
+         }
 
        if (g_cancellable_set_error_if_cancelled (cancellable, error))
          break;
@@ -2485,6 +2633,7 @@ g_socket_condition_wait (GSocket       *socket,
     GPollFD poll_fd[2];
     gint result;
     gint num;
+    gint timeout;
 
     poll_fd[0].fd = socket->priv->fd;
     poll_fd[0].events = condition;
@@ -2493,15 +2642,26 @@ g_socket_condition_wait (GSocket       *socket,
     if (g_cancellable_make_pollfd (cancellable, &poll_fd[1]))
       num++;
 
+    if (socket->priv->timeout)
+      timeout = socket->priv->timeout * 1000;
+    else
+      timeout = -1;
+
     do
-      result = g_poll (poll_fd, num, -1);
+      result = g_poll (poll_fd, num, timeout);
     while (result == -1 && get_socket_errno () == EINTR);
     
     if (num > 1)
       g_cancellable_release_fd (cancellable);
 
-    return cancellable == NULL ||
-      !g_cancellable_set_error_if_cancelled (cancellable, error);
+    if (result == 0)
+      {
+       g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT,
+                            _("Socket I/O timed out"));
+       return FALSE;
+      }
+
+    return !g_cancellable_set_error_if_cancelled (cancellable, error);
   }
   #endif
 }
index c56a14c80067a1e6a914fc2b2ae10af08f02d0da..350cc28976d081abbff030bf1046c374f47181bc 100644 (file)
@@ -97,6 +97,9 @@ gboolean               g_socket_get_keepalive           (GSocket
 gint                   g_socket_get_listen_backlog      (GSocket                 *socket);
 void                   g_socket_set_listen_backlog      (GSocket                 *socket,
                                                         gint                     backlog);
+guint                  g_socket_get_timeout             (GSocket                 *socket);
+void                   g_socket_set_timeout             (GSocket                 *socket,
+                                                        guint                    timeout);
 gboolean               g_socket_is_connected            (GSocket                 *socket);
 gboolean               g_socket_bind                    (GSocket                 *socket,
                                                         GSocketAddress          *address,
index 6e1881a1d22e6922fc2d37c15eb72bb26364ad33..9adcf325479153bf04949dafc17c40edbc66f509 100644 (file)
@@ -14,6 +14,7 @@ gboolean non_blocking = FALSE;
 gboolean use_udp = FALSE;
 gboolean use_source = FALSE;
 int cancel_timeout = 0;
+int read_timeout = 0;
 gboolean unix_socket = FALSE;
 
 static GOptionEntry cmd_entries[] = {
@@ -31,6 +32,8 @@ static GOptionEntry cmd_entries[] = {
   {"unix", 'U', 0, G_OPTION_ARG_NONE, &unix_socket,
    "Use a unix socket instead of IP", NULL},
 #endif
+  {"timeout", 't', 0, G_OPTION_ARG_INT, &read_timeout,
+   "Time out reads after the specified number of seconds", NULL},
   {NULL}
 };
 
@@ -151,6 +154,9 @@ main (int argc,
       return 1;
     }
 
+  if (read_timeout)
+    g_socket_set_timeout (socket, read_timeout);
+
   if (unix_socket)
     {
       GSocketAddress *addr;
index 09ee48d523c177fcf90ff82fbd8f8fc60007f012..aeb1812f976b2d718b369cbd50bca51f011097fc 100644 (file)
@@ -15,6 +15,8 @@ gboolean non_blocking = FALSE;
 gboolean use_udp = FALSE;
 gboolean use_source = FALSE;
 int cancel_timeout = 0;
+int read_timeout = 0;
+int delay = 0;
 gboolean unix_socket = FALSE;
 
 static GOptionEntry cmd_entries[] = {
@@ -36,6 +38,10 @@ static GOptionEntry cmd_entries[] = {
   {"unix", 'U', 0, G_OPTION_ARG_NONE, &unix_socket,
    "Use a unix socket instead of IP", NULL},
 #endif
+  {"delay", 'd', 0, G_OPTION_ARG_INT, &delay,
+   "Delay responses by the specified number of seconds", NULL},
+  {"timeout", 't', 0, G_OPTION_ARG_INT, &read_timeout,
+   "Time out reads after the specified number of seconds", NULL},
   {NULL}
 };
 
@@ -210,6 +216,8 @@ main (int argc,
 
       if (non_blocking)
        g_socket_set_blocking (new_socket, FALSE);
+      if (read_timeout)
+       g_socket_set_timeout (new_socket, read_timeout);
 
       address = g_socket_get_remote_address (new_socket, &error);
       if (!address)
@@ -271,6 +279,13 @@ main (int argc,
 
       to_send = size;
 
+      if (delay)
+       {
+         if (verbose)
+           g_print ("delaying %d seconds before response\n", delay);
+         g_usleep (1000 * 1000 * delay);
+       }
+
       while (to_send > 0)
        {
          ensure_condition (recv_socket, "send", cancellable, G_IO_OUT);