Imported Upstream version 2.6.1
[debian/amanda] / perl / amglue / source.c
diff --git a/perl/amglue/source.c b/perl/amglue/source.c
new file mode 100644 (file)
index 0000000..d7b259a
--- /dev/null
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2005 Zmanda, Inc.  All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License version 2.1 as
+ * published by the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA.
+ *
+ * Contact information: Zmanda Inc., 465 S Mathlida Ave, Suite 300
+ * Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
+ */
+
+#include "amglue.h"
+
+/* GSources are tricky to bind to perl for a few reasons:
+ *  - they have a one-way state machine: once attached and detached, they
+ *    cannot be re-attached
+ *  - different "kinds" of GSources require C-level callbacks with
+ *    different signatures
+ *  - an attached GSource should continue running, even if not referenced
+ *    from perl, while a detached GSource should free all its resources
+ *    when no longer referenced.
+ *
+ * To accomplish all of this, this file implements a "glue object" called
+ * amglue_Source.  There are zero or one amglue_Source objects for each
+ * GSource object, so they serve as a place to store "extra" data about a
+ * GSource.  In particular, they store:
+ *  - a pointer to a C callback function that can trigger a Perl callback
+ *  - a pointer to an SV representing the perl callback to run
+ *  - a reference count
+ * Any number of Perl SV's may reference the amglue_Source -- it tracks this
+ * via its reference count.
+ *
+ * Let's look at this arrangement as it follows a typical usage scenario.  The
+ * numbers in brackets are reference counts.
+ *
+ * -- my $src = Amanda::MainLoop::new_foo_source();
+ * GSrc[1] <----) amSrc[1] <---- $src[1] <--- perl-stack
+ *
+ * The lexical $src contains a reference to the amglue_Source object, which is
+ * referencing the underlying GSource object.  Pretty simple.  The amglue_Source
+ * only counts one reference because the GSource isn't yet attached.  Think of
+ * the ')' in the diagram as a weak reference.  If the perl scope were to end
+ * now, all of these objects would be freed immediately.
+ *
+ * -- $src->set_callback(\&cb);
+ *                              ,--> &cb[1]
+ * GMainLoop --> GSrc[2] <---> amSrc[2]
+ *                              ^--- $src[1] <--- perl-stack
+ *
+ * The GSource has been attached, so GMainLoop holds a reference to it.  The
+ * amglue_Source incremented its own reference count, making the previous weak
+ * reference a full reference, because the link from the GSource will be used
+ * when a callback occurs.  The amglue_Source object also keeps a reference to
+ * the callback coderef.
+ *
+ * -- return;
+ *                              ,--> &cb[1]
+ * GMainLoop --> GSrc[2] <---> amSrc[1]
+ *
+ * When the perl scope ends, the lexical $src is freed, reducing the reference
+ * count on the amglue_Source to 1.  At this point, the object is not accessible
+ * from perl, but it is still accessible from the GSource via a callback.
+ *
+ * -- # in callback
+ *                              ,--> &cb[1]
+ * GMainLoop --> GSrc[2] <---> amSrc[2] <--- $self[1] <--- perl-stack
+ *
+ * When the callback is invoked, a reference to the amglue_Source is placed on
+ * the perl stack, so it is once again referenced twice.
+ *
+ * -- $self->remove();
+ *               GSrc[1] <---) amSrc[1] <--- $self[1] <--- perl-stack
+ *
+ * Now the callback itself has called remove().  The amglue_Source object removes
+ * the GSource from the MainLoop and drops its reference to the perl callback, and
+ * decrements its refcount to again weaken the reference from the GSource.  The
+ * amglue_Source is now useless, but since it is still in scope, it remains
+ * allocated and accessible.
+ *
+ * -- return;
+ *
+ * When the callback returns, the last reference to SV is destroyed, reducing
+ * the reference count to the amglue_Source to zero, reducing the reference to
+ * the GSource to zero.  Everything is gone.
+ */
+
+/* We use a glib 'dataset' to attach an amglue_Source to each GSource
+ * object.  This requires a Quark to describe the kind of data being
+ * attached.
+ *
+ * We define a macro and corresponding global to support access
+ * to our quark.  The compiler will optimize out all but the first
+ * conditional in each function, which is just as we want it. */
+static GQuark _quark = 0;
+#define AMGLUE_SOURCE_QUARK \
+    ( _quark?_quark:(_quark = g_quark_from_static_string("amglue_Source")) )
+
+amglue_Source *
+amglue_source_get(
+    GSource *gsrc,
+    GSourceFunc callback)
+{
+    amglue_Source *src;
+    g_assert(gsrc != NULL);
+
+    src = (amglue_Source *)g_dataset_id_get_data(gsrc, AMGLUE_SOURCE_QUARK);
+
+    if (!src)
+       src = amglue_source_new(gsrc, callback);
+    else
+       amglue_source_ref(src);
+
+    return src;
+}
+
+amglue_Source *
+amglue_source_new(
+    GSource *gsrc,
+    GSourceFunc callback)
+{
+    amglue_Source *src = g_new0(amglue_Source, 1);
+    src->src = gsrc;
+    src->callback = callback;
+    src->state = AMGLUE_SOURCE_NEW;
+    src->refcount = 1;
+    g_dataset_id_set_data(gsrc, AMGLUE_SOURCE_QUARK, (gpointer)src);
+
+    return src;
+}
+
+void
+amglue_source_free(
+    amglue_Source *self)
+{
+    /* if we're attached, we hold a circular reference to ourselves,
+     * so we shouldn't be at refcount=0 */
+    g_assert(self->state != AMGLUE_SOURCE_ATTACHED);
+    g_assert(self->callback_sv == NULL);
+
+    g_dataset_id_remove_data(self->src, AMGLUE_SOURCE_QUARK);
+    g_source_unref(self->src);
+    g_free(self);
+}