+Overview of changes in GLib 2.66.3
+==================================
+
+* Fix awkward bug with `GPollFD` handling in some situations (work by Claudio
+ Saavedra and Eugene M) (#1592)
+
+* Fix sending FDs attached to very large D-Bus messages (work by Simon McVittie
+ and Giovanni Campagna) (#2074)
+
+* Bugs fixed:
+ - #1592 Main loop ignores GPollFD sources when there is at least one source ready with priority higher than default one
+ - !1720 Backport !1718 “gtrace: Add G_GNUC_PRINTF annotation” to glib-2-66
+ - !1721 Backport !1713 “gmain: g_main_context_check() can skip updating polled FD sources” to glib-2-66
+ - !1723 Backport !1711 “Fix race in socketclient-slow test” to glib-2-66
+ - !1727 Backport !1725 “gdbus: Cope with sending fds in a message that takes multiple writes” to glib-2-66
+ - !1736 Backport !1734 “glocalfileinfo: Use a single timeout source at a time for hidden file cache” to glib-2-66
+
+
Overview of changes in GLib 2.66.2
==================================
else
{
#ifdef G_OS_UNIX
- if (fd_list != NULL)
+ if (data->total_written == 0 && fd_list != NULL)
{
+ /* We were trying to write byte 0 of the message, which needs
+ * the fd list to be attached to it, but this connection doesn't
+ * support doing that. */
g_task_return_new_error (task,
G_IO_ERROR,
G_IO_ERROR_FAILED,
/* support for '.hidden' files */
G_LOCK_DEFINE_STATIC (hidden_cache);
static GHashTable *hidden_cache;
+static GSource *hidden_cache_source = NULL; /* Under the hidden_cache lock */
+static guint hidden_cache_ttl_secs = 5;
+static guint hidden_cache_ttl_jitter_secs = 2;
+
+typedef struct
+{
+ GHashTable *hidden_files;
+ gint64 timestamp_secs;
+} HiddenCacheData;
static gboolean
remove_from_hidden_cache (gpointer user_data)
{
+ HiddenCacheData *data;
+ GHashTableIter iter;
+ gboolean retval;
+ gint64 timestamp_secs;
+
G_LOCK (hidden_cache);
- g_hash_table_remove (hidden_cache, user_data);
+ timestamp_secs = g_source_get_time (hidden_cache_source) / G_USEC_PER_SEC;
+
+ g_hash_table_iter_init (&iter, hidden_cache);
+ while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &data))
+ {
+ if (timestamp_secs > data->timestamp_secs + hidden_cache_ttl_secs)
+ g_hash_table_iter_remove (&iter);
+ }
+
+ if (g_hash_table_size (hidden_cache) == 0)
+ {
+ g_clear_pointer (&hidden_cache_source, g_source_unref);
+ retval = G_SOURCE_REMOVE;
+ }
+ else
+ retval = G_SOURCE_CONTINUE;
+
G_UNLOCK (hidden_cache);
- return FALSE;
+ return retval;
}
static GHashTable *
}
static void
-maybe_unref_hash_table (gpointer data)
+free_hidden_file_data (gpointer user_data)
{
- if (data != NULL)
- g_hash_table_unref (data);
+ HiddenCacheData *data = user_data;
+
+ g_clear_pointer (&data->hidden_files, g_hash_table_unref);
+ g_free (data);
}
static gboolean
file_is_hidden (const gchar *path,
const gchar *basename)
{
+ HiddenCacheData *data;
gboolean result;
gchar *dirname;
gpointer table;
if G_UNLIKELY (hidden_cache == NULL)
hidden_cache = g_hash_table_new_full (g_str_hash, g_str_equal,
- g_free, maybe_unref_hash_table);
+ g_free, free_hidden_file_data);
if (!g_hash_table_lookup_extended (hidden_cache, dirname,
- NULL, &table))
+ NULL, (gpointer *) &data))
{
gchar *mydirname;
- GSource *remove_from_cache_source;
+
+ data = g_new0 (HiddenCacheData, 1);
+ data->hidden_files = table = read_hidden_file (dirname);
+ data->timestamp_secs = g_get_monotonic_time () / G_USEC_PER_SEC;
g_hash_table_insert (hidden_cache,
mydirname = g_strdup (dirname),
- table = read_hidden_file (dirname));
+ data);
- remove_from_cache_source = g_timeout_source_new_seconds (5);
- g_source_set_priority (remove_from_cache_source, G_PRIORITY_DEFAULT);
- g_source_set_callback (remove_from_cache_source,
- remove_from_hidden_cache,
- mydirname,
- NULL);
- g_source_attach (remove_from_cache_source,
- GLIB_PRIVATE_CALL (g_get_worker_context) ());
- g_source_unref (remove_from_cache_source);
+ if (!hidden_cache_source)
+ {
+ hidden_cache_source =
+ g_timeout_source_new_seconds (hidden_cache_ttl_secs +
+ hidden_cache_ttl_jitter_secs);
+ g_source_set_priority (hidden_cache_source, G_PRIORITY_DEFAULT);
+ g_source_set_name (hidden_cache_source,
+ "[gio] remove_from_hidden_cache");
+ g_source_set_callback (hidden_cache_source,
+ remove_from_hidden_cache,
+ NULL, NULL);
+ g_source_attach (hidden_cache_source,
+ GLIB_PRIVATE_CALL (g_get_worker_context) ());
+ }
}
+ else
+ table = data->hidden_files;
result = table != NULL && g_hash_table_contains (table, basename);
gboolean signal_received;
} PeerData;
+/* This needs to be enough to usually take more than one write(),
+ * to reproduce
+ * <https://gitlab.gnome.org/GNOME/glib/-/issues/2074>.
+ * 1 MiB ought to be enough. */
+#define BIG_MESSAGE_ARRAY_SIZE (1024 * 1024)
+
static const gchar *test_interface_introspection_xml =
"<node>"
" <interface name='org.gtk.GDBus.PeerTestInterface'>"
" <method name='OpenFile'>"
" <arg type='s' name='path' direction='in'/>"
" </method>"
+ " <method name='OpenFileWithBigMessage'>"
+ " <arg type='s' name='path' direction='in'/>"
+ " <arg type='h' name='handle' direction='out'/>"
+ " <arg type='ay' name='junk' direction='out'/>"
+ " </method>"
" <signal name='PeerSignal'>"
" <arg type='s' name='a_string'/>"
" </signal>"
g_dbus_method_invocation_return_value (invocation, NULL);
}
- else if (g_strcmp0 (method_name, "OpenFile") == 0)
+ else if (g_strcmp0 (method_name, "OpenFile") == 0 ||
+ g_strcmp0 (method_name, "OpenFileWithBigMessage") == 0)
{
#ifdef G_OS_UNIX
const gchar *path;
g_object_unref (fd_list);
g_object_unref (invocation);
+ if (g_strcmp0 (method_name, "OpenFileWithBigMessage") == 0)
+ {
+ char *junk;
+
+ junk = g_new0 (char, BIG_MESSAGE_ARRAY_SIZE);
+ g_dbus_message_set_body (reply,
+ g_variant_new ("(h@ay)",
+ 0,
+ g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
+ junk,
+ BIG_MESSAGE_ARRAY_SIZE,
+ 1)));
+ g_free (junk);
+ }
+
error = NULL;
g_dbus_connection_send_message (connection,
reply,
const gchar *s;
GThread *service_thread;
gulong signal_handler_id;
+ gsize i;
memset (&data, '\0', sizeof (PeerData));
data.current_connections = g_ptr_array_new_with_free_func (g_object_unref);
g_assert_cmpint (data.num_method_calls, ==, 3);
g_signal_handler_disconnect (proxy, signal_handler_id);
- /* check for UNIX fd passing */
+ /*
+ * Check for UNIX fd passing.
+ *
+ * The first time through, we use a very simple method call. Note that
+ * because this does not have a G_VARIANT_TYPE_HANDLE in the message body
+ * to refer to the fd, it is a GDBus-specific idiom that would not
+ * interoperate with libdbus or sd-bus
+ * (see <https://gitlab.gnome.org/GNOME/glib/-/merge_requests/1726>).
+ *
+ * The second time, we call a method that returns a fd attached to a
+ * large message, to reproduce
+ * <https://gitlab.gnome.org/GNOME/glib/-/issues/2074>. It also happens
+ * to follow the more usual pattern for D-Bus messages containing a
+ * G_VARIANT_TYPE_HANDLE to refer to attached fds.
+ */
+ for (i = 0; i < 2; i++)
+ {
#ifdef G_OS_UNIX
- {
- GDBusMessage *method_call_message;
- GDBusMessage *method_reply_message;
- GUnixFDList *fd_list;
- gint fd;
- gchar *buf;
- gsize len;
- gchar *buf2;
- gsize len2;
- const char *testfile = g_test_get_filename (G_TEST_DIST, "file.c", NULL);
-
- method_call_message = g_dbus_message_new_method_call (NULL, /* name */
- "/org/gtk/GDBus/PeerTestObject",
- "org.gtk.GDBus.PeerTestInterface",
- "OpenFile");
- g_dbus_message_set_body (method_call_message, g_variant_new ("(s)", testfile));
- error = NULL;
- method_reply_message = g_dbus_connection_send_message_with_reply_sync (c,
- method_call_message,
- G_DBUS_SEND_MESSAGE_FLAGS_NONE,
- -1,
- NULL, /* out_serial */
- NULL, /* cancellable */
- &error);
- g_assert_no_error (error);
- g_assert (g_dbus_message_get_message_type (method_reply_message) == G_DBUS_MESSAGE_TYPE_METHOD_RETURN);
- fd_list = g_dbus_message_get_unix_fd_list (method_reply_message);
- g_assert (fd_list != NULL);
- g_assert_cmpint (g_unix_fd_list_get_length (fd_list), ==, 1);
- error = NULL;
- fd = g_unix_fd_list_get (fd_list, 0, &error);
- g_assert_no_error (error);
- g_object_unref (method_call_message);
- g_object_unref (method_reply_message);
+ GDBusMessage *method_call_message;
+ GDBusMessage *method_reply_message;
+ GUnixFDList *fd_list;
+ gint fd;
+ gchar *buf;
+ gsize len;
+ gchar *buf2;
+ gsize len2;
+ const char *testfile = g_test_get_filename (G_TEST_DIST, "file.c", NULL);
+ const char *method = "OpenFile";
+ GVariant *body;
+
+ if (i == 1)
+ method = "OpenFileWithBigMessage";
+
+ method_call_message = g_dbus_message_new_method_call (NULL, /* name */
+ "/org/gtk/GDBus/PeerTestObject",
+ "org.gtk.GDBus.PeerTestInterface",
+ method);
+ g_dbus_message_set_body (method_call_message, g_variant_new ("(s)", testfile));
+ error = NULL;
+ method_reply_message = g_dbus_connection_send_message_with_reply_sync (c,
+ method_call_message,
+ G_DBUS_SEND_MESSAGE_FLAGS_NONE,
+ -1,
+ NULL, /* out_serial */
+ NULL, /* cancellable */
+ &error);
+ g_assert_no_error (error);
+ g_assert (g_dbus_message_get_message_type (method_reply_message) == G_DBUS_MESSAGE_TYPE_METHOD_RETURN);
- error = NULL;
- len = 0;
- buf = read_all_from_fd (fd, &len, &error);
- g_assert_no_error (error);
- g_assert (buf != NULL);
- close (fd);
+ body = g_dbus_message_get_body (method_reply_message);
- error = NULL;
- g_file_get_contents (testfile,
- &buf2,
- &len2,
- &error);
- g_assert_no_error (error);
- g_assert_cmpmem (buf, len, buf2, len2);
- g_free (buf2);
- g_free (buf);
- }
+ if (i == 1)
+ {
+ gint32 handle = -1;
+ GVariant *junk = NULL;
+
+ g_assert_cmpstr (g_variant_get_type_string (body), ==, "(hay)");
+ g_variant_get (body, "(h@ay)", &handle, &junk);
+ g_assert_cmpint (handle, ==, 0);
+ g_assert_cmpuint (g_variant_n_children (junk), ==, BIG_MESSAGE_ARRAY_SIZE);
+ g_variant_unref (junk);
+ }
+ else
+ {
+ g_assert_null (body);
+ }
+
+ fd_list = g_dbus_message_get_unix_fd_list (method_reply_message);
+ g_assert (fd_list != NULL);
+ g_assert_cmpint (g_unix_fd_list_get_length (fd_list), ==, 1);
+ error = NULL;
+ fd = g_unix_fd_list_get (fd_list, 0, &error);
+ g_assert_no_error (error);
+ g_object_unref (method_call_message);
+ g_object_unref (method_reply_message);
+
+ error = NULL;
+ len = 0;
+ buf = read_all_from_fd (fd, &len, &error);
+ g_assert_no_error (error);
+ g_assert (buf != NULL);
+ close (fd);
+
+ error = NULL;
+ g_file_get_contents (testfile,
+ &buf2,
+ &len2,
+ &error);
+ g_assert_no_error (error);
+ g_assert_cmpmem (buf, len, buf2, len2);
+ g_free (buf2);
+ g_free (buf);
#else
- error = NULL;
- result = g_dbus_proxy_call_sync (proxy,
- "OpenFile",
- g_variant_new ("(s)", "boo"),
- G_DBUS_CALL_FLAGS_NONE,
- -1,
- NULL, /* GCancellable */
- &error);
- g_assert_error (error, G_IO_ERROR, G_IO_ERROR_DBUS_ERROR);
- g_assert (result == NULL);
- g_error_free (error);
+ /* We do the same number of iterations on non-Unix, so that
+ * the method call count will match. In this case we use
+ * OpenFile both times, because the difference between this
+ * and OpenFileWithBigMessage is only relevant on Unix. */
+ error = NULL;
+ result = g_dbus_proxy_call_sync (proxy,
+ "OpenFile",
+ g_variant_new ("(s)", "boo"),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL, /* GCancellable */
+ &error);
+ g_assert_error (error, G_IO_ERROR, G_IO_ERROR_DBUS_ERROR);
+ g_assert (result == NULL);
+ g_error_free (error);
#endif /* G_OS_UNIX */
+ }
/* Check that g_socket_get_credentials() work - (though this really
* should be in socket.c)
g_variant_get (result, "(&s)", &s);
g_assert_cmpstr (s, ==, "You greeted me with 'Hey Again Peer!'.");
g_variant_unref (result);
- g_assert_cmpint (data.num_method_calls, ==, 5);
+ g_assert_cmpint (data.num_method_calls, ==, 6);
#if 0
/* TODO: THIS TEST DOESN'T WORK YET */
g_main_loop_quit (user_data);
}
-static int
-on_timer (GCancellable *cancel)
+typedef struct
{
- g_cancellable_cancel (cancel);
- return G_SOURCE_REMOVE;
-}
+ GCancellable *cancellable;
+ gboolean completed;
+} EventCallbackData;
static void
on_event (GSocketClient *client,
GSocketClientEvent event,
GSocketConnectable *connectable,
GIOStream *connection,
- gboolean *got_completed_event)
+ EventCallbackData *data)
{
- if (event == G_SOCKET_CLIENT_COMPLETE)
+ if (data->cancellable && event == G_SOCKET_CLIENT_CONNECTED)
+ {
+ g_cancellable_cancel (data->cancellable);
+ }
+ else if (event == G_SOCKET_CLIENT_COMPLETE)
{
- *got_completed_event = TRUE;
+ data->completed = TRUE;
g_assert_null (connection);
}
}
GError *error = NULL;
guint16 port;
GMainLoop *loop;
- GCancellable *cancel;
- gboolean got_completed_event = FALSE;
+ EventCallbackData data = { NULL, FALSE };
/* This just tests that cancellation works as expected, still emits the completed signal,
* and never returns a connection */
g_socket_service_start (service);
client = g_socket_client_new ();
- cancel = g_cancellable_new ();
- g_socket_client_connect_to_host_async (client, "localhost", port, cancel, on_connected_cancelled, loop);
- g_timeout_add (1, (GSourceFunc) on_timer, cancel);
- g_signal_connect (client, "event", G_CALLBACK (on_event), &got_completed_event);
+ data.cancellable = g_cancellable_new ();
+ g_socket_client_connect_to_host_async (client, "localhost", port, data.cancellable, on_connected_cancelled, loop);
+ g_signal_connect (client, "event", G_CALLBACK (on_event), &data);
g_main_loop_run (loop);
- g_assert_true (got_completed_event);
+ g_assert_true (data.completed);
g_main_loop_unref (loop);
g_object_unref (service);
g_object_unref (client);
- g_object_unref (cancel);
+ g_object_unref (data.cancellable);
}
static void
guint16 port;
GMainLoop *loop;
GCancellable *cancel;
- gboolean got_completed_event = FALSE;
+ EventCallbackData data = { NULL, FALSE };
/* This tests the same things as above, test_happy_eyeballs_cancel_delayed(), but
* with different timing since it sends an already cancelled cancellable */
cancel = g_cancellable_new ();
g_cancellable_cancel (cancel);
g_socket_client_connect_to_host_async (client, "localhost", port, cancel, on_connected_cancelled, loop);
- g_signal_connect (client, "event", G_CALLBACK (on_event), &got_completed_event);
+ g_signal_connect (client, "event", G_CALLBACK (on_event), &data);
g_main_loop_run (loop);
- g_assert_true (got_completed_event);
+ g_assert_true (data.completed);
g_main_loop_unref (loop);
g_object_unref (service);
g_object_unref (client);
* store #GPollFD records that need to be polled.
* @n_fds: (in): length of @fds.
*
- * Determines information necessary to poll this main loop.
+ * Determines information necessary to poll this main loop. You should
+ * be careful to pass the resulting @fds array and its length @n_fds
+ * as is when calling g_main_context_check(), as this function relies
+ * on assumptions made when the array is filled.
*
* You must have successfully acquired the context with
* g_main_context_acquire() before you may call this function.
TRACE (GLIB_MAIN_CONTEXT_BEFORE_QUERY (context, max_priority));
+ /* fds is filled sequentially from poll_records. Since poll_records
+ * are incrementally sorted by file descriptor identifier, fds will
+ * also be incrementally sorted.
+ */
n_poll = 0;
lastpollrec = NULL;
for (pollrec = context->poll_records; pollrec; pollrec = pollrec->next)
*/
events = pollrec->fd->events & ~(G_IO_ERR|G_IO_HUP|G_IO_NVAL);
+ /* This optimization --using the same GPollFD to poll for more
+ * than one poll record-- relies on the poll records being
+ * incrementally sorted.
+ */
if (lastpollrec && pollrec->fd->fd == lastpollrec->fd->fd)
{
if (n_poll - 1 < n_fds)
* the last call to g_main_context_query()
* @n_fds: return value of g_main_context_query()
*
- * Passes the results of polling back to the main loop.
+ * Passes the results of polling back to the main loop. You should be
+ * careful to pass @fds and its length @n_fds as received from
+ * g_main_context_query(), as this functions relies on assumptions
+ * on how @fds is filled.
*
* You must have successfully acquired the context with
* g_main_context_acquire() before you may call this function.
return FALSE;
}
+ /* The linear iteration below relies on the assumption that both
+ * poll records and the fds array are incrementally sorted by file
+ * descriptor identifier.
+ */
pollrec = context->poll_records;
i = 0;
while (pollrec && i < n_fds)
{
+ /* Make sure that fds is sorted by file descriptor identifier. */
+ g_assert (i <= 0 || fds[i - 1].fd < fds[i].fd);
+
+ /* Skip until finding the first GPollRec matching the current GPollFD. */
+ while (pollrec && pollrec->fd->fd != fds[i].fd)
+ pollrec = pollrec->next;
+
+ /* Update all consecutive GPollRecs that match. */
while (pollrec && pollrec->fd->fd == fds[i].fd)
{
if (pollrec->priority <= max_priority)
pollrec = pollrec->next;
}
+ /* Iterate to next GPollFD. */
i++;
}
newrec->fd = fd;
newrec->priority = priority;
+ /* Poll records are incrementally sorted by file descriptor identifier. */
prevrec = NULL;
nextrec = context->poll_records;
while (nextrec)
const gchar *group,
const gchar *name,
const gchar *message_format,
- ...);
+ ...) G_GNUC_PRINTF (5, 6);
#ifndef HAVE_SYSPROF
/* Optimise the whole call out */
close (fd);
}
+static void
+test_unix_fd_priority (void)
+{
+ gint fd1, fd2;
+ GMainLoop *loop;
+ GSource *source;
+
+ gint s1 = 0;
+ gboolean s2 = FALSE, s3 = FALSE;
+
+ g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/1592");
+
+ loop = g_main_loop_new (NULL, FALSE);
+
+ source = g_idle_source_new ();
+ g_source_set_callback (source, count_calls, &s1, NULL);
+ g_source_set_priority (source, 0);
+ g_source_attach (source, NULL);
+ g_source_unref (source);
+
+ fd1 = open ("/dev/random", O_RDONLY);
+ g_assert_cmpint (fd1, >=, 0);
+ source = g_unix_fd_source_new (fd1, G_IO_IN);
+ g_source_set_callback (source, G_SOURCE_FUNC (flag_bool), &s2, NULL);
+ g_source_set_priority (source, 10);
+ g_source_attach (source, NULL);
+ g_source_unref (source);
+
+ fd2 = open ("/dev/random", O_RDONLY);
+ g_assert_cmpint (fd2, >=, 0);
+ source = g_unix_fd_source_new (fd2, G_IO_IN);
+ g_source_set_callback (source, G_SOURCE_FUNC (flag_bool), &s3, NULL);
+ g_source_set_priority (source, 0);
+ g_source_attach (source, NULL);
+ g_source_unref (source);
+
+ /* This tests a bug that depends on the source with the lowest FD
+ identifier to have the lowest priority. Make sure that this is
+ the case. */
+ g_assert_cmpint (fd1, <, fd2);
+
+ g_assert_true (g_main_context_iteration (NULL, FALSE));
+
+ /* Idle source should have been dispatched. */
+ g_assert_cmpint (s1, ==, 1);
+ /* Low priority FD source shouldn't have been dispatched. */
+ g_assert_false (s2);
+ /* Default priority FD source should have been dispatched. */
+ g_assert_true (s3);
+
+ g_main_loop_unref (loop);
+
+ close (fd1);
+ close (fd2);
+}
+
#endif
#ifdef G_OS_UNIX
g_test_add_func ("/mainloop/source-unix-fd-api", test_source_unix_fd_api);
g_test_add_func ("/mainloop/wait", test_mainloop_wait);
g_test_add_func ("/mainloop/unix-file-poll", test_unix_file_poll);
+ g_test_add_func ("/mainloop/unix-fd-priority", test_unix_fd_priority);
#endif
g_test_add_func ("/mainloop/nfds", test_nfds);
project('glib', 'c', 'cpp',
- version : '2.66.2',
+ version : '2.66.3',
# NOTE: We keep this pinned at 0.49 because that's what Debian 10 ships
meson_version : '>= 0.49.2',
default_options : [