diff --git a/docs/reference/gio/gio-sections.txt b/docs/reference/gio/gio-sections.txt index 71c597b10..c9ed66bc8 100644 --- a/docs/reference/gio/gio-sections.txt +++ b/docs/reference/gio/gio-sections.txt @@ -1852,6 +1852,7 @@ g_socket_is_connected g_socket_create_source g_socket_condition_check g_socket_condition_wait +g_socket_condition_timed_wait g_socket_get_available_bytes g_socket_set_listen_backlog g_socket_get_listen_backlog diff --git a/gio/gio.symbols b/gio/gio.symbols index 140eaab87..9b609c17f 100644 --- a/gio/gio.symbols +++ b/gio/gio.symbols @@ -949,6 +949,7 @@ g_socket_close g_socket_shutdown g_socket_condition_check g_socket_condition_wait +g_socket_condition_timed_wait g_socket_connect g_socket_create_source g_socket_get_available_bytes diff --git a/gio/gsocket.c b/gio/gsocket.c index c6a913765..d46d497a1 100644 --- a/gio/gsocket.c +++ b/gio/gsocket.c @@ -3404,6 +3404,8 @@ g_socket_condition_check (GSocket *socket, * the appropriate value (%G_IO_ERROR_CANCELLED or * %G_IO_ERROR_TIMED_OUT). * + * See also g_socket_condition_timed_wait(). + * * Returns: %TRUE if the condition was met, %FALSE otherwise * * Since: 2.22 @@ -3416,17 +3418,69 @@ g_socket_condition_wait (GSocket *socket, { g_return_val_if_fail (G_IS_SOCKET (socket), FALSE); + return g_socket_condition_timed_wait (socket, condition, -1, + cancellable, error); +} + +/** + * g_socket_condition_timed_wait: + * @socket: a #GSocket + * @condition: a #GIOCondition mask to wait for + * @timeout: the maximum time (in microseconds) to wait, or -1 + * @cancellable: (allow-none): a #GCancellable, or %NULL + * @error: a #GError pointer, or %NULL + * + * Waits for up to @timeout microseconds for @condition to become true + * on @socket. If the condition is met, %TRUE is returned. + * + * If @cancellable is cancelled before the condition is met, or if + * @timeout (or the socket's #GSocket:timeout) 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). + * + * If you don't want a timeout, use g_socket_condition_wait(). + * (Alternatively, you can pass -1 for @timeout.) + * + * Note that although @timeout is in microseconds for consistency with + * other GLib APIs, this function actually only has millisecond + * resolution, and the behavior is undefined if @timeout is not an + * exact number of milliseconds. + * + * Returns: %TRUE if the condition was met, %FALSE otherwise + * + * Since: 2.32 + */ +gboolean +g_socket_condition_timed_wait (GSocket *socket, + GIOCondition condition, + gint64 timeout, + GCancellable *cancellable, + GError **error) +{ + gint64 start_time; + + g_return_val_if_fail (G_IS_SOCKET (socket), FALSE); + if (!check_socket (socket, error)) return FALSE; if (g_cancellable_set_error_if_cancelled (cancellable, error)) return FALSE; + if (socket->priv->timeout && + (timeout < 0 || socket->priv->timeout < timeout / G_USEC_PER_SEC)) + timeout = socket->priv->timeout * 1000; + else if (timeout != -1) + timeout = timeout / 1000; + + start_time = g_get_monotonic_time (); + #ifdef G_OS_WIN32 { GIOCondition current_condition; WSAEVENT events[2]; - DWORD res, timeout; + DWORD res; GPollFD cancel_fd; int num_events; @@ -3441,16 +3495,14 @@ 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 + if (timeout == -1) timeout = WSA_INFINITE; current_condition = update_condition (socket); while ((condition & current_condition) == 0) { - res = WSAWaitForMultipleEvents(num_events, events, - FALSE, timeout, FALSE); + res = WSAWaitForMultipleEvents (num_events, events, + FALSE, timeout, FALSE); if (res == WSA_WAIT_FAILED) { int errsv = get_socket_errno (); @@ -3472,6 +3524,13 @@ g_socket_condition_wait (GSocket *socket, break; current_condition = update_condition (socket); + + if (timeout != WSA_INFINITE) + { + timeout -= (g_get_monotonic_time () - start_time) * 1000; + if (timeout < 0) + timeout = 0; + } } remove_condition_watch (socket, &condition); if (num_events > 1) @@ -3484,7 +3543,6 @@ 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; @@ -3493,14 +3551,19 @@ 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; + while (TRUE) + { + result = g_poll (poll_fd, num, timeout); + if (result != -1 || errno != EINTR) + break; - do - result = g_poll (poll_fd, num, timeout); - while (result == -1 && get_socket_errno () == EINTR); + if (timeout != -1) + { + timeout -= (g_get_monotonic_time () - start_time) * 1000; + if (timeout < 0) + timeout = 0; + } + } if (num > 1) g_cancellable_release_fd (cancellable); diff --git a/gio/gsocket.h b/gio/gsocket.h index 1ad6a6fc4..d817328a1 100644 --- a/gio/gsocket.h +++ b/gio/gsocket.h @@ -145,6 +145,11 @@ gboolean g_socket_condition_wait (GSocket GIOCondition condition, GCancellable *cancellable, GError **error); +gboolean g_socket_condition_timed_wait (GSocket *socket, + GIOCondition condition, + gint64 timeout, + GCancellable *cancellable, + GError **error); GSocket * g_socket_accept (GSocket *socket, GCancellable *cancellable, GError **error); diff --git a/gio/tests/socket.c b/gio/tests/socket.c index ecdb3be15..7404bba8f 100644 --- a/gio/tests/socket.c +++ b/gio/tests/socket.c @@ -583,6 +583,56 @@ test_ipv6_v4mapped (void) } #endif +static void +test_timed_wait (void) +{ + IPTestData *data; + GError *error = NULL; + GSocket *client; + GSocketAddress *addr; + gint64 start_time; + gint poll_duration; + + data = create_server (G_SOCKET_FAMILY_IPV4, echo_server_thread, FALSE); + addr = g_socket_get_local_address (data->server, &error); + + client = g_socket_new (G_SOCKET_FAMILY_IPV4, + G_SOCKET_TYPE_STREAM, + G_SOCKET_PROTOCOL_DEFAULT, + &error); + g_assert_no_error (error); + + g_socket_set_blocking (client, TRUE); + g_socket_set_timeout (client, 1); + + g_socket_connect (client, addr, NULL, &error); + g_assert_no_error (error); + g_object_unref (addr); + + start_time = g_get_monotonic_time (); + g_socket_condition_timed_wait (client, G_IO_IN, 100000 /* 100 ms */, + NULL, &error); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT); + g_clear_error (&error); + poll_duration = g_get_monotonic_time () - start_time; + + g_assert_cmpint (poll_duration, >=, 100000); + g_assert_cmpint (poll_duration, <, 110000); + + g_socket_close (client, &error); + g_assert_no_error (error); + + g_thread_join (data->thread); + + g_socket_close (data->server, &error); + g_assert_no_error (error); + + g_object_unref (data->server); + g_object_unref (client); + + g_slice_free (IPTestData, data); +} + static void test_sockaddr (void) { @@ -774,10 +824,11 @@ main (int argc, g_test_add_func ("/socket/ipv4_async", test_ipv4_async); g_test_add_func ("/socket/ipv6_sync", test_ipv6_sync); g_test_add_func ("/socket/ipv6_async", test_ipv6_async); - g_test_add_func ("/socket/close_graceful", test_close_graceful); #if defined (IPPROTO_IPV6) && defined (IPV6_V6ONLY) g_test_add_func ("/socket/ipv6_v4mapped", test_ipv6_v4mapped); #endif + g_test_add_func ("/socket/close_graceful", test_close_graceful); + g_test_add_func ("/socket/timed_wait", test_timed_wait); g_test_add_func ("/socket/address", test_sockaddr); #ifdef G_OS_UNIX g_test_add_func ("/socket/unix-from-fd", test_unix_from_fd);