From 65cc5d895ae125b09f2403761f434fd78ef05af7 Mon Sep 17 00:00:00 2001 From: Dan Winship Date: Tue, 16 Jun 2009 20:22:58 -0400 Subject: [PATCH] Support g_main_context_push_thread_default() in gio GFile allows for the possibility that external implementations may not support thread-default contexts yet, via g_file_supports_thread_contexts(). GVolumeMonitor is not yet thread-default-context aware. Add a test program to verify that basic gio async ops work correctly in non-default contexts. http://bugzilla.gnome.org/show_bug.cgi?id=579984 --- gio/gdummyfile.c | 2 + gio/gfile.c | 25 ++++++ gio/gfile.h | 3 + gio/gfilemonitor.c | 26 ++++-- gio/gio.symbols | 1 + gio/gioscheduler.c | 29 ++++--- gio/glocalfile.c | 2 + gio/gsimpleasyncresult.c | 62 ++++++++++---- gio/gsocketclient.c | 2 +- gio/gsocketinputstream.c | 2 +- gio/gsocketlistener.c | 2 +- gio/gsocketoutputstream.c | 2 +- gio/gtcpconnection.c | 2 +- gio/gunixinputstream.c | 4 +- gio/gunixmount.c | 4 +- gio/gunixoutputstream.c | 4 +- gio/gunixresolver.c | 207 ++++++++++++++++++++++++++++++---------------- gio/gunixvolume.c | 4 +- gio/gvolumemonitor.c | 7 +- gio/gwin32resolver.c | 4 +- gio/tests/.gitignore | 1 + gio/tests/Makefile.am | 10 ++- gio/tests/contexts.c | 190 ++++++++++++++++++++++++++++++++++++++++++ 23 files changed, 474 insertions(+), 121 deletions(-) create mode 100644 gio/tests/contexts.c diff --git a/gio/gdummyfile.c b/gio/gdummyfile.c index 1033b4e..7d6e763 100644 --- a/gio/gdummyfile.c +++ b/gio/gdummyfile.c @@ -425,6 +425,8 @@ g_dummy_file_file_iface_init (GFileIface *iface) iface->get_relative_path = g_dummy_file_get_relative_path; iface->resolve_relative_path = g_dummy_file_resolve_relative_path; iface->get_child_for_display_name = g_dummy_file_get_child_for_display_name; + + iface->supports_thread_contexts = TRUE; } /* Uri handling helper functions: */ diff --git a/gio/gfile.c b/gio/gfile.c index 49adf46..1a95f94 100644 --- a/gio/gfile.c +++ b/gio/gfile.c @@ -6751,5 +6751,30 @@ g_file_stop_mountable_finish (GFile *file, return (* iface->stop_mountable_finish) (file, result, error); } +/** + * g_file_supports_thread_contexts: + * @file: a #GFile. + * + * Checks if @file supports thread-default + * contexts. If this returns %FALSE, you cannot perform + * asynchronous operations on @file in a thread that has a + * thread-default context. + * + * Returns: Whether or not @file supports thread-default contexts. + * + * Since: 2.22 + */ +gboolean +g_file_supports_thread_contexts (GFile *file) +{ + GFileIface *iface; + + g_return_val_if_fail (G_IS_FILE (file), FALSE); + + iface = G_FILE_GET_IFACE (file); + return iface->supports_thread_contexts; +} + #define __G_FILE_C__ #include "gioaliasdef.c" diff --git a/gio/gfile.h b/gio/gfile.h index 3707c82..1df47c6 100644 --- a/gio/gfile.h +++ b/gio/gfile.h @@ -505,6 +505,7 @@ struct _GFileIface gboolean (* stop_mountable_finish) (GFile *file, GAsyncResult *result, GError **error); + gboolean supports_thread_contexts; }; GType g_file_get_type (void) G_GNUC_CONST; @@ -940,6 +941,8 @@ gboolean g_file_replace_contents_finish (GFile *file, char **new_etag, GError **error); +gboolean g_file_supports_thread_contexts (GFile *file); + G_END_DECLS #endif /* __G_FILE_H__ */ diff --git a/gio/gfilemonitor.c b/gio/gfilemonitor.c index b24a466..a470baa 100644 --- a/gio/gfilemonitor.c +++ b/gio/gfilemonitor.c @@ -47,8 +47,14 @@ static void file_change_free (FileChange *change); * g_file_monitor(), g_file_monitor_file(), or * g_file_monitor_directory(). * - * To get informed about changes to the file or directory you - * are monitoring, connect to the #GFileMonitor::changed signal. + * To get informed about changes to the file or directory you are + * monitoring, connect to the #GFileMonitor::changed signal. The + * signal will be emitted in the thread-default main + * context of the thread that the monitor was created in + * (though if the global default main context is blocked, this may + * cause notifications to be blocked even if the thread-default + * context is still running). **/ G_LOCK_DEFINE_STATIC(cancelled); @@ -82,6 +88,8 @@ struct _GFileMonitorPrivate { GSource *timeout; guint32 timeout_fires_at; + + GMainContext *context; }; enum { @@ -169,6 +177,9 @@ g_file_monitor_finalize (GObject *object) g_hash_table_destroy (monitor->priv->rate_limiter); + if (monitor->priv->context) + g_main_context_unref (monitor->priv->context); + G_OBJECT_CLASS (g_file_monitor_parent_class)->finalize (object); } @@ -258,6 +269,7 @@ g_file_monitor_init (GFileMonitor *monitor) monitor->priv->rate_limit_msec = DEFAULT_RATE_LIMIT_MSECS; monitor->priv->rate_limiter = g_hash_table_new_full (g_file_hash, (GEqualFunc)g_file_equal, NULL, (GDestroyNotify) rate_limiter_free); + monitor->priv->context = g_main_context_get_thread_default (); } /** @@ -414,7 +426,7 @@ emit_in_idle (GFileMonitor *monitor, * pending idles. */ g_source_set_callback (source, emit_cb, monitor, NULL); - g_source_attach (source, NULL); + g_source_attach (source, monitor->priv->context); } /* We reverse this in the processor */ priv->pending_file_changes = g_slist_prepend (priv->pending_file_changes, change); @@ -570,7 +582,7 @@ rate_limiter_timeout (gpointer timeout_data) { source = g_timeout_source_new (data.min_time + 1); /* + 1 to make sure we've really passed the time */ g_source_set_callback (source, rate_limiter_timeout, monitor, NULL); - g_source_attach (source, NULL); + g_source_attach (source, monitor->priv->context); monitor->priv->timeout = source; monitor->priv->timeout_fires_at = data.time_now + data.min_time; @@ -622,7 +634,7 @@ update_rate_limiter_timeout (GFileMonitor *monitor, { source = g_timeout_source_new (data.min_time + 1); /* + 1 to make sure we've really passed the time */ g_source_set_callback (source, rate_limiter_timeout, monitor, NULL); - g_source_attach (source, NULL); + g_source_attach (source, monitor->priv->context); monitor->priv->timeout = source; monitor->priv->timeout_fires_at = data.time_now + data.min_time; @@ -640,7 +652,9 @@ update_rate_limiter_timeout (GFileMonitor *monitor, * has taken place. Should be called from file monitor * implementations only. * - * The signal will be emitted from an idle handler. + * The signal will be emitted from an idle handler (in the thread-default main + * context). **/ void g_file_monitor_emit_event (GFileMonitor *monitor, diff --git a/gio/gio.symbols b/gio/gio.symbols index 2323c93..6a11ad9 100644 --- a/gio/gio.symbols +++ b/gio/gio.symbols @@ -343,6 +343,7 @@ g_file_start_mountable g_file_start_mountable_finish g_file_stop_mountable g_file_stop_mountable_finish +g_file_supports_thread_contexts #endif #endif diff --git a/gio/gioscheduler.c b/gio/gioscheduler.c index 8d97a56..1787266 100644 --- a/gio/gioscheduler.c +++ b/gio/gioscheduler.c @@ -55,6 +55,7 @@ struct _GIOSchedulerJob { gint io_priority; GCancellable *cancellable; + GMainContext *context; guint idle_tag; }; @@ -72,6 +73,8 @@ g_io_job_free (GIOSchedulerJob *job) { if (job->cancellable) g_object_unref (job->cancellable); + if (job->context) + g_main_context_unref (job->context); g_free (job); } @@ -242,6 +245,10 @@ g_io_scheduler_push_job (GIOSchedulerJobFunc job_func, if (cancellable) job->cancellable = g_object_ref (cancellable); + job->context = g_main_context_get_thread_default (); + if (job->context) + g_main_context_ref (job->context); + G_LOCK (active_jobs); active_jobs = g_slist_prepend (active_jobs, job); job->active_link = active_jobs; @@ -341,12 +348,12 @@ mainloop_proxy_free (MainLoopProxy *proxy) /** * g_io_scheduler_job_send_to_mainloop: * @job: a #GIOSchedulerJob - * @func: a #GSourceFunc callback that will be called in the main thread + * @func: a #GSourceFunc callback that will be called in the original thread * @user_data: data to pass to @func * @notify: a #GDestroyNotify for @user_data, or %NULL * - * Used from an I/O job to send a callback to be run in the - * main loop (main thread), waiting for the result (and thus + * Used from an I/O job to send a callback to be run in the thread + * that the job was started from, waiting for the result (and thus * blocking the I/O job). * * Returns: The return value of @func @@ -359,7 +366,6 @@ g_io_scheduler_job_send_to_mainloop (GIOSchedulerJob *job, { GSource *source; MainLoopProxy *proxy; - guint id; gboolean ret_val; g_return_val_if_fail (job != NULL, FALSE); @@ -389,7 +395,7 @@ g_io_scheduler_job_send_to_mainloop (GIOSchedulerJob *job, g_source_set_callback (source, mainloop_proxy_func, proxy, NULL); - id = g_source_attach (source, NULL); + g_source_attach (source, job->context); g_source_unref (source); g_cond_wait (proxy->ack_condition, proxy->ack_lock); @@ -404,14 +410,14 @@ g_io_scheduler_job_send_to_mainloop (GIOSchedulerJob *job, /** * g_io_scheduler_job_send_to_mainloop_async: * @job: a #GIOSchedulerJob - * @func: a #GSourceFunc callback that will be called in the main thread + * @func: a #GSourceFunc callback that will be called in the original thread * @user_data: data to pass to @func * @notify: a #GDestroyNotify for @user_data, or %NULL * - * Used from an I/O job to send a callback to be run asynchronously - * in the main loop (main thread). The callback will be run when the - * main loop is available, but at that time the I/O job might have - * finished. The return value from the callback is ignored. + * Used from an I/O job to send a callback to be run asynchronously in + * the thread that the job was started from. The callback will be run + * when the main loop is available, but at that time the I/O job might + * have finished. The return value from the callback is ignored. * * Note that if you are passing the @user_data from g_io_scheduler_push_job() * on to this function you have to ensure that it is not freed before @@ -426,7 +432,6 @@ g_io_scheduler_job_send_to_mainloop_async (GIOSchedulerJob *job, { GSource *source; MainLoopProxy *proxy; - guint id; g_return_if_fail (job != NULL); g_return_if_fail (func != NULL); @@ -452,7 +457,7 @@ g_io_scheduler_job_send_to_mainloop_async (GIOSchedulerJob *job, g_source_set_callback (source, mainloop_proxy_func, proxy, (GDestroyNotify)mainloop_proxy_free); - id = g_source_attach (source, NULL); + g_source_attach (source, job->context); g_source_unref (source); } diff --git a/gio/glocalfile.c b/gio/glocalfile.c index 1f956a5..8ed1e09 100644 --- a/gio/glocalfile.c +++ b/gio/glocalfile.c @@ -2392,4 +2392,6 @@ g_local_file_file_iface_init (GFileIface *iface) iface->move = g_local_file_move; iface->monitor_dir = g_local_file_monitor_dir; iface->monitor_file = g_local_file_monitor_file; + + iface->supports_thread_contexts = TRUE; } diff --git a/gio/gsimpleasyncresult.c b/gio/gsimpleasyncresult.c index e071d96..fe0a6af 100644 --- a/gio/gsimpleasyncresult.c +++ b/gio/gsimpleasyncresult.c @@ -89,11 +89,13 @@ * * GSimpleAsyncResult can integrate into GLib's event loop, #GMainLoop, * or it can use #GThreads if available. - * g_simple_async_result_complete() will finish an I/O task directly within - * the main event loop. g_simple_async_result_complete_in_idle() will - * integrate the I/O task into the main event loop as an idle function and - * g_simple_async_result_run_in_thread() will run the job in a separate - * thread. + * g_simple_async_result_complete() will finish an I/O task directly + * from the point where it is called. g_simple_async_result_complete_in_idle() + * will finish it from an idle handler in the thread-default main + * context. g_simple_async_result_run_in_thread() will run the + * job in a separate thread and then deliver the result to the + * thread-default main context. * * To set the results of an asynchronous function, * g_simple_async_result_set_op_res_gpointer(), @@ -119,6 +121,7 @@ struct _GSimpleAsyncResult GObject *source_object; GAsyncReadyCallback callback; gpointer user_data; + GMainContext *context; GError *error; gboolean failed; gboolean handle_cancellation; @@ -163,6 +166,9 @@ g_simple_async_result_finalize (GObject *object) if (simple->source_object) g_object_unref (simple->source_object); + if (simple->context) + g_main_context_unref (simple->context); + clear_op_res (simple); if (simple->error) @@ -183,6 +189,10 @@ static void g_simple_async_result_init (GSimpleAsyncResult *simple) { simple->handle_cancellation = TRUE; + + simple->context = g_main_context_get_thread_default (); + if (simple->context) + g_main_context_ref (simple->context); } /** @@ -547,16 +557,35 @@ g_simple_async_result_set_error (GSimpleAsyncResult *simple, * g_simple_async_result_complete: * @simple: a #GSimpleAsyncResult. * - * Completes an asynchronous I/O job. - * Must be called in the main thread, as it invokes the callback that - * should be called in the main thread. If you are in a different thread - * use g_simple_async_result_complete_in_idle(). + * Completes an asynchronous I/O job immediately. Must be called in + * the thread where the asynchronous result was to be delivered, as it + * invokes the callback directly. If you are in a different thread use + * g_simple_async_result_complete_in_idle(). **/ void g_simple_async_result_complete (GSimpleAsyncResult *simple) { +#ifndef G_DISABLE_CHECKS + GSource *current_source; + GMainContext *current_context; +#endif + g_return_if_fail (G_IS_SIMPLE_ASYNC_RESULT (simple)); +#ifndef G_DISABLE_CHECKS + current_source = g_main_current_source (); + if (current_source) + { + current_context = g_source_get_context (current_source); + if (current_context == g_main_context_default ()) + current_context = NULL; + if (simple->context != current_context) + g_warning ("g_simple_async_result_complete() called from wrong context!"); + } + else + g_warning ("g_simple_async_result_complete() called from outside main loop!"); +#endif + if (simple->callback) simple->callback (simple->source_object, G_ASYNC_RESULT (simple), @@ -577,14 +606,14 @@ complete_in_idle_cb (gpointer data) * g_simple_async_result_complete_in_idle: * @simple: a #GSimpleAsyncResult. * - * Completes an asynchronous function in the main event loop using - * an idle function. + * Completes an asynchronous function in an idle handler in the thread-default main + * loop of the thread that @simple was initially created in. **/ void g_simple_async_result_complete_in_idle (GSimpleAsyncResult *simple) { GSource *source; - guint id; g_return_if_fail (G_IS_SIMPLE_ASYNC_RESULT (simple)); @@ -594,7 +623,7 @@ g_simple_async_result_complete_in_idle (GSimpleAsyncResult *simple) g_source_set_priority (source, G_PRIORITY_DEFAULT); g_source_set_callback (source, complete_in_idle_cb, simple, g_object_unref); - id = g_source_attach (source, NULL); + g_source_attach (source, simple->context); g_source_unref (source); } @@ -638,7 +667,6 @@ run_in_thread (GIOSchedulerJob *job, RunInThreadData *data = _data; GSimpleAsyncResult *simple = data->simple; GSource *source; - guint id; if (simple->handle_cancellation && g_cancellable_is_cancelled (c)) @@ -655,7 +683,7 @@ run_in_thread (GIOSchedulerJob *job, g_source_set_priority (source, G_PRIORITY_DEFAULT); g_source_set_callback (source, complete_in_idle_cb_for_thread, data, NULL); - id = g_source_attach (source, NULL); + g_source_attach (source, simple->context); g_source_unref (source); return FALSE; @@ -668,7 +696,9 @@ run_in_thread (GIOSchedulerJob *job, * @io_priority: the io priority of the request. * @cancellable: optional #GCancellable object, %NULL to ignore. * - * Runs the asynchronous job in a separated thread. + * Runs the asynchronous job in a separate thread and then calls + * g_simple_async_result_complete_in_idle() on @simple to return + * the result to the appropriate main loop. **/ void g_simple_async_result_run_in_thread (GSimpleAsyncResult *simple, diff --git a/gio/gsocketclient.c b/gio/gsocketclient.c index cfb398f..2a2b862 100644 --- a/gio/gsocketclient.c +++ b/gio/gsocketclient.c @@ -796,7 +796,7 @@ g_socket_client_enumerator_callback (GObject *object, g_source_set_callback (source, (GSourceFunc) g_socket_client_socket_callback, data, NULL); - g_source_attach (source, NULL); + g_source_attach (source, g_main_context_get_thread_default ()); g_source_unref (source); g_object_unref (address); diff --git a/gio/gsocketinputstream.c b/gio/gsocketinputstream.c index d1fdbc3..046970f 100644 --- a/gio/gsocketinputstream.c +++ b/gio/gsocketinputstream.c @@ -188,7 +188,7 @@ g_socket_input_stream_read_async (GInputStream *stream, g_source_set_callback (source, (GSourceFunc) g_socket_input_stream_read_ready, g_object_ref (input_stream), g_object_unref); - g_source_attach (source, NULL); + g_source_attach (source, g_main_context_get_thread_default ()); g_source_unref (source); } else diff --git a/gio/gsocketlistener.c b/gio/gsocketlistener.c index f668d14..4050365 100644 --- a/gio/gsocketlistener.c +++ b/gio/gsocketlistener.c @@ -765,7 +765,7 @@ g_socket_listener_accept_socket_async (GSocketListener *listener, accept_ready, data, cancellable, - NULL); + g_main_context_get_thread_default ()); } /** diff --git a/gio/gsocketoutputstream.c b/gio/gsocketoutputstream.c index 121fb95..7aa9680 100644 --- a/gio/gsocketoutputstream.c +++ b/gio/gsocketoutputstream.c @@ -190,7 +190,7 @@ g_socket_output_stream_write_async (GOutputStream *stream, g_source_set_callback (source, (GSourceFunc) g_socket_output_stream_write_ready, g_object_ref (output_stream), g_object_unref); - g_source_attach (source, NULL); + g_source_attach (source, g_main_context_get_thread_default ()); g_source_unref (source); } else diff --git a/gio/gtcpconnection.c b/gio/gtcpconnection.c index 611c195..de1aa14 100644 --- a/gio/gtcpconnection.c +++ b/gio/gtcpconnection.c @@ -321,7 +321,7 @@ g_tcp_connection_close_async (GIOStream *stream, g_source_set_callback (source, (GSourceFunc) close_read_ready, data, (GDestroyNotify)close_async_data_free); - g_source_attach (source, NULL); + g_source_attach (source, g_main_context_get_thread_default ()); g_source_unref (source); return; diff --git a/gio/gunixinputstream.c b/gio/gunixinputstream.c index 9a8e8e3..2813aad 100644 --- a/gio/gunixinputstream.c +++ b/gio/gunixinputstream.c @@ -507,7 +507,7 @@ g_unix_input_stream_read_async (GInputStream *stream, cancellable); g_source_set_callback (source, (GSourceFunc)read_async_cb, data, g_free); - g_source_attach (source, NULL); + g_source_attach (source, g_main_context_get_thread_default ()); g_source_unref (source); } @@ -635,7 +635,7 @@ g_unix_input_stream_close_async (GInputStream *stream, idle = g_idle_source_new (); g_source_set_callback (idle, (GSourceFunc)close_async_cb, data, close_async_data_free); - g_source_attach (idle, NULL); + g_source_attach (idle, g_main_context_get_thread_default ()); g_source_unref (idle); } diff --git a/gio/gunixmount.c b/gio/gunixmount.c index 8856201..c475ff9 100644 --- a/gio/gunixmount.c +++ b/gio/gunixmount.c @@ -361,11 +361,11 @@ eject_unmount_do_cb (gpointer user_data) data->error_channel_source = g_io_create_watch (data->error_channel, G_IO_IN); g_source_set_callback (data->error_channel_source, (GSourceFunc) eject_unmount_read_error, data, NULL); - g_source_attach (data->error_channel_source, NULL); + g_source_attach (data->error_channel_source, g_main_context_get_thread_default ()); child_watch = g_child_watch_source_new (child_pid); g_source_set_callback (child_watch, (GSourceFunc) eject_unmount_cb, data, NULL); - g_source_attach (child_watch, NULL); + g_source_attach (child_watch, g_main_context_get_thread_default ()); g_source_unref (child_watch); handle_error: diff --git a/gio/gunixoutputstream.c b/gio/gunixoutputstream.c index 6c11e85..147561b 100644 --- a/gio/gunixoutputstream.c +++ b/gio/gunixoutputstream.c @@ -494,7 +494,7 @@ g_unix_output_stream_write_async (GOutputStream *stream, cancellable); g_source_set_callback (source, (GSourceFunc)write_async_cb, data, g_free); - g_source_attach (source, NULL); + g_source_attach (source, g_main_context_get_thread_default ()); g_source_unref (source); } @@ -591,7 +591,7 @@ g_unix_output_stream_close_async (GOutputStream *stream, idle = g_idle_source_new (); g_source_set_callback (idle, (GSourceFunc)close_async_cb, data, g_free); - g_source_attach (idle, NULL); + g_source_attach (idle, g_main_context_get_thread_default ()); g_source_unref (idle); } diff --git a/gio/gunixresolver.c b/gio/gunixresolver.c index a1f7425..d25274f 100644 --- a/gio/gunixresolver.c +++ b/gio/gunixresolver.c @@ -84,21 +84,16 @@ g_unix_resolver_finalize (GObject *object) * a. The resolution completes: g_unix_resolver_watch() sees that * the request has completed, and calls * g_unix_resolver_request_complete(), which detaches the - * "cancelled" signal handler (if it was present) and then - * immediately completes the async_result (since - * g_unix_resolver_watch() is already run from main-loop - * time.) After completing the async_result, it unrefs it, - * causing the req to be freed as well. + * "cancelled" signal handler (if it was present), queues the + * async_result to be completed, and then unrefs it. * * b. The resolution is cancelled: request_cancelled() calls * _g_asyncns_cancel() to cancel the resolution. Then it calls * g_unix_resolver_request_complete(), which detaches the * signal handler, and queues async_result to complete in an - * idle handler. It then unrefs the async_result to ensure - * that after its callback runs, it will be destroyed, in turn - * causing the req to be freed. Because the asyncns resolution - * was cancelled, g_unix_resolver_watch() will never be - * triggered for this req. + * idle handler. Because the asyncns resolution was cancelled, + * g_unix_resolver_watch() will never be triggered for this + * req. * * Since there's only a single thread, it's not possible for the * request to both complete and be cancelled "at the same time", @@ -108,18 +103,29 @@ g_unix_resolver_finalize (GObject *object) */ typedef struct _GUnixResolverRequest GUnixResolverRequest; -typedef void (*GUnixResolverFreeFunc) (GUnixResolverRequest *); +typedef void (*GUnixResolverFunc) (GUnixResolverRequest *); struct _GUnixResolverRequest { GUnixResolver *gur; _g_asyncns_query_t *qy; union { - gchar *hostname; - GInetAddress *address; - gchar *service; + struct { + gchar *hostname; + GList *addresses; + } name; + + struct { + GInetAddress *address; + gchar *hostname; + } address; + + struct { + gchar *service; + GList *targets; + } service; } u; - GUnixResolverFreeFunc free_func; + GUnixResolverFunc process_func, free_func; GCancellable *cancellable; GSimpleAsyncResult *async_result; @@ -133,7 +139,8 @@ static void request_cancelled (GCancellable *cancellable, static GUnixResolverRequest * g_unix_resolver_request_new (GUnixResolver *gur, _g_asyncns_query_t *qy, - GUnixResolverFreeFunc free_func, + GUnixResolverFunc process_func, + GUnixResolverFunc free_func, GCancellable *cancellable, GSimpleAsyncResult *async_result) { @@ -142,6 +149,7 @@ g_unix_resolver_request_new (GUnixResolver *gur, req = g_slice_new0 (GUnixResolverRequest); req->gur = g_object_ref (gur); req->qy = qy; + req->process_func = process_func; req->free_func = free_func; if (cancellable) @@ -161,9 +169,8 @@ g_unix_resolver_request_new (GUnixResolver *gur, static void g_unix_resolver_request_free (GUnixResolverRequest *req) { - /* If the user didn't call _finish the qy will still be around. */ - if (req->qy) - _g_asyncns_cancel (req->gur->asyncns, req->qy); + req->free_func (req); + g_object_unref (req->gur); /* We don't have to free req->cancellable and req->async_result, * since they must already have been freed if we're here. @@ -173,8 +180,7 @@ g_unix_resolver_request_free (GUnixResolverRequest *req) } static void -g_unix_resolver_request_complete (GUnixResolverRequest *req, - gboolean need_idle) +g_unix_resolver_request_complete (GUnixResolverRequest *req) { if (req->cancellable) { @@ -183,16 +189,11 @@ g_unix_resolver_request_complete (GUnixResolverRequest *req, req->cancellable = NULL; } - if (need_idle) - g_simple_async_result_complete_in_idle (req->async_result); - else - g_simple_async_result_complete (req->async_result); - - /* If we completed_in_idle, that will have taken an extra ref on - * req->async_result; if not, then we're already done. Either way we - * need to unref the async_result to make sure it eventually is - * destroyed, causing req to be freed. + /* We always complete_in_idle, even if we were called from + * g_unix_resolver_watch(), since we might have been started under a + * non-default g_main_context_get_thread_default(). */ + g_simple_async_result_complete_in_idle (req->async_result); g_object_unref (req->async_result); } @@ -210,7 +211,7 @@ request_cancelled (GCancellable *cancellable, g_simple_async_result_set_from_error (req->async_result, error); g_error_free (error); - g_unix_resolver_request_complete (req, TRUE); + g_unix_resolver_request_complete (req); } static gboolean @@ -234,7 +235,8 @@ g_unix_resolver_watch (GIOChannel *iochannel, (qy = _g_asyncns_getnext (gur->asyncns)) != NULL) { req = _g_asyncns_getuserdata (gur->asyncns, qy); - g_unix_resolver_request_complete (req, FALSE); + req->process_func (req); + g_unix_resolver_request_complete (req); } return TRUE; @@ -243,7 +245,8 @@ g_unix_resolver_watch (GIOChannel *iochannel, static GUnixResolverRequest * resolve_async (GUnixResolver *gur, _g_asyncns_query_t *qy, - GUnixResolverFreeFunc free_func, + GUnixResolverFunc process_func, + GUnixResolverFunc free_func, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data, @@ -253,7 +256,8 @@ resolve_async (GUnixResolver *gur, GUnixResolverRequest *req; result = g_simple_async_result_new (G_OBJECT (gur), callback, user_data, tag); - req = g_unix_resolver_request_new (gur, qy, free_func, cancellable, result); + req = g_unix_resolver_request_new (gur, qy, process_func, free_func, + cancellable, result); g_object_unref (result); _g_asyncns_setuserdata (gur->asyncns, qy, req); @@ -261,9 +265,32 @@ resolve_async (GUnixResolver *gur, } static void +lookup_by_name_process (GUnixResolverRequest *req) +{ + struct addrinfo *res; + gint retval; + GError *error = NULL; + + retval = _g_asyncns_getaddrinfo_done (req->gur->asyncns, req->qy, &res); + req->u.name.addresses = + _g_resolver_addresses_from_addrinfo (req->u.name.hostname, + res, retval, &error); + if (res) + freeaddrinfo (res); + + if (error) + { + g_simple_async_result_set_from_error (req->async_result, error); + g_error_free (error); + } +} + +static void lookup_by_name_free (GUnixResolverRequest *req) { - g_free (req->u.hostname); + g_free (req->u.name.hostname); + if (req->u.name.addresses) + g_resolver_free_addresses (req->u.name.addresses); } static void @@ -279,9 +306,9 @@ lookup_by_name_async (GResolver *resolver, qy = _g_asyncns_getaddrinfo (gur->asyncns, hostname, NULL, &_g_resolver_addrinfo_hints); - req = resolve_async (gur, qy, lookup_by_name_free, cancellable, - callback, user_data, lookup_by_name_async); - req->u.hostname = g_strdup (hostname); + req = resolve_async (gur, qy, lookup_by_name_process, lookup_by_name_free, + cancellable, callback, user_data, lookup_by_name_async); + req->u.name.hostname = g_strdup (hostname); } static GList * @@ -291,28 +318,48 @@ lookup_by_name_finish (GResolver *resolver, { GSimpleAsyncResult *simple; GUnixResolverRequest *req; - struct addrinfo *res; - gint retval; GList *addresses; g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (resolver), lookup_by_name_async), FALSE); simple = G_SIMPLE_ASYNC_RESULT (result); + if (g_simple_async_result_propagate_error (simple, error)) + return NULL; + req = g_simple_async_result_get_op_res_gpointer (simple); - retval = _g_asyncns_getaddrinfo_done (req->gur->asyncns, req->qy, &res); - req->qy = NULL; - addresses = _g_resolver_addresses_from_addrinfo (req->u.hostname, res, retval, error); - if (res) - freeaddrinfo (res); + addresses = req->u.name.addresses; + req->u.name.addresses = NULL; return addresses; } static void +lookup_by_address_process (GUnixResolverRequest *req) +{ + gchar host[NI_MAXHOST]; + gint retval; + GError *error = NULL; + + retval = _g_asyncns_getnameinfo_done (req->gur->asyncns, req->qy, + host, sizeof (host), NULL, 0); + req->u.address.hostname = + _g_resolver_name_from_nameinfo (req->u.address.address, + host, retval, &error); + + if (error) + { + g_simple_async_result_set_from_error (req->async_result, error); + g_error_free (error); + } +} + +static void lookup_by_address_free (GUnixResolverRequest *req) { - g_object_unref (req->u.address); + g_object_unref (req->u.address.address); + if (req->u.address.hostname) + g_free (req->u.address.hostname); } static void @@ -332,9 +379,10 @@ lookup_by_address_async (GResolver *resolver, qy = _g_asyncns_getnameinfo (gur->asyncns, (struct sockaddr *)&sockaddr, sockaddr_size, NI_NAMEREQD, TRUE, FALSE); - req = resolve_async (gur, qy, lookup_by_address_free, cancellable, + req = resolve_async (gur, qy, lookup_by_address_process, + lookup_by_address_free, cancellable, callback, user_data, lookup_by_address_async); - req->u.address = g_object_ref (address); + req->u.address.address = g_object_ref (address); } static gchar * @@ -344,26 +392,53 @@ lookup_by_address_finish (GResolver *resolver, { GSimpleAsyncResult *simple; GUnixResolverRequest *req; - gchar host[NI_MAXHOST], *name; - gint retval; + gchar *name; g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (resolver), lookup_by_address_async), FALSE); simple = G_SIMPLE_ASYNC_RESULT (result); + if (g_simple_async_result_propagate_error (simple, error)) + return NULL; + req = g_simple_async_result_get_op_res_gpointer (simple); - retval = _g_asyncns_getnameinfo_done (req->gur->asyncns, req->qy, - host, sizeof (host), NULL, 0); - req->qy = NULL; - name = _g_resolver_name_from_nameinfo (req->u.address, host, retval, error); + name = req->u.address.hostname; + req->u.address.hostname = NULL; return name; } static void +lookup_service_process (GUnixResolverRequest *req) +{ + guchar *answer; + gint len, herr; + GError *error = NULL; + + len = _g_asyncns_res_done (req->gur->asyncns, req->qy, &answer); + if (len < 0) + herr = h_errno; + else + herr = 0; + + req->u.service.targets = + _g_resolver_targets_from_res_query (req->u.service.service, + answer, len, herr, &error); + _g_asyncns_freeanswer (answer); + + if (error) + { + g_simple_async_result_set_from_error (req->async_result, error); + g_error_free (error); + } +} + +static void lookup_service_free (GUnixResolverRequest *req) { - g_free (req->u.service); + g_free (req->u.service.service); + if (req->u.service.targets) + g_resolver_free_targets (req->u.service.targets); } static void @@ -378,9 +453,9 @@ lookup_service_async (GResolver *resolver, _g_asyncns_query_t *qy; qy = _g_asyncns_res_query (gur->asyncns, rrname, C_IN, T_SRV); - req = resolve_async (gur, qy, lookup_service_free, cancellable, - callback, user_data, lookup_service_async); - req->u.service = g_strdup (rrname); + req = resolve_async (gur, qy, lookup_service_process, lookup_service_free, + cancellable, callback, user_data, lookup_service_async); + req->u.service.service = g_strdup (rrname); } static GList * @@ -390,23 +465,17 @@ lookup_service_finish (GResolver *resolver, { GSimpleAsyncResult *simple; GUnixResolverRequest *req; - guchar *answer; - gint len, herr; GList *targets; g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (resolver), lookup_service_async), FALSE); simple = G_SIMPLE_ASYNC_RESULT (result); - req = g_simple_async_result_get_op_res_gpointer (simple); - len = _g_asyncns_res_done (req->gur->asyncns, req->qy, &answer); - req->qy = NULL; - if (len < 0) - herr = h_errno; - else - herr = 0; + if (g_simple_async_result_propagate_error (simple, error)) + return NULL; - targets = _g_resolver_targets_from_res_query (req->u.service, answer, len, herr, error); - _g_asyncns_freeanswer (answer); + req = g_simple_async_result_get_op_res_gpointer (simple); + targets = req->u.service.targets; + req->u.service.targets = NULL; return targets; } diff --git a/gio/gunixvolume.c b/gio/gunixvolume.c index a1037ab..6f227fc 100644 --- a/gio/gunixvolume.c +++ b/gio/gunixvolume.c @@ -420,11 +420,11 @@ eject_mount_do (GVolume *volume, data->error_channel_source = g_io_create_watch (data->error_channel, G_IO_IN); g_source_set_callback (data->error_channel_source, (GSourceFunc) eject_mount_read_error, data, NULL); - g_source_attach (data->error_channel_source, NULL); + g_source_attach (data->error_channel_source, g_main_context_get_thread_default ()); child_watch = g_child_watch_source_new (child_pid); g_source_set_callback (child_watch, (GSourceFunc) eject_mount_cb, data, NULL); - g_source_attach (child_watch, NULL); + g_source_attach (child_watch, g_main_context_get_thread_default ()); g_source_unref (child_watch); handle_error: diff --git a/gio/gvolumemonitor.c b/gio/gvolumemonitor.c index 168a174..6c7a52d 100644 --- a/gio/gvolumemonitor.c +++ b/gio/gvolumemonitor.c @@ -41,7 +41,12 @@ * #GVolumeMonitor is for listing the user interesting devices and volumes * on the computer. In other words, what a file selector or file manager * would show in a sidebar. -**/ + * + * #GVolumeMonitor is not thread-default-context + * aware, and so should not be used other than from the main + * thread, with no thread-default-context active. + **/ G_DEFINE_TYPE (GVolumeMonitor, g_volume_monitor, G_TYPE_OBJECT); diff --git a/gio/gwin32resolver.c b/gio/gwin32resolver.c index b02b08c..5198b95 100644 --- a/gio/gwin32resolver.c +++ b/gio/gwin32resolver.c @@ -234,7 +234,7 @@ request_cancelled (GCancellable *cancellable, req->cancelled_idle = g_idle_source_new (); g_source_set_callback (req->cancelled_idle, (GSourceFunc)request_cancelled_idle, req, NULL); - g_source_attach (req->cancelled_idle, NULL); + g_source_attach (req->cancelled_idle, g_main_context_get_thread_default ()); } static DWORD WINAPI @@ -479,7 +479,7 @@ g_win32_handle_source_add (HANDLE handle, g_source_add_poll (source, &hsource->pollfd); g_source_set_callback (source, callback, user_data, NULL); - g_source_attach (source, NULL); + g_source_attach (source, g_main_context_get_thread_default ()); return source; } diff --git a/gio/tests/.gitignore b/gio/tests/.gitignore index 9295dda..b815e87 100644 --- a/gio/tests/.gitignore +++ b/gio/tests/.gitignore @@ -21,3 +21,4 @@ send-data socket-client socket-server srvtarget +contexts diff --git a/gio/tests/Makefile.am b/gio/tests/Makefile.am index e49c29e..8b5d30b 100644 --- a/gio/tests/Makefile.am +++ b/gio/tests/Makefile.am @@ -7,7 +7,8 @@ INCLUDES = \ -I$(top_srcdir)/gmodule \ -I$(top_srcdir)/gobject \ -I$(top_srcdir)/gio \ - $(GLIB_DEBUG_FLAGS) + $(GLIB_DEBUG_FLAGS) \ + -DSRCDIR=\""$(srcdir)"\" noinst_PROGRAMS = $(TEST_PROGS) $(SAMPLE_PROGS) progs_ldadd = \ @@ -29,7 +30,8 @@ TEST_PROGS += \ sleepy-stream \ filter-streams \ simple-async-result \ - srvtarget + srvtarget \ + contexts SAMPLE_PROGS = resolver socket-server socket-client echo-server httpd send-data @@ -110,4 +112,8 @@ send_data_LDADD = $(progs_ldadd) \ srvtarget_SOURCES = srvtarget.c srvtarget_LDADD = $(progs_ldadd) +contexts_SOURCES = contexts.c +contexts_LDADD = $(progs_ldadd) \ + $(top_builddir)/gthread/libgthread-2.0.la + DISTCLEAN_FILES = applications/mimeinfo.cache diff --git a/gio/tests/contexts.c b/gio/tests/contexts.c new file mode 100644 index 0000000..3fe16cb --- /dev/null +++ b/gio/tests/contexts.c @@ -0,0 +1,190 @@ +#include +#include +#include + +#define TEST_FILE (SRCDIR "/Makefile.am") +char *test_file_buffer; +gsize test_file_size; +static char async_read_buffer[8192]; + +static void +read_data (GObject *source, GAsyncResult *result, gpointer loop) +{ + GInputStream *in = G_INPUT_STREAM (source); + GError *error = NULL; + gssize nread; + + nread = g_input_stream_read_finish (in, result, &error); + g_assert_no_error (error); + + g_assert_cmpint (nread, >, 0); + g_assert_cmpint (nread, <=, MIN(sizeof (async_read_buffer), test_file_size)); + g_assert (memcmp (async_read_buffer, test_file_buffer, nread) == 0); + + g_main_loop_quit (loop); +} + +static void +opened_for_read (GObject *source, GAsyncResult *result, gpointer loop) +{ + GFile *file = G_FILE (source); + GFileInputStream *in; + GError *error = NULL; + + in = g_file_read_finish (file, result, &error); + g_assert_no_error (error); + + memset (async_read_buffer, 0, sizeof (async_read_buffer)); + g_input_stream_read_async (G_INPUT_STREAM (in), + async_read_buffer, sizeof (async_read_buffer), + G_PRIORITY_DEFAULT, NULL, + read_data, loop); +} + +/* Test 1: Async I/O started in a thread with a thread-default context + * will stick to that thread, and will complete even if the default + * main loop is blocked. (NB: the last part would not be true if we + * were testing GFileMonitor!) + */ + +static gboolean idle_start_test1_thread (gpointer loop); +static gpointer test1_thread (gpointer user_data); + +static GCond *test1_cond; +static GMutex *test1_mutex; + +static void +test_thread_independence (void) +{ + GMainLoop *loop; + + test1_cond = g_cond_new (); + test1_mutex = g_mutex_new (); + + loop = g_main_loop_new (NULL, FALSE); + g_idle_add (idle_start_test1_thread, loop); + g_main_loop_run (loop); + g_main_loop_unref (loop); + + g_mutex_free (test1_mutex); + g_cond_free (test1_cond); +} + +static gboolean +idle_start_test1_thread (gpointer loop) +{ + GTimeVal time; + GThread *thread; + gboolean io_completed; + + g_mutex_lock (test1_mutex); + thread = g_thread_create (test1_thread, NULL, TRUE, NULL); + + g_get_current_time (&time); + time.tv_sec += 2; + io_completed = g_cond_timed_wait (test1_cond, test1_mutex, &time); + g_assert (io_completed); + g_thread_join (thread); + + g_mutex_unlock (test1_mutex); + g_main_loop_quit (loop); + return FALSE; +} + +static gpointer +test1_thread (gpointer user_data) +{ + GMainContext *context; + GMainLoop *loop; + GFile *file; + + /* Wait for main thread to be waiting on test1_cond */ + g_mutex_lock (test1_mutex); + g_mutex_unlock (test1_mutex); + + context = g_main_context_new (); + g_assert (g_main_context_get_thread_default () == NULL); + g_main_context_push_thread_default (context); + g_assert (g_main_context_get_thread_default () == context); + + file = g_file_new_for_path (TEST_FILE); + g_assert (g_file_supports_thread_contexts (file)); + + loop = g_main_loop_new (context, FALSE); + g_file_read_async (file, G_PRIORITY_DEFAULT, NULL, + opened_for_read, loop); + g_main_loop_run (loop); + g_main_loop_unref (loop); + + g_cond_signal (test1_cond); + return NULL; +} + +/* Test 2: If we push a thread-default context in the main thread, we + * can run async ops in that context without running the default + * context. + */ + +static gboolean test2_fail (gpointer user_data); + +static void +test_context_independence (void) +{ + GMainContext *context; + GMainLoop *loop; + GFile *file; + guint default_timeout; + GSource *thread_default_timeout; + + context = g_main_context_new (); + g_assert (g_main_context_get_thread_default () == NULL); + g_main_context_push_thread_default (context); + g_assert (g_main_context_get_thread_default () == context); + + file = g_file_new_for_path (TEST_FILE); + g_assert (g_file_supports_thread_contexts (file)); + + /* Add a timeout to the main loop, to fail immediately if it gets run */ + default_timeout = g_timeout_add_full (G_PRIORITY_HIGH, 0, + test2_fail, NULL, NULL); + /* Add a timeout to the alternate loop, to fail if the I/O *doesn't* run */ + thread_default_timeout = g_timeout_source_new_seconds (2); + g_source_set_callback (thread_default_timeout, test2_fail, NULL, NULL); + g_source_attach (thread_default_timeout, context); + + loop = g_main_loop_new (context, FALSE); + g_file_read_async (file, G_PRIORITY_DEFAULT, NULL, + opened_for_read, loop); + g_main_loop_run (loop); + g_main_loop_unref (loop); + + g_source_remove (default_timeout); + g_source_destroy (thread_default_timeout); + g_source_unref (thread_default_timeout); +} + +static gboolean +test2_fail (gpointer user_data) +{ + g_assert_not_reached (); + return FALSE; +} + +int +main (int argc, char **argv) +{ + GError *error = NULL; + + g_thread_init (NULL); + g_type_init (); + g_test_init (&argc, &argv, NULL); + + g_file_get_contents (TEST_FILE, &test_file_buffer, + &test_file_size, &error); + g_assert_no_error (error); + + g_test_add_func ("/gio/contexts/thread-independence", test_thread_independence); + g_test_add_func ("/gio/contexts/context-independence", test_context_independence); + + return g_test_run(); +} -- 2.7.4