[glib/wip/ghandle: 9/16] GCancellable: add critical section support
- From: Ryan Lortie <desrt src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [glib/wip/ghandle: 9/16] GCancellable: add critical section support
- Date: Fri, 19 Dec 2014 17:26:04 +0000 (UTC)
commit 582e41a28d7e9d114e91037eb1368d42d75c2657
Author: Ryan Lortie <desrt desrt ca>
Date: Thu Dec 18 18:00:49 2014 -0500
GCancellable: add critical section support
This allows using the per-thread handle in order to avoid creating one
per-cancellable.
It also does no system calls in the "not cancelled" case, which is nice.
docs/reference/gio/gio-sections.txt | 2 +
gio/gcancellable.c | 255 +++++++++++++++++++++++++++++++++++
gio/gcancellable.h | 10 ++
3 files changed, 267 insertions(+), 0 deletions(-)
---
diff --git a/docs/reference/gio/gio-sections.txt b/docs/reference/gio/gio-sections.txt
index a14778b..fa1875d 100644
--- a/docs/reference/gio/gio-sections.txt
+++ b/docs/reference/gio/gio-sections.txt
@@ -1228,6 +1228,8 @@ g_cancellable_reset
g_cancellable_connect
g_cancellable_disconnect
g_cancellable_cancel
+g_cancellable_enter_critical_section_using_handle
+g_cancellable_leave_critical_section
<SUBSECTION Standard>
GCancellableClass
G_CANCELLABLE
diff --git a/gio/gcancellable.c b/gio/gcancellable.c
index 533ae99..9fa887b 100644
--- a/gio/gcancellable.c
+++ b/gio/gcancellable.c
@@ -50,6 +50,12 @@ struct _GCancellablePrivate
guint cancelled_running : 1;
guint cancelled_running_waiting : 1;
+ union {
+ GThread *one;
+ GThread **several;
+ } critical_threads;
+ guint n_critical_threads;
+
guint fd_refcount;
GWakeup *wakeup;
};
@@ -735,6 +741,17 @@ g_cancellable_cancel (GCancellable *cancellable)
priv->cancelled = TRUE;
priv->cancelled_running = TRUE;
+ /* wake threads in critical sections */
+ if G_UNLIKELY (cancellable->priv->n_critical_threads > 1)
+ {
+ guint i;
+
+ for (i = 0; i < cancellable->priv->n_critical_threads; i++)
+ g_thread_wakeup (cancellable->priv->critical_threads.several[i]);
+ }
+ else if (cancellable->priv->n_critical_threads)
+ g_thread_wakeup (cancellable->priv->critical_threads.one);
+
if (priv->wakeup)
GLIB_PRIVATE_CALL (g_wakeup_signal) (priv->wakeup);
@@ -993,3 +1010,241 @@ g_cancellable_source_new (GCancellable *cancellable)
return source;
}
+
+static void
+g_cancellable_add_critical_thread (GCancellable *cancellable,
+ GThread *thread)
+{
+ g_thread_ref (thread);
+
+ /* The vast majority case will be when there is only one thread, but
+ * we do support a single cancellable in use from multiple threads and
+ * they may all be using critical-section handling.
+ */
+ if G_LIKELY (cancellable->priv->n_critical_threads == 0)
+ {
+ cancellable->priv->critical_threads.one = thread;
+ cancellable->priv->n_critical_threads = 1;
+ }
+ else if (cancellable->priv->n_critical_threads == 1)
+ {
+ GThread *other = cancellable->priv->critical_threads.one;
+
+ cancellable->priv->critical_threads.several = g_new (GThread *, 2);
+ cancellable->priv->critical_threads.several[0] = other;
+ cancellable->priv->critical_threads.several[1] = thread;
+ cancellable->priv->n_critical_threads = 2;
+ }
+ else
+ {
+ GThread **threads;
+ guint i;
+
+ threads = g_new (GThread *, cancellable->priv->n_critical_threads + 1);
+
+ for (i = 0; i < cancellable->priv->n_critical_threads; i++)
+ {
+ g_assert (cancellable->priv->critical_threads.several[i] != thread);
+ threads[i] = cancellable->priv->critical_threads.several[i];
+ }
+
+ threads[i++] = thread;
+
+ g_free (cancellable->priv->critical_threads.several);
+ cancellable->priv->critical_threads.several = threads;
+ cancellable->priv->n_critical_threads++;
+ }
+}
+
+static void
+g_cancellable_remove_critical_thread (GCancellable *cancellable,
+ GThread *thread)
+{
+ if G_LIKELY (cancellable->priv->n_critical_threads == 1)
+ {
+ g_assert (cancellable->priv->critical_threads.one == thread);
+ cancellable->priv->n_critical_threads = 0;
+ }
+ else if (cancellable->priv->n_critical_threads == 2)
+ {
+ GThread *other;
+
+ if (cancellable->priv->critical_threads.several[0] != thread)
+ {
+ g_assert (cancellable->priv->critical_threads.several[0] == thread);
+ other = cancellable->priv->critical_threads.several[0];
+ }
+ else
+ other = cancellable->priv->critical_threads.several[1];
+
+ g_free (cancellable->priv->critical_threads.several);
+ cancellable->priv->critical_threads.one = other;
+ cancellable->priv->n_critical_threads = 1;
+ }
+ else
+ {
+ guint i;
+
+ for (i = 0; i < cancellable->priv->n_critical_threads; i++)
+ if (cancellable->priv->critical_threads.several[i] == thread)
+ break;
+
+ g_assert (i != cancellable->priv->n_critical_threads);
+ cancellable->priv->n_critical_threads--;
+
+ for (; i < cancellable->priv->n_critical_threads; i++)
+ /* move from the right */
+ cancellable->priv->critical_threads.several[i] = cancellable->priv->critical_threads.several[i + 1];
+ }
+
+ g_thread_unref (thread);
+}
+
+/**
+ * g_cancellable_enter_critical_section_using_handle:
+ * @cancellable: (nullable): a #GCancellable, or %NULL
+ * @thread: the current #GThread
+ * @error: a pointer to a %NULL #GError, or %NULL
+ *
+ * Attempts to enter a critical section that can be cancelled by
+ * @cancellable.
+ *
+ * See g_thread_enter_critical_section_using_handle() for a conceptual
+ * introduction.
+ *
+ * @thread absolutely must be equal to the current thread as returned by
+ * g_thread_self(). The behaviour is completely undefined otherwise.
+ *
+ * This function is essentially a convenience wrapper. First, it
+ * atomically checks for cancellation and returns %G_HANDLE_NULL with
+ * @error appropriately set if the cancellable is already cancelled.
+ * Then it sets up @cancellable so that a cancellation in another thread
+ * will result in g_thread_wakeup() being called on @thread. Finally,
+ * g_thread_enter_critical_section_using_handle() is called and the
+ * result is returned.
+ *
+ * The result is that the returned handle will poll as ready if
+ * @cancellable is triggered.
+ *
+ * You must call g_cancellable_leave_critical_section() when you are
+ * done.
+ *
+ * The example in the documentation for
+ * g_thread_enter_critical_section_using_handle() could be written
+ * using GCancellable as follows:
+ *
+ * |[
+ * static gpointer worker (gpointer user_data) {
+ * GCancellable *cancellable = user_data;
+ * GThread *self = g_thread_self ();
+ * gboolean should_exit = FALSE;
+ *
+ * while (TRUE)
+ * {
+ * ghandle handle;
+ *
+ * handle = g_cancellable_enter_critical_section_using_handle (cancellable, self, NULL);
+ * if (!g_handle_is_valid (handle))
+ * break;
+ *
+ * do_blocking_work (handle);
+ *
+ * g_cancellable_leave_critical_section (cancellable, self, NULL);
+ * }
+ *
+ * return NULL;
+ * }
+ *
+ * void start_cancellable_worker (GCancellable *cancellable) {
+ * g_atomic_set (&continue_work, 1);
+ * g_thread_unref (g_thread_new ("worker", worker, cancellable));
+ * }
+ * ]|
+ *
+ * Returns: a valid #ghandle if the critical section was entered or
+ * %G_HANDLE_NULL (and @error set) if not. Use g_handle_is_valid() to
+ * check.
+ *
+ * Since: 2.44
+ */
+ghandle
+g_cancellable_enter_critical_section_using_handle (GCancellable *cancellable,
+ GThread *thread,
+ GError **error)
+{
+ if (cancellable)
+ {
+ gboolean cancelled = FALSE;
+
+ g_mutex_lock (&cancellable_mutex);
+
+ cancelled = cancellable->priv->cancelled;
+
+ if (!cancelled)
+ g_cancellable_add_critical_thread (cancellable, thread);
+
+ g_mutex_unlock (&cancellable_mutex);
+
+ if (cancelled)
+ {
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_CANCELLED,
+ _("Operation was cancelled"));
+ return G_HANDLE_NULL;
+ }
+ }
+
+ return g_thread_enter_critical_section_using_handle (thread);
+}
+
+/**
+ * g_cancellable_leave_critical_section:
+ * @cancellable: (nullable): a #GCancellable, or %NULL
+ * @thread: the current #GThread
+ * @error: a pointer to a %NULL #GError, or %NULL
+ *
+ * Leaves the critical section entered by
+ * g_cancellable_enter_critical_section_using_handle().
+ *
+ * This will also check @cancellable again for having been cancelled
+ * (which may very well be the reason for the operating in the critical
+ * section having finished). This provides a convenient chance to
+ * recheck @cancellable, but you may safely ignore the result if you
+ * will be checking it again soon anyway.
+ *
+ * Returns: %TRUE if @cancellable was not cancelled. Returns %FALSE
+ * (with @error set appropriately) in case of cancellation.
+ *
+ * Since: 2.44
+ */
+gboolean
+g_cancellable_leave_critical_section (GCancellable *cancellable,
+ GThread *thread,
+ GError **error)
+{
+ g_thread_leave_critical_section (thread);
+
+ if (cancellable)
+ {
+ gboolean cancelled = FALSE;
+
+ g_mutex_lock (&cancellable_mutex);
+
+ g_cancellable_remove_critical_thread (cancellable, thread);
+ cancelled = cancellable->priv->cancelled;
+
+ g_mutex_unlock (&cancellable_mutex);
+
+ if (cancelled)
+ {
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_CANCELLED,
+ _("Operation was cancelled"));
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
diff --git a/gio/gcancellable.h b/gio/gcancellable.h
index 493e5ff..1724713 100644
--- a/gio/gcancellable.h
+++ b/gio/gcancellable.h
@@ -125,6 +125,16 @@ void g_cancellable_disconnect (GCancellable *cancellable,
GLIB_AVAILABLE_IN_ALL
void g_cancellable_cancel (GCancellable *cancellable);
+GLIB_AVAILABLE_IN_2_44
+ghandle g_cancellable_enter_critical_section_using_handle (GCancellable *cancellable,
+ GThread *self,
+ GError **error);
+
+GLIB_AVAILABLE_IN_2_44
+gboolean g_cancellable_leave_critical_section (GCancellable *cancellable,
+ GThread *self,
+ GError **error);
+
G_END_DECLS
#endif /* __G_CANCELLABLE_H__ */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]