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
This commit is contained in:
Dan Winship 2014-02-01 14:21:10 +01:00
parent d690b3dcd0
commit 074df39681
2 changed files with 100 additions and 27 deletions

View File

@ -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;

View File

@ -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();
}