summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexander Larsson <alexl@redhat.com>2009-05-14 11:41:26 (GMT)
committerAlexander Larsson <alexl@redhat.com>2009-05-14 11:41:26 (GMT)
commit6a9275874b05c933e78e184ce8ea25ca8db48c7d (patch)
tree05cbf58a88a356f12851c7d15069b665aed4e2a7
parent712f8f8613c2ae396da43ba93cf514f53761e5b1 (diff)
downloadgnio-6a9275874b05c933e78e184ce8ea25ca8db48c7d.tar.gz
gnio-6a9275874b05c933e78e184ce8ea25ca8db48c7d.tar.xz
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.
-rw-r--r--gio/gsocket.c260
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));