[gnome-devel-docs] platform-demos: Add a page on writing GSource implementations
- From: Philip Withnall <pwithnall src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-devel-docs] platform-demos: Add a page on writing GSource implementations
- Date: Thu, 19 Feb 2015 16:30:22 +0000 (UTC)
commit 59a8c23c958aae2b334822e04f93775e67f83eec
Author: Philip Withnall <philip withnall collabora co uk>
Date: Thu Feb 12 12:01:19 2015 +0000
platform-demos: Add a page on writing GSource implementations
This includes some example code which is discussed in the page. The code
comes with its own test suite which it is #included into, and the test
suite is run on `make check`.
The rules for compiling the test suite are entirely manual at the moment
to avoid introducing a dependency on a compiler to the gnome-devel-docs
module. They cannot be changed to use more conventional automake support
for building C programs, because the mere presence of a _SOURCES
variable causes automake to require a C compiler for dependency
generation.
https://bugzilla.gnome.org/show_bug.cgi?id=744717
.gitignore | 2 +
configure.ac | 12 +
platform-demos/C/custom-gsource.c.page | 301 +++++++++++++++++++++
platform-demos/C/samples/example-custom-gsource.c | 166 ++++++++++++
platform-demos/C/samples/test-custom-gsource.c | 127 +++++++++
platform-demos/Makefile.am | 19 ++-
6 files changed, 626 insertions(+), 1 deletions(-)
---
diff --git a/.gitignore b/.gitignore
index 1531b63..d21538b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -33,3 +33,5 @@ Makefile.in
/platform-overview/??*/*.mo
/platform-overview/??*/*.page
/platform-overview/??*/*.xml
+
+/platform-demos/C/samples/test-custom-gsource
diff --git a/configure.ac b/configure.ac
index c382f20..791b53a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -6,6 +6,18 @@ AM_MAINTAINER_MODE([enable])
YELP_HELP_INIT
+# C compiler and pkg-config for testing demo source code.
+# These dependencies are all optional: it must be possible for documenters to
+# run `./configure && make` without needing anything more than autotools and
+# Yelp. `make check` and `make distcheck` may require the compilers.
+AC_PATH_PROG([CC],[cc])
+PKG_PROG_PKG_CONFIG
+
+PKG_CHECK_MODULES([GLIB],[glib-2.0 gobject-2.0 gio-2.0],
+ [have_glib=yes],[have_glib=no])
+
+AM_CONDITIONAL([BUILD_TESTS],[test "$CC" != "" && test "$have_glib" = "yes"])
+
AC_OUTPUT([
Makefile
accessibility-devel-guide/Makefile
diff --git a/platform-demos/C/custom-gsource.c.page b/platform-demos/C/custom-gsource.c.page
new file mode 100644
index 0000000..2a4353b
--- /dev/null
+++ b/platform-demos/C/custom-gsource.c.page
@@ -0,0 +1,301 @@
+<page xmlns="http://projectmallard.org/1.0/"
+ xmlns:its="http://www.w3.org/2005/11/its"
+ xmlns:xi="http://www.w3.org/2003/XInclude"
+ type="guide" style="task"
+ id="custom-gsource.c">
+
+ <info>
+ <link type="guide" xref="c#examples"/>
+
+ <credit type="author copyright">
+ <name>Philip Withnall</name>
+ <email its:translate="no">philip withnall collabora co uk</email>
+ <years>2015</years>
+ </credit>
+
+ <include href="legal.xml" xmlns="http://www.w3.org/2001/XInclude"/>
+
+ <desc>
+ Tutorial for writing a custom <code>GSource</code> implementation
+ </desc>
+ </info>
+
+ <title>Custom GSources</title>
+
+ <synopsis>
+ <title>Summary</title>
+
+ <p>
+ This article is a tutorial on creating a custom <code>GSource</code>. For
+ the reference documentation, see the
+ <link href="https://developer.gnome.org/glib/stable/glib-The-Main-Event-Loop.html#GSource">GLib
+ API reference</link>.
+ </p>
+ </synopsis>
+
+ <section id="what-is-gsource">
+ <title>What is <code>GSource</code>?</title>
+
+ <p>
+ A <link
href="https://developer.gnome.org/glib/stable/glib-The-Main-Event-Loop.html#GSource"><code>GSource</code></link>
+ is an expected event with an associated callback function which will be
+ invoked when that event is received. An event could be a timeout or data
+ being received on a socket, for example.
+ </p>
+
+ <p>
+ GLib contains various types of <code>GSource</code>, but also allows
+ applications to define their own, allowing custom events to be integrated
+ into the main loop.
+ </p>
+
+ <p>
+ The structure of a <code>GSource</code> and its virtual functions are
+ documented in detail in the
+ <link href="https://developer.gnome.org/glib/stable/glib-The-Main-Event-Loop.html#GSourceFuncs">GLib
+ API reference</link>.
+ </p>
+ </section>
+
+ <section id="queue-source">
+ <title>A Message Queue Source</title>
+
+ <p>
+ As a running example, a message queue source will be used which dispatches
+ its callback whenever a message is enqueued to a queue internal to the
+ source (potentially from another thread).
+ </p>
+
+ <p>
+ This type of source is useful for efficiently transferring large numbers
+ of messages between main contexts. The alternative is transferring each
+ message as a separate idle <code>GSource</code> using
+ <code>g_source_attach()</code>. For large numbers of messages, this means
+ a lot of allocations and frees of <code>GSource</code>s.
+ </p>
+
+ <section id="gsource-structure">
+ <title>Structure</title>
+
+ <p>
+ Firstly, a structure for the source needs to be declared. This must
+ contain a <code>GSource</code> as its parent, followed by the private
+ fields for the source: the queue and a function to call to free each
+ message once finished with.
+ </p>
+ <code mime="text/x-csrc">
+typedef struct {
+ GSource parent;
+ GAsyncQueue *queue; /* owned */
+ GDestroyNotify destroy_message;
+} MessageQueueSource;</code>
+ </section>
+
+ <section id="prepare-function">
+ <title>Prepare Function</title>
+
+ <p>
+ Next, the prepare function for the source must be defined. This determines
+ whether the source is ready to be dispatched. As this source is using an
+ in-memory queue, this can be determined by checking the queue’s length: if
+ there are elements in the queue, the source can be dispatched to handle
+ them.
+ </p>
+ <code mime="text/x-csrc">
+return (g_async_queue_length (message_queue_source->queue) > 0);</code>
+ </section>
+
+ <section id="check-function">
+ <title>Check Function</title>
+
+ <p>
+ As this source has no file descriptors, the prepare and check functions
+ essentially have the same job, so a check function is not needed.
+ Setting the field to <code>NULL</code> in <code>GSourceFuncs</code>
+ bypasses the check function for this source type.
+ </p>
+ </section>
+
+ <section id="dispatch-function">
+ <title>Dispatch Function</title>
+
+ <p>
+ For this source, the dispatch function is where the complexity lies. It
+ needs to dequeue a message from the queue, then pass that message to the
+ <code>GSource</code>’s callback function. No messages may be queued: even
+ through the prepare function returned true, another source wrapping the
+ same queue may have been dispatched in the mean time and taken the final
+ message from the queue. Further, if no callback has been set for the
+ <code>GSource</code> (which is allowed), the message must be destroyed and
+ silently dropped.
+ </p>
+
+ <p>
+ If both a message and callback are set, the callback can be invoked on the
+ message and its return value propagated as the return value of the
+ dispatch function. This is <code>FALSE</code> to destroy the
+ <code>GSource</code> and <code>TRUE</code> to keep it alive, just as for
+ <code>GSourceFunc</code> — these semantics are the same for all dispatch
+ function implementations.
+ </p>
+ <code mime="text/x-csrc">
+/* Pop a message off the queue. */
+message = g_async_queue_try_pop (message_queue_source->queue);
+
+/* If there was no message, bail. */
+if (message == NULL)
+ {
+ /* Keep the source around to handle the next message. */
+ return TRUE;
+ }
+
+/* @func may be %NULL if no callback was specified.
+ * If so, drop the message. */
+if (func == NULL)
+ {
+ if (message_queue_source->destroy_message != NULL)
+ {
+ message_queue_source->destroy_message (message);
+ }
+
+ /* Keep the source around to consume the next message. */
+ return TRUE;
+ }
+
+return func (message, user_data);</code>
+ </section>
+
+ <section id="callback">
+ <title>Callback Functions</title>
+
+ <p>
+ The callback from a <code>GSource</code> does not have to have type
+ <code>GSourceFunc</code>. It can be whatever function type is called in
+ the source’s dispatch function, as long as that type is sufficiently
+ documented.
+ </p>
+
+ <p>
+ Normally, <code>g_source_set_callback()</code> is used to set the
+ callback function for a source instance. With its
+ <code>GDestroyNotify</code>, a strong reference can be held to keep an
+ object alive while the source is still alive:
+ </p>
+ <code mime="text/x-csrc">
+g_source_set_callback (source, callback_func,
+ g_object_ref (object_to_strong_ref),
+ (GDestroyNotify) g_object_unref);</code>
+
+ <p>
+ However, <code>GSource</code> has a layer of indirection for retrieving
+ this callback, exposed as <code>g_source_set_callback_indirect()</code>.
+ This allows GObject to set a <code>GClosure</code> as the callback for a
+ source, which allows for sources which are automatically destroyed when
+ an object is finalized — a <em>weak</em> reference, in contrast to the
+ <em>strong</em> reference above:
+ </p>
+ <code mime="text/x-csrc">
+g_source_set_closure (source,
+ g_cclosure_new_object (callback_func,
+ object_to_weak_ref));</code>
+
+ <p>
+ It also allows for a generic, closure-based ‘dummy’ callback, which can
+ be used when a source needs to exist but no action needs to be performed
+ in its callback:
+ </p>
+ <code mime="text/x-csrc">
+g_source_set_dummy_callback (source);</code>
+ </section>
+
+ <section id="constructor">
+ <title>Constructor</title>
+
+ <p>
+ Finally, the <code>GSourceFuncs</code> definition of the
+ <code>GSource</code> can be written, alongside a construction function.
+ It is typical practice to expose new source types simply as
+ <code>GSource</code>s, not as the subtype structure; so the constructor
+ returns a <code>GSource*</code>.
+ </p>
+
+ <p>
+ The example constructor here also demonstrates use of a child source to
+ support cancellation conveniently. If the <code>GCancellable</code> is
+ cancelled, the applicationj’s callback will be dispatched and can check
+ for cancellation. (The application code will need to make a pointer to
+ the <code>GCancellable</code> available to its callback, as a field of the
+ callback’s user data set in <code>g_source_set_callback()</code>.)
+ </p>
+ <code mime="text/x-csrc">
+GSource *
+message_queue_source_new (GAsyncQueue *queue,
+ GDestroyNotify destroy_message,
+ GCancellable *cancellable)
+{
+ GSource *source; /* alias of @message_queue_source */
+ MessageQueueSource *message_queue_source; /* alias of @source */
+
+ g_return_val_if_fail (queue != NULL, NULL);
+ g_return_val_if_fail (cancellable == NULL ||
+ G_IS_CANCELLABLE (cancellable), NULL);
+
+ source = g_source_new (&message_queue_source_funcs,
+ sizeof (MessageQueueSource));
+ message_queue_source = (MessageQueueSource *) source;
+
+ /* The caller can overwrite this name with something more useful later. */
+ g_source_set_name (source, "MessageQueueSource");
+
+ message_queue_source->queue = g_async_queue_ref (queue);
+ message_queue_source->destroy_message = destroy_message;
+
+ /* Add a cancellable source. */
+ if (cancellable != NULL)
+ {
+ GSource *cancellable_source;
+
+ cancellable_source = g_cancellable_source_new (cancellable);
+ g_source_set_dummy_callback (cancellable_source);
+ g_source_add_child_source (source, cancellable_source);
+ g_source_unref (cancellable_source);
+ }
+
+ return source;
+}</code>
+ </section>
+ </section>
+
+ <section id="full-listing">
+ <title>Complete Example</title>
+
+ <listing>
+ <title>Complete Example Code</title>
+
+ <code mime="text/x-csrc"><include xmlns="http://www.w3.org/2001/XInclude"
+ href="samples/example-custom-gsource.c"
+ parse="text"/></code>
+ </listing>
+ </section>
+
+ <section id="further-examples">
+ <title>Further Examples</title>
+
+ <p>
+ Sources can be more complex than the example given above. In
+ <link href="http://nice.freedesktop.org/">libnice</link>, a custom
+ <code>GSource</code> is needed to poll a set of sockets which changes
+ dynamically. The implementation is given as <code>ComponentSource</code>
+ in <link
href="http://cgit.freedesktop.org/libnice/libnice/tree/agent/component.c#n941">component.c</link>
+ and demonstrates a more complex use of the prepare function.
+ </p>
+
+ <p>
+ Another example is a custom source to interface GnuTLS with GLib in its
+ <code>GTlsConnection</code> implementation.
+ <link
href="https://git.gnome.org/browse/glib-networking/tree/tls/gnutls/gtlsconnection-gnutls.c#n871"><code>GTlsConnectionGnutlsSource</code></link>
+ synchronizes the main thread and a TLS worker thread which performs the
+ blocking TLS operations.
+ </p>
+ </section>
+</page>
diff --git a/platform-demos/C/samples/example-custom-gsource.c
b/platform-demos/C/samples/example-custom-gsource.c
new file mode 100644
index 0000000..fbeedc6
--- /dev/null
+++ b/platform-demos/C/samples/example-custom-gsource.c
@@ -0,0 +1,166 @@
+/**
+ * MessageQueueSource:
+ *
+ * This is a #GSource which wraps a #GAsyncQueue and is dispatched whenever a
+ * message can be pulled off the queue. Messages can be enqueued from any
+ * thread.
+ *
+ * The callbacks dispatched by a #MessageQueueSource have type
+ * #MessageQueueSourceFunc.
+ *
+ * #MessageQueueSource supports adding a #GCancellable child source which will
+ * additionally dispatch if a provided #GCancellable is cancelled.
+ */
+typedef struct {
+ GSource parent;
+ GAsyncQueue *queue; /* owned */
+ GDestroyNotify destroy_message;
+} MessageQueueSource;
+
+/**
+ * MessageQueueSourceFunc:
+ * @message: (transfer full) (nullable): message pulled off the queue
+ * @user_data: user data provided to g_source_set_callback()
+ *
+ * Callback function type for #MessageQueueSource.
+ */
+typedef gboolean (*MessageQueueSourceFunc) (gpointer message,
+ gpointer user_data);
+
+static gboolean
+message_queue_source_prepare (GSource *source,
+ gint *timeout_)
+{
+ MessageQueueSource *message_queue_source = (MessageQueueSource *) source;
+
+ return (g_async_queue_length (message_queue_source->queue) > 0);
+}
+
+static gboolean
+message_queue_source_dispatch (GSource *source,
+ GSourceFunc callback,
+ gpointer user_data)
+{
+ MessageQueueSource *message_queue_source = (MessageQueueSource *) source;
+ gpointer message;
+ MessageQueueSourceFunc func = (MessageQueueSourceFunc) callback;
+
+ /* Pop a message off the queue. */
+ message = g_async_queue_try_pop (message_queue_source->queue);
+
+ /* If there was no message, bail. */
+ if (message == NULL)
+ {
+ /* Keep the source around to handle the next message. */
+ return TRUE;
+ }
+
+ /* @func may be %NULL if no callback was specified.
+ * If so, drop the message. */
+ if (func == NULL)
+ {
+ if (message_queue_source->destroy_message != NULL)
+ {
+ message_queue_source->destroy_message (message);
+ }
+
+ /* Keep the source around to consume the next message. */
+ return TRUE;
+ }
+
+ return func (message, user_data);
+}
+
+static void
+message_queue_source_finalize (GSource *source)
+{
+ MessageQueueSource *message_queue_source = (MessageQueueSource *) source;
+
+ g_async_queue_unref (message_queue_source->queue);
+}
+
+static gboolean
+message_queue_source_closure_callback (gpointer message,
+ gpointer user_data)
+{
+ GClosure *closure = user_data;
+ GValue param_value = G_VALUE_INIT;
+ GValue result_value = G_VALUE_INIT;
+ gboolean retval;
+
+ /* The invoked function is responsible for freeing @message. */
+ g_value_init (&result_value, G_TYPE_BOOLEAN);
+ g_value_init (¶m_value, G_TYPE_POINTER);
+ g_value_set_pointer (¶m_value, message);
+
+ g_closure_invoke (closure, &result_value, 1, ¶m_value, NULL);
+ retval = g_value_get_boolean (&result_value);
+
+ g_value_unset (¶m_value);
+ g_value_unset (&result_value);
+
+ return retval;
+}
+
+static GSourceFuncs message_queue_source_funcs =
+ {
+ message_queue_source_prepare,
+ NULL, /* check */
+ message_queue_source_dispatch,
+ message_queue_source_finalize,
+ (GSourceFunc) message_queue_source_closure_callback,
+ NULL,
+ };
+
+/**
+ * message_queue_source_new:
+ * @queue: the queue to check
+ * @destroy_message: (nullable): function to free a message, or %NULL
+ * @cancellable: (nullable): a #GCancellable, or %NULL
+ *
+ * Create a new #MessageQueueSource, a type of #GSource which dispatches for
+ * each message queued to it.
+ *
+ * If a callback function of type #MessageQueueSourceFunc is connected to the
+ * returned #GSource using g_source_set_callback(), it will be invoked for each
+ * message, with the message passed as its first argument. It is responsible for
+ * freeing the message. If no callback is set, messages are automatically freed
+ * as they are queued.
+ *
+ * Returns: (transfer full): a new #MessageQueueSource
+ */
+GSource *
+message_queue_source_new (GAsyncQueue *queue,
+ GDestroyNotify destroy_message,
+ GCancellable *cancellable)
+{
+ GSource *source; /* alias of @message_queue_source */
+ MessageQueueSource *message_queue_source; /* alias of @source */
+
+ g_return_val_if_fail (queue != NULL, NULL);
+ g_return_val_if_fail (cancellable == NULL ||
+ G_IS_CANCELLABLE (cancellable), NULL);
+
+ source = g_source_new (&message_queue_source_funcs,
+ sizeof (MessageQueueSource));
+ message_queue_source = (MessageQueueSource *) source;
+
+ /* The caller can overwrite this name with something more useful later. */
+ g_source_set_name (source, "MessageQueueSource");
+
+ message_queue_source->queue = g_async_queue_ref (queue);
+ message_queue_source->destroy_message = destroy_message;
+
+ /* Add a cancellable source. */
+ if (cancellable != NULL)
+ {
+ GSource *cancellable_source;
+
+ cancellable_source = g_cancellable_source_new (cancellable);
+ g_source_set_dummy_callback (cancellable_source);
+ g_source_add_child_source (source, cancellable_source);
+ g_source_unref (cancellable_source);
+ }
+
+ return source;
+}
diff --git a/platform-demos/C/samples/test-custom-gsource.c b/platform-demos/C/samples/test-custom-gsource.c
new file mode 100644
index 0000000..b06c243
--- /dev/null
+++ b/platform-demos/C/samples/test-custom-gsource.c
@@ -0,0 +1,127 @@
+#include <glib.h>
+#include <glib-object.h>
+#include <gio/gio.h>
+
+/* The bulk of the code. */
+GSource *
+message_queue_source_new (GAsyncQueue *queue,
+ GDestroyNotify destroy_message,
+ GCancellable *cancellable);
+
+#include "example-custom-gsource.c"
+
+/* Utility functions. */
+static gboolean
+source_cb (gpointer message,
+ gpointer user_data)
+{
+ guint *seen_i = user_data;
+
+ g_assert_cmpuint (GPOINTER_TO_UINT (message), ==, *seen_i);
+ *seen_i = *seen_i + 1;
+
+ return TRUE;
+}
+
+static guint global_destroy_calls = 0;
+
+static void
+destroy_cb (gpointer user_data)
+{
+ global_destroy_calls++;
+}
+
+/* Tests. */
+static void
+test_construction (void)
+{
+ GSource *source = NULL;
+ GAsyncQueue *queue = NULL;
+
+ queue = g_async_queue_new ();
+ source = message_queue_source_new (queue, NULL, NULL);
+ g_source_unref (source);
+ g_async_queue_unref (queue);
+}
+
+static void
+test_messages_with_callback (void)
+{
+ GSource *source;
+ GAsyncQueue *queue;
+ guint i, seen_i;
+ GMainContext *main_context;
+
+ queue = g_async_queue_new_full (destroy_cb);
+ source = message_queue_source_new (queue, destroy_cb, NULL);
+
+ /* Enqueue some messages. */
+ for (i = 1; i <= 100; i++)
+ {
+ g_async_queue_push (queue, GUINT_TO_POINTER (i));
+ }
+
+ /* Create and run a main loop. Expect to see the messages in order. */
+ seen_i = 1;
+ global_destroy_calls = 0;
+
+ main_context = g_main_context_new ();
+ g_source_set_callback (source, (GSourceFunc) source_cb, &seen_i, NULL);
+ g_source_attach (source, main_context);
+
+ while (g_main_context_iteration (main_context, FALSE));
+
+ g_assert_cmpuint (global_destroy_calls, ==, 0);
+ g_assert_cmpuint (seen_i, ==, 101);
+ g_assert_cmpuint (g_async_queue_length (queue), ==, 0);
+
+ g_main_context_unref (main_context);
+ g_async_queue_unref (queue);
+ g_source_unref (source);
+}
+
+static void
+test_messages_without_callback (void)
+{
+ GSource *source;
+ GAsyncQueue *queue;
+ guint i;
+ GMainContext *main_context;
+
+ queue = g_async_queue_new_full (destroy_cb);
+ source = message_queue_source_new (queue, destroy_cb, NULL);
+
+ /* Enqueue some messages. */
+ for (i = 1; i <= 100; i++)
+ {
+ g_async_queue_push (queue, GUINT_TO_POINTER (i));
+ }
+
+ /* Create and run a main loop. */
+ main_context = g_main_context_new ();
+ g_source_attach (source, main_context);
+
+ while (g_main_context_iteration (main_context, FALSE));
+
+ g_assert_cmpuint (global_destroy_calls, ==, 100);
+ g_assert_cmpuint (g_async_queue_length (queue), ==, 0);
+
+ g_main_context_unref (main_context);
+ g_async_queue_unref (queue);
+ g_source_unref (source);
+}
+
+int
+main (int argc,
+ char **argv)
+{
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add_func ("/custom-gsource/construction", test_construction);
+ g_test_add_func ("/custom-gsource/messages-with-callback",
+ test_messages_with_callback);
+ g_test_add_func ("/custom-gsource/messages-without-callback",
+ test_messages_without_callback);
+
+ return g_test_run ();
+}
diff --git a/platform-demos/Makefile.am b/platform-demos/Makefile.am
index f1efbf7..fb5c9cc 100644
--- a/platform-demos/Makefile.am
+++ b/platform-demos/Makefile.am
@@ -478,6 +478,23 @@ HELP_FILES = \
window.c.page \
window.js.page \
window.py.page \
- window.vala.page
+ window.vala.page \
+ custom-gsource.c.page
HELP_LINGUAS = ca de el es fr gl pt_BR
+
+# Tooling to compile and run unit tests for example code.
+demo_sources += samples/example-custom-gsource.c
+EXTRA_DIST = C/samples/test-custom-gsource.c
+WARN_CFLAGS = -Wall -Wextra -Wno-unused-parameter
+
+if BUILD_TESTS
+C/samples/test-custom-gsource: C/samples/test-custom-gsource.c C/samples/example-custom-gsource.c
+ $(CC) -o $@ $(WARN_CFLAGS) $(GLIB_CFLAGS) $(GLIB_LIBS) $<
+
+check: C/samples/test-custom-gsource
+ C/samples/test-custom-gsource
+.PHONY: check
+
+CLEANFILES = C/samples/test-custom-gsource
+endif BUILD_TESTS
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]