[glib] Fix g_socket_get_available() with TCP on Windows



commit 074df396813692c7680c5406224131eda554d474
Author: Dan Winship <danw gnome org>
Date:   Sat Feb 1 14:21:10 2014 +0100

    Fix g_socket_get_available() with TCP on Windows
    
    Windows needs a special inefficient hack to implement
    g_socket_get_available() correctly for UDP sockets, but that hack
    isn't needed for TCP, and in fact, might give the wrong answer in that
    case. Fix it to only use the hack with UDP.
    
    Also, fix that case to handle non-blocking sockets as well.
    
    And add a test case for g_socket_get_available() with TCP.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=723422

 gio/gsocket.c      |   20 ++++++++--
 gio/tests/socket.c |  107 ++++++++++++++++++++++++++++++++++++++++-----------
 2 files changed, 100 insertions(+), 27 deletions(-)
---
diff --git a/gio/gsocket.c b/gio/gsocket.c
index fb3bf96..4b691f8 100644
--- a/gio/gsocket.c
+++ b/gio/gsocket.c
@@ -2478,8 +2478,10 @@ g_socket_get_available_bytes (GSocket *socket)
 #ifdef G_OS_WIN32
   const gint bufsize = 64 * 1024;
   static guchar *buf = NULL;
-#endif
+  u_long avail;
+#else
   gint avail;
+#endif
 
   g_return_val_if_fail (G_IS_SOCKET (socket), -1);
 
@@ -2490,10 +2492,20 @@ g_socket_get_available_bytes (GSocket *socket)
   if (ioctl (socket->priv->fd, FIONREAD, &avail) < 0)
     avail = -1;
 #else
-  if (G_UNLIKELY (g_once_init_enter (&buf)))
-    g_once_init_leave (&buf, g_malloc (bufsize));
+  if (socket->priv->type == G_SOCKET_TYPE_DATAGRAM)
+    {
+      if (G_UNLIKELY (g_once_init_enter (&buf)))
+        g_once_init_leave (&buf, g_malloc (bufsize));
 
-  avail = recv (socket->priv->fd, buf, bufsize, MSG_PEEK);
+      avail = recv (socket->priv->fd, buf, bufsize, MSG_PEEK);
+      if (avail == -1 && get_socket_errno () == WSAEWOULDBLOCK)
+        avail = 0;
+    }
+  else
+    {
+      if (ioctlsocket (socket->priv->fd, FIONREAD, &avail) < 0)
+        avail = -1;
+    }
 #endif
 
   return avail;
diff --git a/gio/tests/socket.c b/gio/tests/socket.c
index 7892b9c..4066563 100644
--- a/gio/tests/socket.c
+++ b/gio/tests/socket.c
@@ -896,41 +896,63 @@ test_reuse_udp (void)
 }
 
 static void
-test_datagram_get_available (void)
+test_get_available (gconstpointer user_data)
 {
+  GSocketType socket_type = GPOINTER_TO_UINT (user_data);
   GError *err = NULL;
-  GSocket *server, *client;
+  GSocket *listener, *server, *client;
   GInetAddress *addr;
   GSocketAddress *saddr;
   gchar data[] = "0123456789abcdef";
   gchar buf[34];
   gssize nread;
 
-  server = g_socket_new (G_SOCKET_FAMILY_IPV4,
-                                G_SOCKET_TYPE_DATAGRAM,
-                                G_SOCKET_PROTOCOL_DEFAULT,
-                                &err);
+  listener = g_socket_new (G_SOCKET_FAMILY_IPV4,
+                           socket_type,
+                           G_SOCKET_PROTOCOL_DEFAULT,
+                           &err);
   g_assert_no_error (err);
-  g_assert (G_IS_SOCKET (server));
+  g_assert (G_IS_SOCKET (listener));
 
   client = g_socket_new (G_SOCKET_FAMILY_IPV4,
-                        G_SOCKET_TYPE_DATAGRAM,
-                        G_SOCKET_PROTOCOL_DEFAULT,
-                        &err);
+                         socket_type,
+                         G_SOCKET_PROTOCOL_DEFAULT,
+                         &err);
   g_assert_no_error (err);
   g_assert (G_IS_SOCKET (client));
 
+  if (socket_type == G_SOCKET_TYPE_STREAM)
+    {
+      g_socket_set_option (client, IPPROTO_TCP, TCP_NODELAY, TRUE, &err);
+      g_assert_no_error (err);
+    }
+
   addr = g_inet_address_new_any (G_SOCKET_FAMILY_IPV4);
   saddr = g_inet_socket_address_new (addr, 0);
 
-  g_socket_bind (server, saddr, TRUE, &err);
+  g_socket_bind (listener, saddr, TRUE, &err);
   g_assert_no_error (err);
   g_object_unref (saddr);
   g_object_unref (addr);
 
-  saddr = g_socket_get_local_address (server, &err);
+  saddr = g_socket_get_local_address (listener, &err);
   g_assert_no_error (err);
 
+  if (socket_type == G_SOCKET_TYPE_STREAM)
+    {
+      g_socket_listen (listener, &err);
+      g_assert_no_error (err);
+      g_socket_connect (client, saddr, NULL, &err);
+      g_assert_no_error (err);
+
+      server = g_socket_accept (listener, NULL, &err);
+      g_assert_no_error (err);
+      g_socket_set_blocking (server, FALSE);
+      g_object_unref (listener);
+    }
+  else
+    server = listener;
+
   g_socket_send_to (client, saddr, data, sizeof (data), NULL, &err);
   g_assert_no_error (err);
 
@@ -941,24 +963,60 @@ test_datagram_get_available (void)
   g_socket_send_to (client, saddr, data, sizeof (data), NULL, &err);
   g_assert_no_error (err);
 
-  /* g_socket_condition_wait() won't help here since the socket is
-   * definitely already readable. So there's a race condition here, but
-   * at least the failure mode is passes-when-it-shouldn't, not
-   * fails-when-it-shouldn't.
+  /* We need to wait until the data has actually been copied into the
+   * server socket's buffers, but g_socket_condition_wait() won't help
+   * here since the socket is definitely already readable. So there's
+   * a race condition in checking its available bytes. In the TCP
+   * case, we poll for a bit until the new data shows up. In the UDP
+   * case, there's not much we can do, but at least the failure mode
+   * is passes-when-it-shouldn't, not fails-when-it-shouldn't.
    */
-  g_usleep (100000);
-  g_assert_cmpint (g_socket_get_available_bytes (server), ==, sizeof (data));
+  if (socket_type == G_SOCKET_TYPE_STREAM)
+    {
+      int tries;
+
+      for (tries = 0; tries < 100; tries++)
+        {
+          if (g_socket_get_available_bytes (server) > sizeof (data))
+            break;
+          g_usleep (100000);
+        }
+
+      g_assert_cmpint (g_socket_get_available_bytes (server), ==, 2 * sizeof (data));
+    }
+  else
+    {
+      g_usleep (100000);
+      g_assert_cmpint (g_socket_get_available_bytes (server), ==, sizeof (data));
+    }
 
   g_assert_cmpint (sizeof (buf), >=, 2 * sizeof (data));
   nread = g_socket_receive (server, buf, sizeof (buf), NULL, &err);
-  g_assert_cmpint (nread, ==, sizeof (data));
   g_assert_no_error (err);
 
-  g_assert_cmpint (g_socket_get_available_bytes (server), ==, sizeof (data));
+  if (socket_type == G_SOCKET_TYPE_STREAM)
+    {
+      g_assert_cmpint (nread, ==, 2 * sizeof (data));
+      g_assert_cmpint (g_socket_get_available_bytes (server), ==, 0);
+    }
+  else
+    {
+      g_assert_cmpint (nread, ==, sizeof (data));
+      g_assert_cmpint (g_socket_get_available_bytes (server), ==, sizeof (data));
+    }
 
   nread = g_socket_receive (server, buf, sizeof (buf), NULL, &err);
-  g_assert_cmpint (nread, ==, sizeof (data));
-  g_assert_no_error (err);
+  if (socket_type == G_SOCKET_TYPE_STREAM)
+    {
+      g_assert_cmpint (nread, ==, -1);
+      g_assert_error (err, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK);
+      g_clear_error (&err);
+    }
+  else
+    {
+      g_assert_cmpint (nread, ==, sizeof (data));
+      g_assert_no_error (err);
+    }
 
   g_assert_cmpint (g_socket_get_available_bytes (server), ==, 0);
 
@@ -993,7 +1051,10 @@ main (int   argc,
 #endif
   g_test_add_func ("/socket/reuse/tcp", test_reuse_tcp);
   g_test_add_func ("/socket/reuse/udp", test_reuse_udp);
-  g_test_add_func ("/socket/datagram_get_available", test_datagram_get_available);
+  g_test_add_data_func ("/socket/get_available/datagram", GUINT_TO_POINTER (G_SOCKET_TYPE_DATAGRAM),
+                        test_get_available);
+  g_test_add_data_func ("/socket/get_available/stream", GUINT_TO_POINTER (G_SOCKET_TYPE_STREAM),
+                        test_get_available);
 
   return g_test_run();
 }


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