[gnio] Always use non-blocking sockets and emulate blocking mode for the API



commit 6a9275874b05c933e78e184ce8ea25ca8db48c7d
Author: Alexander Larsson <alexl redhat com>
Date:   Thu May 14 13:41:26 2009 +0200

    Always use non-blocking sockets and emulate blocking mode for the API
    
    This avoids the issue where windows automatically enables nonblocking
    mode in various ways and sometimes makes it hard to get out of it.
    This way we provide a consistent cross platform API.
---
 gio/gsocket.c |  260 ++++++++++++++++++++++++++++++++++++---------------------
 1 files changed, 164 insertions(+), 96 deletions(-)

diff --git a/gio/gsocket.c b/gio/gsocket.c
index 57b83c6..1cdb67b 100644
--- a/gio/gsocket.c
+++ b/gio/gsocket.c
@@ -79,12 +79,15 @@
  * To know when a call would successfully run you can call g_socket_condition_check(),
  * or g_socket_condition_wait(). You can also use g_socket_create_source() and
  * attach it to a #GMainContext to get callbacks when I/O is possible.
+ * Note that all sockets are always set to non blocking mode in the system, and
+ * blocking mode is emulated in GSocket.
  *
- * Applications should always be able to handle getting a %G_IO_ERROR_WOULD_BLOCK
- * error even when some other function said that I/O was possible. This can
- * easily happen in case of a race condition in the application, but it can
- * also happen for other reasons. For instance, on Windows a socket is
- * always seen as writable until a write returns %G_IO_ERROR_WOULD_BLOCK.
+ * When working in non-blocking mode applications should always be able to
+ * handle getting a %G_IO_ERROR_WOULD_BLOCK error even when some other
+ * function said that I/O was possible. This can easily happen in case
+ * of a race condition in the application, but it can also happen for other
+ * reasons. For instance, on Windows a socket is always seen as writable
+ * until a write returns %G_IO_ERROR_WOULD_BLOCK.
  *
  * #GSocket<!-- -->s can be either connection oriented or datagram based.
  * For connection oriented types you must first establish a connection by
@@ -136,7 +139,6 @@ struct _GSocketPrivate
   guint           reuse_address : 1;
   guint           keepalive : 1;
   guint           closed : 1;
-  guint           blocking_mode_unknown : 1;
 #ifdef G_OS_WIN32
   WSAEVENT        event;
   int             current_events;
@@ -228,6 +230,37 @@ _win32_unset_event_mask (GSocket *socket, int mask)
 #define win32_unset_event_mask(_socket, _mask)
 #endif
 
+static void
+set_fd_nonblocking (int fd)
+{
+#ifndef G_OS_WIN32
+  glong arg;
+#else
+  gulong arg;
+#endif
+
+#ifndef G_OS_WIN32
+  if ((arg = fcntl (fd, F_GETFL, NULL)) < 0)
+    {
+      g_warning ("Error getting socket status flags: %s", socket_strerror (errno));
+      arg = 0;
+    }
+
+  arg = arg | O_NONBLOCK;
+
+  if (fcntl (fd, F_SETFL, arg) < 0)
+      g_warning ("Error setting socket status flags: %s", socket_strerror (errno));
+#else
+  arg = TRUE;
+
+  if (ioctlsocket (fd, FIONBIO, &arg) == SOCKET_ERROR)
+    {
+      int errsv = get_socket_errno ();
+      g_warning ("Error setting socket status flags: %s", socket_strerror (errsv));
+    }
+#endif
+}
+
 static gboolean
 check_socket (GSocket *socket,
 	      GError **error)
@@ -349,23 +382,6 @@ g_socket_details_from_fd (GSocket *socket)
           g_socket_address_new_from_native (&address, addrlen);
     }
 
-#ifndef G_OS_WIN32
-  {
-    int result;
-    result = fcntl (fd, F_GETFL, NULL);
-    if (result == -1)
-      {
-	errsv = get_socket_errno ();
-	goto err;
-      }
-    socket->priv->blocking = !(result & O_NONBLOCK);
-  }
-#else
-  /* There doesn't seem to be a way to get this on win32... */
-  socket->priv->blocking = FALSE;
-  socket->priv->blocking_mode_unknown = TRUE;
-#endif
-
   optlen = sizeof bool_val;
   if (getsockopt (fd, SOL_SOCKET, SO_KEEPALIVE,
 		  (void *)&bool_val, &optlen) == 0)
@@ -502,6 +518,13 @@ g_socket_constructed (GObject *object)
                                                socket->priv->type,
                                                socket->priv->protocol,
                                                &socket->priv->construct_error);
+
+  /* Always use native nonblocking sockets, as
+     windows sets sockets to nonblocking automatically
+     in certain operations. This way we make things work
+     the same on all platforms */
+  if (socket->priv->fd != -1)
+    set_fd_nonblocking (socket->priv->fd);
 }
 
 static void
@@ -835,9 +858,9 @@ g_socket_new (GSocketFamily family,
  * or winsock SOCKET handle.
  *
  * This reads all the settings from the file descriptor so that
- * all properties should work. However, on Windows it is not possible
- * to read the non-blocking property of a socket so it won't be
- * reliably detected.
+ * all properties should work. Note that the file descriptor
+ * will be set to non-blocking mode, independent on the blocking
+ * mode of the #GSocket.
  *
  * Returns: a #GSocket or %NULL on error.
  *     Free the returned object with g_object_unref().
@@ -864,14 +887,9 @@ g_socket_new_from_fd (gint fd,
  * non-blocking mode all functions return results immediately or
  * with a %G_IO_ERROR_WOULD_BLOCK error.
  *
- * All sockets are created in blocking mode. Although on windows any
- * call to g_socket_create_source(), g_socket_condition_check(), or
- * g_socket_condition_wait() will automatically enable non-blocking mode.
- *
- * This call generally always succeeds, however on Windows you cannot
- * set a socket to blocking while there is a source from g_socket_create_source()
- * for @socket alive. If you need to ensure blocking mode was enabled, read the
- * current value with g_socket_get_blocking() after setting.
+ * All sockets are created in blocking mode. However, note that the
+ * platform level socket is always non-blocking, and blocking mode
+ * is a GSocket level feature.
  *
  * Since: 2.22
  **/
@@ -879,43 +897,14 @@ void
 g_socket_set_blocking (GSocket  *socket,
                        gboolean  blocking)
 {
-#ifndef G_OS_WIN32
-  glong arg;
-#else
-  gulong arg;
-#endif
-
   g_return_if_fail (G_IS_SOCKET (socket));
 
   blocking = !!blocking;
 
-  if (socket->priv->blocking == blocking &&
-      !socket->priv->blocking_mode_unknown)
+  if (socket->priv->blocking == blocking)
     return;
 
-#ifndef G_OS_WIN32
-  if ((arg = fcntl (socket->priv->fd, F_GETFL, NULL)) < 0)
-    {
-      g_warning ("Error getting socket status flags: %s", socket_strerror (errno));
-      return; /* Don't change blocking */
-    }
-
-  arg = blocking ? arg & ~O_NONBLOCK : arg | O_NONBLOCK;
-
-  if (fcntl (socket->priv->fd, F_SETFL, arg) < 0)
-#else
-  arg = !blocking;
-
-  if (ioctlsocket (socket->priv->fd, FIONBIO, &arg) == SOCKET_ERROR)
-#endif
-    {
-      int errsv = get_socket_errno ();
-      g_warning ("Error setting socket status flags: %s", socket_strerror (errsv));
-      return; /* Don't change blocking */
-    }
-
   socket->priv->blocking = blocking;
-  socket->priv->blocking_mode_unknown = FALSE;
   g_object_notify (G_OBJECT (socket), "blocking");
 }
 
@@ -1403,14 +1392,31 @@ g_socket_accept (GSocket       *socket,
 
   while (1)
     {
+      if (socket->priv->blocking &&
+	  !g_socket_condition_wait (socket,
+				    G_IO_IN, NULL, error))
+	return NULL;
+
       if ((ret = accept (socket->priv->fd, NULL, 0)) < 0)
 	{
 	  int errsv = get_socket_errno ();
 
+	  win32_unset_event_mask (socket, FD_ACCEPT);
+
 	  if (errsv == EINTR)
 	    continue;
 
-	  win32_unset_event_mask (socket, FD_ACCEPT);
+	  if (socket->priv->blocking)
+	    {
+#ifdef WSAEWOULDBLOCK
+	      if (errsv == WSAEWOULDBLOCK)
+		continue;
+#else
+	      if (errsv == EWOULDBLOCK ||
+		  errsv == EAGAIN)
+		continue;
+#endif
+	    }
 
 	  g_set_error (error, G_IO_ERROR,
 		       socket_io_error_from_errno (errsv),
@@ -1424,18 +1430,9 @@ g_socket_accept (GSocket       *socket,
 
 #ifdef G_OS_WIN32
   {
-    gulong arg;
-
     /* The socket inherits the accepting sockets event mask and even object,
        we need to remove that */
     WSAEventSelect (ret, NULL, 0);
-
-    /* It also inherits the blocking mode, but on unix newly accepted
-       sockets are blocking, disable blocking to get the same behaviour
-       as on unix. */
-    arg = FALSE;
-    if (ioctlsocket (ret, FIONBIO, &arg) == SOCKET_ERROR)
-      g_warning ("Unable to set newly allocated socket to blocking mode");
   }
 #else
   {
@@ -1455,16 +1452,7 @@ g_socket_accept (GSocket       *socket,
 #endif
 
   new_socket = g_socket_new_from_fd (ret, error);
-
-  if (new_socket)
-    {
-#ifdef G_OS_WIN32
-      /* We set blocking above, and new_from_fd can't read this on win32 */
-      new_socket->priv->blocking = TRUE;
-      new_socket->priv->blocking_mode_unknown = FALSE;
-#endif
-    }
-  else
+  if (new_socket == NULL)
     {
 #ifdef G_OS_WIN32
       closesocket (ret);
@@ -1518,6 +1506,11 @@ g_socket_connect (GSocket         *socket,
 
   while (1)
     {
+      if (socket->priv->blocking &&
+	  !g_socket_condition_wait (socket,
+				    G_IO_IN, NULL, error))
+	return FALSE;
+
       if (connect (socket->priv->fd, (struct sockaddr *) buffer,
 		   g_socket_address_get_native_size (address)) < 0)
 	{
@@ -1531,8 +1524,19 @@ g_socket_connect (GSocket         *socket,
 #else
 	  if (errsv == WSAEINPROGRESS)
 #endif
-	    g_set_error (error, G_IO_ERROR, G_IO_ERROR_PENDING,
-			 _("Connection in progress"));
+	    {
+	      if (socket->priv->blocking)
+		{
+		  g_socket_condition_wait (socket, G_IO_OUT, NULL, NULL);
+		  if (g_socket_check_pending_error (socket, error))
+		    break;
+		  else
+		    g_prefix_error (error, _("Error connecting: "));
+		}
+	      else
+		g_set_error (error, G_IO_ERROR, G_IO_ERROR_PENDING,
+			     _("Connection in progress"));
+	    }
 	  else
 	    g_set_error (error, G_IO_ERROR,
 			 socket_io_error_from_errno (errsv),
@@ -1628,6 +1632,11 @@ g_socket_receive (GSocket       *socket,
 
   while (1)
     {
+      if (socket->priv->blocking &&
+	  !g_socket_condition_wait (socket,
+				    G_IO_IN, NULL, error))
+	return -1;
+
       if ((ret = recv (socket->priv->fd, buffer, size, 0)) < 0)
 	{
 	  int errsv = get_socket_errno ();
@@ -1635,6 +1644,18 @@ g_socket_receive (GSocket       *socket,
 	  if (errsv == EINTR)
 	    continue;
 
+	  if (socket->priv->blocking)
+	    {
+#ifdef WSAEWOULDBLOCK
+	      if (errsv == WSAEWOULDBLOCK)
+		continue;
+#else
+	      if (errsv == EWOULDBLOCK ||
+		  errsv == EAGAIN)
+		continue;
+#endif
+	    }
+
 	  win32_unset_event_mask (socket, FD_READ);
 
 	  g_set_error (error, G_IO_ERROR,
@@ -1739,6 +1760,11 @@ g_socket_send (GSocket      *socket,
 
   while (1)
     {
+      if (socket->priv->blocking &&
+	  !g_socket_condition_wait (socket,
+				    G_IO_OUT, NULL, error))
+	return -1;
+
       if ((ret = send (socket->priv->fd, buffer, size, 0)) < 0)
 	{
 	  int errsv = get_socket_errno ();
@@ -1751,6 +1777,18 @@ g_socket_send (GSocket      *socket,
 	    win32_unset_event_mask (socket, FD_WRITE);
 #endif
 
+	  if (socket->priv->blocking)
+	    {
+#ifdef WSAEWOULDBLOCK
+	      if (errsv == WSAEWOULDBLOCK)
+		continue;
+#else
+	      if (errsv == EWOULDBLOCK ||
+		  errsv == EAGAIN)
+		continue;
+#endif
+	    }
+
 	  g_set_error (error, G_IO_ERROR,
 		       socket_io_error_from_errno (errsv),
 		       _("Error sending data: %s"), socket_strerror (errsv));
@@ -1978,16 +2016,7 @@ update_select_events (GSocket *socket)
 	event = socket->priv->event;
 
       if (WSAEventSelect (socket->priv->fd, event, event_mask) == 0)
-	{
-	  socket->priv->selected_events = event_mask;
-
-	  /* This automatically enables nonblocking mode */
-	  if (socket->priv->blocking)
-	    {
-	      socket->priv->blocking = FALSE;
-	      g_object_notify (G_OBJECT (socket), "blocking");
-	    }
-	}
+	socket->priv->selected_events = event_mask;
     }
 }
 
@@ -2557,6 +2586,11 @@ g_socket_send_message (GSocket                *socket,
 
     while (1)
       {
+	if (socket->priv->blocking &&
+	    !g_socket_condition_wait (socket,
+				      G_IO_OUT, NULL, error))
+	  return -1;
+
 	result = sendmsg (socket->priv->fd, &msg, flags);
 	if (result < 0)
 	  {
@@ -2565,6 +2599,11 @@ g_socket_send_message (GSocket                *socket,
 	    if (errsv == EINTR)
 	      continue;
 
+	    if (socket->priv->blocking &&
+		(errsv == EWOULDBLOCK ||
+		 errsv == EAGAIN))
+	      continue;
+
 	    g_set_error (error, G_IO_ERROR,
 			 socket_io_error_from_errno (errsv),
 			 _("Error sending message: %s"), socket_strerror (errsv));
@@ -2613,6 +2652,11 @@ g_socket_send_message (GSocket                *socket,
 
     while (1)
       {
+	if (socket->priv->blocking &&
+	    !g_socket_condition_wait (socket,
+				      G_IO_OUT, NULL, error))
+	  return -1;
+
 	if (address)
 	  result = WSASendTo (socket->priv->fd,
 			      bufs, num_vectors,
@@ -2635,6 +2679,10 @@ g_socket_send_message (GSocket                *socket,
 	    if (errsv == WSAEWOULDBLOCK)
 	      win32_unset_event_mask (socket, FD_WRITE);
 
+	    if (socket->priv->blocking &&
+		errsv == WSAEWOULDBLOCK)
+	      continue;
+
 	    g_set_error (error, G_IO_ERROR,
 			 socket_io_error_from_errno (errsv),
 			 _("Error sending message: %s"), socket_strerror (errsv));
@@ -2804,6 +2852,11 @@ g_socket_receive_message (GSocket                 *socket,
     /* do it */
     while (1)
       {
+	if (socket->priv->blocking &&
+	    !g_socket_condition_wait (socket,
+				      G_IO_IN, NULL, error))
+	  return -1;
+
 	result = recvmsg (socket->priv->fd, &msg, msg.msg_flags);
 
 	if (result < 0)
@@ -2813,6 +2866,11 @@ g_socket_receive_message (GSocket                 *socket,
 	    if (errsv == EINTR)
 	      continue;
 
+	    if (socket->priv->blocking &&
+		(errsv == EWOULDBLOCK ||
+		 errsv == EAGAIN))
+	      continue;
+
 	    g_set_error (error, G_IO_ERROR,
 			 socket_io_error_from_errno (errsv),
 			 _("Error receiving message: %s"), socket_strerror (errsv));
@@ -2922,6 +2980,11 @@ g_socket_receive_message (GSocket                 *socket,
     /* do it */
     while (1)
       {
+	if (socket->priv->blocking &&
+	    !g_socket_condition_wait (socket,
+				      G_IO_IN, NULL, error))
+	  return -1;
+
 	addrlen = sizeof addr;
 	if (address)
 	  result = WSARecvFrom (socket->priv->fd,
@@ -2942,6 +3005,11 @@ g_socket_receive_message (GSocket                 *socket,
 	      continue;
 
 	    win32_unset_event_mask (socket, FD_READ);
+
+	    if (socket->priv->blocking &&
+		errsv == WSAEWOULDBLOCK)
+	      continue;
+
 	    g_set_error (error, G_IO_ERROR,
 			 socket_io_error_from_errno (errsv),
 			 _("Error receiving message: %s"), socket_strerror (errsv));



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]