diff --git a/docs/reference/gio/gio-docs.xml b/docs/reference/gio/gio-docs.xml
index 6df88da..95b9382 100644
--- a/docs/reference/gio/gio-docs.xml
+++ b/docs/reference/gio/gio-docs.xml
@@ -37,6 +37,7 @@
 	<xi:include href="xml/gasyncresult.xml"/>
         <xi:include href="xml/gioscheduler.xml"/>
 	<xi:include href="xml/gsimpleasyncresult.xml"/>
+	<xi:include href="xml/gfiber.xml"/>
     </chapter>
     
     <chapter id="streaming">
diff --git a/docs/reference/gio/gio-sections.txt b/docs/reference/gio/gio-sections.txt
index aa5723b..3741022 100644
--- a/docs/reference/gio/gio-sections.txt
+++ b/docs/reference/gio/gio-sections.txt
@@ -1,4 +1,37 @@
 <SECTION>
+<FILE>gfiber</FILE>
+<TITLE>GFiber</TITLE>
+GFiber
+GFiberFunc
+g_fiber_run
+g_fiber_run_full
+g_fiber_run_finish
+g_fiber_get_context
+g_fiber_get_priority
+g_fiber_get_cancellable
+g_fiber_get_op_id
+g_fiber_get_async_ready_callback
+g_fiber_get_async_ready_user_data
+g_fiber_get_async_result
+g_fiber_yield
+g_fiber_idle
+g_fiber_sleep
+g_fiber_sleep_seconds
+g_fiber_attach_source
+g_fiber_get_current
+<SUBSECTION Standard>
+GFiberClass
+G_FIBER
+G_IS_FIBER
+G_TYPE_FIBER
+G_FIBER_CLASS
+G_IS_FIBER_CLASS
+G_FIBER_GET_CLASS
+<SUBSECTION Private>
+g_fiber_get_type
+</SECTION>
+
+<SECTION>
 <FILE>ginitable</FILE>
 <TITLE>GInitable</TITLE>
 GInitable
diff --git a/docs/reference/gio/gio.types b/docs/reference/gio/gio.types
index 5980100..35e0b4f 100644
--- a/docs/reference/gio/gio.types
+++ b/docs/reference/gio/gio.types
@@ -17,6 +17,7 @@ g_drive_get_type
 g_emblem_get_type
 g_emblem_origin_get_type
 g_emblemed_icon_get_type
+g_fiber_get_type
 g_file_attribute_info_flags_get_type
 g_file_attribute_status_get_type
 g_file_attribute_type_get_type
diff --git a/gio/Makefile.am b/gio/Makefile.am
index 3f8f180..4d4aeb7 100644
--- a/gio/Makefile.am
+++ b/gio/Makefile.am
@@ -50,7 +50,7 @@ AM_CPPFLAGS = \
 	$(GLIB_DEBUG_FLAGS)				\
 	-DG_DISABLE_DEPRECATED				\
 	-DGIO_COMPILATION				\
-	-DGIO_MODULE_DIR=\"$(GIO_MODULE_DIR)\"	
+	-DGIO_MODULE_DIR=\"$(GIO_MODULE_DIR)\"
 
 lib_LTLIBRARIES = libgio-2.0.la
 
@@ -196,6 +196,8 @@ libgio_2_0_la_SOURCES =		\
 	gemblem.c 		\
 	gemblemedicon.h		\
 	gemblemedicon.c		\
+	gfiber.c		\
+	gfiber.h		\
 	gfile.c 		\
 	gfileattribute.c 	\
 	gfileattribute-priv.h 	\
@@ -328,6 +330,7 @@ gio_headers =			\
 	gdrive.h 		\
 	gemblem.h 		\
 	gemblemedicon.h		\
+	gfiber.h		\
 	gfile.h 		\
 	gfileattribute.h 	\
 	gfileenumerator.h 	\
diff --git a/gio/gfiber.c b/gio/gfiber.c
new file mode 100644
index 0000000..4443db8
--- /dev/null
+++ b/gio/gfiber.c
@@ -0,0 +1,991 @@
+/*
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * 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.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#include "config.h"
+
+#include "gioerror.h"
+#include "gcancellable.h"
+#include "gsimpleasyncresult.h"
+#include "gfiber.h"
+
+#include "glibintl.h"
+#include "gioalias.h"
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* Low-level abstraction */
+
+typedef struct _ExecutionContext      ExecutionContext;
+typedef struct _ExecutionContextGroup ExecutionContextGroup;
+
+struct _ExecutionContext
+{
+  /* the function to execute */
+  void (*func) (gpointer data);
+
+  /* the context that transfered control to us */
+  ExecutionContext *caller;
+
+  /* the group that the context belongs to */
+  ExecutionContextGroup *group;
+
+  gpointer data;
+
+  gboolean runnable;
+
+  /* the GFiber object associated with the context (NULL for leader contexts) */
+  gpointer fiber;
+};
+
+struct _ExecutionContextGroup
+{
+  /* thread pool used for group */
+  GThreadPool *thread_pool;
+
+  /* the currently running execution context */
+  ExecutionContext *current;
+
+  /* the leader of the group */
+  ExecutionContext leader;
+
+  /* synchronization variables to ensure only a single running context */
+  GMutex *run_lock;
+  GCond *run_cond;
+};
+
+static ExecutionContextGroup *execution_context_group_get (void);
+
+static void                   execution_context_init      (ExecutionContext *context);
+
+static gpointer               execution_context_transfer  (ExecutionContext *from,
+                                                           ExecutionContext *to,
+                                                           gpointer          arg);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* A thread-specific variable for the ExecutionContentGroup that is attached to the thread */
+static GStaticPrivate _group = G_STATIC_PRIVATE_INIT;
+
+static void
+exeuction_context_group_thread_func (gpointer data,        /* data passed from g_thread_pool_push() */
+                                     gpointer user_data)   /* data passed from g_thread_pool_new() */
+{
+  ExecutionContext *context = data;
+  ExecutionContextGroup *group = user_data;
+
+  /* use the same execution context group so fibers created inside
+   * this fiber will belong to the same group
+   */
+  g_static_private_set (&_group, group, NULL); /* TODO: clean up? */
+
+  g_mutex_lock (group->run_lock);
+  while (!context->runnable)
+    {
+      g_cond_wait (group->run_cond, group->run_lock);
+    }
+
+  group->current = context;
+  context->func (context->data);
+
+  context->caller->runnable = TRUE;
+  g_cond_broadcast (group->run_cond);
+  g_mutex_unlock (group->run_lock);
+}
+
+static void
+execution_context_init (ExecutionContext *context)
+{
+  ExecutionContextGroup *group;
+
+  group = execution_context_group_get ();
+
+  context->runnable = FALSE;
+  context->caller = NULL;
+  context->group = group;
+  g_thread_pool_push (group->thread_pool,
+                      context,
+                      NULL);
+}
+
+static gpointer
+execution_context_transfer (ExecutionContext *from,
+                            ExecutionContext *to,
+                            gpointer          arg)
+{
+  ExecutionContextGroup *group;
+
+  group = execution_context_group_get ();
+  g_assert (from->group == group && from->group == to->group);
+
+  from->runnable = FALSE;
+  to->runnable = TRUE;
+  to->data = arg;
+  to->caller = from;
+
+  g_cond_broadcast (group->run_cond);
+  g_mutex_unlock (group->run_lock);
+  g_mutex_lock (group->run_lock);
+  while (!from->runnable) {
+    g_cond_wait (group->run_cond, group->run_lock);
+  }
+  group->current = from;
+
+  return from->data;
+}
+
+static ExecutionContextGroup *
+execution_context_group_get (void)
+{
+  ExecutionContextGroup *group;
+
+  group = g_static_private_get (&_group);
+  if (G_LIKELY (group != NULL))
+    goto out;
+
+  group = g_new0 (ExecutionContextGroup, 1);
+  group->thread_pool = g_thread_pool_new (exeuction_context_group_thread_func,
+                                          group,
+                                          -1,    /* no limit */
+                                          FALSE, /* not exclusive */
+                                          NULL);
+  group->run_lock = g_mutex_new ();
+  group->run_cond = g_cond_new ();
+
+  group->leader.runnable = TRUE;
+  group->leader.group = group;
+  group->current = &group->leader;
+  g_mutex_lock (group->run_lock);  /* take the lock since leader is running right now */
+
+  g_static_private_set (&_group, group, NULL); /* TODO: clean up? */
+
+ out:
+  return group;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static guint
+_g_idle_add_full_for_context (GMainContext  *context,
+                              gint           priority,
+                              GSourceFunc    function,
+                              gpointer       data,
+                              GDestroyNotify notify)
+{
+  GSource *source;
+  guint id;
+
+  g_return_val_if_fail (function != NULL, 0);
+
+  source = g_idle_source_new ();
+
+  if (priority != G_PRIORITY_DEFAULT_IDLE)
+    g_source_set_priority (source, priority);
+
+  g_source_set_callback (source, function, data, notify);
+  id = g_source_attach (source, context);
+  g_source_unref (source);
+
+  return id;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * SECTION:gfiber
+ * @short_description: Cooperatively scheduled threads of execution
+ * @include: gio/gio.h
+ *
+ * A #GFiber is a thread of execution that is cooperatively scheduled
+ * by the application. Because of this, synchronization constructs,
+ * such as #GMutex, are unnecessary in code using fibers since only a
+ * single fiber runs at one time.
+ *
+ * For maximum portability, #GFiber is implemented using #GThread so
+ * thread-specific data (such as #GPrivate) applies to fibers as well.
+ * If performance is a concern, applications should be using e.g.
+ * makecontext()/swapcontext() as defined by POSIX.1-2001.
+ *
+ * TODO: write more stuff here. give examples. talk about relation to
+ * threads. etc etc etc.
+ */
+
+/**
+ * GFiber:
+ *
+ * A cooperatively scheduled thread of execution.
+ *
+ * Since: 2.22
+ */
+
+struct _GFiberPrivate
+{
+  ExecutionContext context;
+  ExecutionContext *main_context;
+
+  GFiberFunc fiber_func;
+  gpointer fiber_data;
+
+  GAsyncReadyCallback callback;
+  gpointer callback_data;
+
+  GMainContext *gmain_context;
+  gint priority;
+  GCancellable *cancellable;
+
+  GAsyncResult *async_result;
+
+  guint         id_that_resumed_us;
+
+  guint next_async_id;
+
+  GAsyncResult *last_async_res;
+
+  guint id_counter;
+};
+
+G_DEFINE_TYPE (GFiber, g_fiber, G_TYPE_OBJECT);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+g_fiber_finalize (GObject *object)
+{
+  GFiber *fiber = G_FIBER (object);
+
+  if (fiber->priv->cancellable != NULL)
+    g_object_unref (fiber->priv->cancellable);
+
+  if (fiber->priv->gmain_context != NULL)
+    g_main_context_unref (fiber->priv->gmain_context);
+
+  if (G_OBJECT_CLASS (g_fiber_parent_class)->finalize != NULL)
+    G_OBJECT_CLASS (g_fiber_parent_class)->finalize (object);
+}
+
+/**
+ * g_fiber_run_finish:
+ * @fiber: A #GFiber.
+ * @res: A #GAsyncResult.
+ * @error: Return location for the #GError set by the #GFiberFunc or %NULL.
+ *
+ * Function to get the result of running a fiber.
+ *
+ * This function is intended to be called from the
+ * #GAsyncReadyCallback function supplied to g_fiber_run() or
+ * g_fiber_run_full().
+ *
+ * Returns: The return value from the #GFiberFunc.
+ *
+ * Since: 2.22
+ */
+gpointer
+g_fiber_run_finish (GFiber              *fiber,
+                    GAsyncResult        *res,
+                    GError             **error)
+{
+  GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
+  gpointer ret;
+
+  g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == g_fiber_run_finish);
+
+  ret = g_simple_async_result_get_op_res_gpointer (simple);
+
+  g_simple_async_result_propagate_error (simple, error);
+
+  return ret;
+}
+
+static GStaticPrivate current_fiber = G_STATIC_PRIVATE_INIT;
+
+/* runs in newly created thread for the fiber */
+static void
+run_fiber_func (void *data)
+{
+  GFiber *fiber = G_FIBER (data);
+  GError *error;
+  gpointer result;
+  GSimpleAsyncResult *simple;
+
+  g_static_private_set (&current_fiber, NULL, NULL);
+
+  /* ok, we're now in our own thread, run the fiber function */
+  error = NULL;
+  result = fiber->priv->fiber_func (fiber,
+                                    fiber->priv->fiber_data,
+                                    fiber->priv->cancellable,
+                                    &error);
+
+  /* report the result back */
+  simple = g_simple_async_result_new (G_OBJECT (fiber),
+                                      fiber->priv->callback,
+                                      fiber->priv->callback_data,
+                                      g_fiber_run_finish);
+  g_simple_async_result_set_op_res_gpointer (simple, result, NULL);
+
+  /* set error if cancelled */
+  if (error == NULL && fiber->priv->cancellable != NULL)
+    g_cancellable_set_error_if_cancelled (fiber->priv->cancellable, &error);
+
+  if (error != NULL)
+    {
+      g_simple_async_result_set_from_error (simple, error);
+      g_error_free (error);
+    }
+  g_simple_async_result_complete_in_idle (simple);
+  g_object_unref (simple);
+
+  /* Nuke the fiber object; note that the reference count here is 2
+   * since the GSimpleAsyncResult object holds a reference. So our
+   * finalize() method won't run until the main loop is processing the
+   * idle handler
+   */
+  g_object_unref (fiber);
+
+}
+
+/* runs in the thread for the GMainContext for the fiber */
+static gboolean
+schedule_fiber (gpointer user_data)
+{
+  GFiber *fiber = G_FIBER (user_data);
+  ExecutionContextGroup *group;
+
+  group = execution_context_group_get ();
+  fiber->priv->main_context = &group->leader;
+  fiber->priv->context.func = run_fiber_func;
+  fiber->priv->context.fiber = fiber;
+  execution_context_init (&fiber->priv->context);
+
+  execution_context_transfer (fiber->priv->main_context, &fiber->priv->context, g_object_ref (fiber));
+
+  return FALSE;
+}
+
+/**
+ * g_fiber_run_full:
+ * @func: A #GFiberFunc.
+ * @data: Data to pass to @func.
+ * @context: A #GMainContext or %NULL for the default context.
+ * @priority: A priority such as #G_PRIORITY_DEFAULT.
+ * @cancellable: A #GCancellable to pass to @func or %NULL.
+ * @callback: A #GAsyncReadyCallback to invoke when the fiber completes or %NULL to ignore.
+ * @user_data: User data to pass to @callback.
+ *
+ * Creates a new fiber that will run in a separate context of
+ * execution. When @func returns, @callback is invoked and you can
+ * call g_fiber_run_finish() to get the result.
+ *
+ * Note that this function returns immediately – @func will be
+ * scheduled in idle for @context with priority @priority.
+ *
+ * For a simpler version of this function with fewer options, see
+ * g_fiber_run().
+ *
+ * Since: 2.22
+ */
+void
+g_fiber_run_full (GFiberFunc           func,
+                  gpointer             data,
+                  GMainContext        *context,
+                  gint                 priority,
+                  GCancellable        *cancellable,
+                  GAsyncReadyCallback  callback,
+                  gpointer             user_data)
+{
+  GFiber *fiber;
+
+  fiber = G_FIBER (g_object_new (G_TYPE_FIBER, NULL));
+
+  fiber->priv->callback = callback;
+  fiber->priv->callback_data = user_data;
+
+  if (cancellable != NULL)
+    {
+      fiber->priv->cancellable = g_object_ref (cancellable);
+
+      if (g_cancellable_is_cancelled (fiber->priv->cancellable))
+        {
+          GSimpleAsyncResult *simple;
+          /* already cancelled */
+          simple = g_simple_async_result_new (G_OBJECT (fiber),
+                                              fiber->priv->callback,
+                                              fiber->priv->callback_data,
+                                              g_fiber_run_finish);
+          g_simple_async_result_set_error (simple,
+                                           G_IO_ERROR,
+                                           G_IO_ERROR_CANCELLED,
+                                           _("Operation was cancelled"));
+          g_simple_async_result_complete_in_idle (simple);
+          g_object_unref (simple);
+          g_object_unref (fiber);
+          goto out;
+        }
+    }
+
+  if (context != NULL)
+    fiber->priv->gmain_context = g_main_context_ref (context);
+  fiber->priv->priority = priority;
+
+  fiber->priv->fiber_func = func;
+  fiber->priv->fiber_data = data;
+
+  _g_idle_add_full_for_context (context, priority, schedule_fiber, fiber, g_object_unref);
+
+ out:
+  ;
+}
+
+/**
+ * g_fiber_run:
+ * @func: A #GFiberFunc.
+ * @data: Data to pass to @func.
+ * @cancellable: A #GCancellable to pass to @func or %NULL.
+ * @callback: A #GAsyncReadyCallback to invoke when the fiber completes or %NULL to ignore.
+ * @user_data: User data to pass to @callback.
+ *
+ * Creates a new fiber that will run in a separate context of
+ * execution. When @func returns, @callback is invoked and you can
+ * call g_fiber_run_finish() to get the result.
+ *
+ * Note that this function returns immediately – @func will be
+ * scheduled in idle for the default #GMainContext with priority
+ * #G_PRIORITY_DEFAULT.
+ *
+ * For a more complicated version of this function with more options,
+ * see g_fiber_run_full().
+ *
+ * Since: 2.22
+ */
+void
+g_fiber_run (GFiberFunc           func,
+             gpointer             data,
+             GCancellable        *cancellable,
+             GAsyncReadyCallback  callback,
+             gpointer             user_data)
+{
+  g_fiber_run_full (func,
+                    data,
+                    NULL,
+                    G_PRIORITY_DEFAULT,
+                    cancellable,
+                    callback,
+                    user_data);
+}
+
+static void
+g_fiber_class_init (GFiberClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  gobject_class->finalize     = g_fiber_finalize;
+
+  g_type_class_add_private (klass, sizeof (GFiberPrivate));
+}
+
+static void
+g_fiber_init (GFiber *fiber)
+{
+  fiber->priv = G_TYPE_INSTANCE_GET_PRIVATE (fiber, G_TYPE_FIBER, GFiberPrivate);
+
+  fiber->priv->id_counter = 1;
+}
+
+/**
+ * g_fiber_get_context:
+ * @fiber: A #GFiber.
+ *
+ * Gets the #GMainContext used for @fiber.
+ *
+ * Returns: A #GMainContext or %NULL if @fiber is using the default
+ * context. Do not free, this object is owned by @fiber.
+ *
+ * Since: 2.22
+ */
+GMainContext *
+g_fiber_get_context (GFiber *fiber)
+{
+  g_return_val_if_fail (G_IS_FIBER (fiber), NULL);
+  return fiber->priv->gmain_context;
+}
+
+/**
+ * g_fiber_get_priority:
+ * @fiber: A #GFiber.
+ *
+ * Gets the priority used for @fiber.
+ *
+ * Returns: A priority such as #G_PRIORITY_DEFAULT.
+ *
+ * Since: 2.22
+ */
+gint
+g_fiber_get_priority (GFiber *fiber)
+{
+  g_return_val_if_fail (G_IS_FIBER (fiber), 0);
+  return fiber->priv->priority;
+}
+
+/**
+ * g_fiber_get_cancellable:
+ * @fiber: A #GFiber.
+ *
+ * Gets the #GCancellable passed to @fiber, if any.
+ *
+ * Returns: A #GCancellable or %NULL. Do not free, this object is
+ * owned by @fiber.
+ *
+ * Since: 2.22
+ */
+GCancellable *
+g_fiber_get_cancellable (GFiber *fiber)
+{
+  g_return_val_if_fail (G_IS_FIBER (fiber), NULL);
+  return fiber->priv->cancellable;
+}
+
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* represents a pending event ready to be delivered via g_fiber_yield() */
+typedef struct
+{
+  GFiber *fiber;
+  guint id;
+  GAsyncResult *res;
+} PendingEvent;
+
+static PendingEvent *
+pending_event_new (GFiber       *fiber,
+                   guint         id,
+                   GAsyncResult *res)
+{
+  PendingEvent *event;
+
+  event = g_new0 (PendingEvent, 1);
+  event->fiber = g_object_ref (fiber);
+  event->id = id;
+  event->res = res != NULL ? g_object_ref (res) : NULL;
+
+  return event;
+}
+
+static void
+pending_event_free (PendingEvent *event)
+{
+  if (event->res != NULL)
+    g_object_unref (event->res);
+  g_object_unref (event->fiber);
+  g_free (event);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static guint
+g_fiber_allocate_id (GFiber *fiber)
+{
+  return fiber->priv->id_counter++;
+}
+
+static gboolean
+resume_fiber_in_idle (gpointer user_data)
+{
+  PendingEvent *event = user_data;
+
+  event->fiber->priv->id_that_resumed_us = event->id;
+  event->fiber->priv->last_async_res = event->res;
+  execution_context_transfer (event->fiber->priv->main_context, &event->fiber->priv->context, NULL);
+
+  event->fiber->priv->id_that_resumed_us = 0;
+  event->fiber->priv->last_async_res = NULL;
+
+  return FALSE;
+}
+
+static void
+g_fiber_resume (GFiber        *fiber,
+                guint          id,
+                GAsyncResult  *res)
+{
+  /* ensure we switch to this context from the right thread */
+  _g_idle_add_full_for_context (fiber->priv->gmain_context,
+                                fiber->priv->priority,
+                                resume_fiber_in_idle,
+                                pending_event_new (fiber, id, res),
+                                (GDestroyNotify) pending_event_free);
+}
+
+/**
+ * g_fiber_yield:
+ * @fiber: A #GFiber.
+ *
+ * Yields execution to other execution contexts until an event source
+ * resumes @fiber.
+ *
+ * Returns: The id of the event source that resumed @fiber.
+ *
+ * Since: 2.22
+ */
+guint
+g_fiber_yield (GFiber *fiber)
+{
+  execution_context_transfer (&fiber->priv->context, fiber->priv->main_context, NULL);
+  return fiber->priv->id_that_resumed_us;
+}
+
+typedef struct
+{
+  guint id;
+  GFiber *fiber;
+} AsyncData;
+
+/**
+ * g_fiber_get_op_id:
+ * @fiber: A #GFiber.
+ *
+ * Gets the id for the event source for the next asynchronous
+ * callback.
+ *
+ * Returns: An event source id (never 0).
+ *
+ * Since: 2.22
+ */
+guint
+g_fiber_get_op_id (GFiber *fiber)
+{
+  g_return_val_if_fail (G_IS_FIBER (fiber), 0);
+  g_return_val_if_fail (fiber->priv->next_async_id == 0, 0);
+
+  fiber->priv->next_async_id = g_fiber_allocate_id (fiber);
+
+  return fiber->priv->next_async_id;
+}
+
+static void
+generic_fiber_async_cb (GObject *source_object,
+                        GAsyncResult *res,
+                        gpointer user_data)
+{
+  AsyncData *data = user_data;
+
+  g_fiber_resume (data->fiber, data->id, res);
+  g_free (data);
+}
+
+/**
+ * g_fiber_get_async_ready_callback:
+ * @fiber: A #GFiber.
+ *
+ * Gets a #GAsyncReadyCallback function to pass to an asynchronous
+ * function.
+ *
+ * Returns: A #GAsyncReadyCallback.
+ *
+ * Since: 2.22
+ */
+GAsyncReadyCallback
+g_fiber_get_async_ready_callback (GFiber *fiber)
+{
+  return generic_fiber_async_cb;
+}
+
+/**
+ * g_fiber_get_async_ready_user_data:
+ * @fiber: A #GFiber.
+ *
+ * Gets a #gpointer for user data to pass to an asynchronous function.
+ *
+ * Returns: A #gpointer.
+ *
+ * Since: 2.22
+ */
+gpointer
+g_fiber_get_async_ready_user_data (GFiber *fiber)
+{
+  AsyncData *data;
+
+  if (fiber->priv->next_async_id == 0)
+    g_fiber_get_op_id (fiber);
+
+  data = g_new0 (AsyncData, 1);
+  data->id = fiber->priv->next_async_id;
+  data->fiber = fiber;
+
+  fiber->priv->next_async_id = 0;
+
+  return data;
+}
+
+/**
+ * g_fiber_get_async_result:
+ * @fiber: A #GFiber.
+ *
+ * Gets the #GAsyncResult for the event source that resumed up @fiber
+ * for an asynchronous function, if any.
+ *
+ * You can use this value to call the corresponding _finish() function
+ * for the asynchronous function to get the result of the operation.
+ *
+ * Returns: A #GAsyncResult or %NULL if @fiber was not resumed by an
+ * event source for an asynchronous function.
+ *
+ * Since: 2.22
+ */
+GAsyncResult *
+g_fiber_get_async_result (GFiber *fiber)
+{
+  return fiber->priv->last_async_res;
+}
+
+typedef struct
+{
+  GSourceFuncs funcs;
+  GSourceFuncs *orig_funcs;
+  GFiber *fiber;
+  guint id;
+} GFiberSourceFuncsData;
+
+static gboolean
+g_fiber_source_func_dispatch (GSource    *source,
+                              GSourceFunc callback,
+                              gpointer    user_data)
+{
+  GFiberSourceFuncsData *data;
+  gboolean ret;
+
+  data = (GFiberSourceFuncsData *) source->source_funcs;
+
+  /* chain up to the dispatch function */
+  ret = data->orig_funcs->dispatch (source, callback, user_data);
+
+  /* now resume the fiber */
+  g_fiber_resume (data->fiber, data->id, NULL);
+
+  return ret;
+}
+
+static void
+g_fiber_source_func_finalize (GSource *source)
+{
+  GFiberSourceFuncsData *data;
+
+  /* free the struct with our overridden functions and replace original functions */
+  data = (GFiberSourceFuncsData *) source->source_funcs;
+
+  source->source_funcs = data->orig_funcs;
+  g_free (data);
+
+  /* chain up */
+  if (source->source_funcs->finalize != NULL)
+    source->source_funcs->finalize (source);
+}
+
+/**
+ * g_fiber_attach_source:
+ * @fiber: A #GFiber.
+ * @source: A #GSource.
+ *
+ * Attaches @source to @fiber and the #GMainContext used by @fiber.
+ *
+ * Whenever @source is ready to dispatch data, @fiber will be resumed
+ * and g_fiber_yield() will return the event source id returned by
+ * this function.
+ *
+ * Note that the returned event source id is not the same as get with
+ * g_source_get_id(). The former is specific to @fiber, the latter to
+ * the #GMainContext that @fiber is assoicated with.
+ *
+ * Use g_source_destroy() to detach the @source from @fiber and the
+ * underlying #GMainContext.
+ *
+ * Returns: An event source id (never 0).
+ *
+ * Since: 2.22
+ */
+guint
+g_fiber_attach_source (GFiber        *fiber,
+                       GSource       *source)
+{
+  GFiberSourceFuncsData *data;
+
+  g_return_val_if_fail (G_IS_FIBER (fiber), 0);
+  g_return_val_if_fail (source->source_funcs != NULL, 0);
+
+  /* Override dispatch() and finalize() functions to hook into dispatching */
+  data = g_new0 (GFiberSourceFuncsData, 1);
+  data->orig_funcs = source->source_funcs;
+  data->funcs = *source->source_funcs;
+  data->funcs.dispatch = g_fiber_source_func_dispatch;
+  data->funcs.finalize = g_fiber_source_func_finalize;
+  data->fiber = fiber;
+  data->id = g_fiber_allocate_id (fiber);
+
+  source->source_funcs = (GSourceFuncs *) data;
+
+  g_source_attach (source, fiber->priv->gmain_context);
+
+  return data->id;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * g_fiber_get_current:
+ *
+ * Gets the currently executing fiber, if any, for the current thread.
+ *
+ * Returns: A #GFiber for the currently executing thread or %NULL if
+ * not executing code in a fiber. Do not free this object.
+ *
+ * Since: 2.22
+ **/
+GFiber *
+g_fiber_get_current (void)
+{
+  ExecutionContextGroup *group;
+
+  group = execution_context_group_get ();
+
+  return group->current->fiber;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+on_cb (gpointer user_data)
+{
+  return FALSE;
+}
+
+/* generic dispatch used by g_fiber_sleep(), g_fiber_sleep_seconds() and g_fiber_idle() */
+static guint
+generic_dispatch (GFiber *fiber,
+                  GSource *source)
+{
+  guint id;
+  guint id_that_resumed_us;
+  guint ret;
+
+  g_source_set_priority (source, fiber->priv->priority);
+  g_source_set_callback (source, on_cb, NULL, NULL);
+  id = g_fiber_attach_source (fiber, source);
+
+  id_that_resumed_us = g_fiber_yield (fiber);
+
+  if (id_that_resumed_us == id)
+    {
+      /* Our source resumed us (it has already been destroyed) */
+      ret = 0;
+    }
+  else
+    {
+      /* We were resumed by something else, destroy the source
+       * since it is still pending
+       */
+      g_source_destroy (source);
+      ret = id_that_resumed_us;
+    }
+
+  g_source_unref (source);
+
+  return ret;
+}
+
+/**
+ * g_fiber_sleep:
+ * @fiber: A #GFiber.
+ * @msec: Number of milliseconds to sleep.
+ *
+ * Makes @fiber sleep for up to @msec milliseconds.
+ *
+ * If an event source resumes @fiber, then this function returns the
+ * event source id of the event source (possibly much sooner than
+ * within @msec milliseconds).
+ *
+ * Returns: 0 if no event sources resumed @fiber, otherwise the id of
+ * the event source.
+ *
+ * Since: 2.22
+ */
+guint
+g_fiber_sleep (GFiber *fiber,
+               guint   msec)
+{
+  GSource *source;
+
+  source = g_timeout_source_new (msec);
+
+  return generic_dispatch (fiber, source);
+}
+
+/**
+ * g_fiber_sleep_seconds:
+ * @fiber: A #GFiber.
+ * @seconds: Number of seconds to sleep.
+ *
+ * Makes @fiber sleep for up to approximately @seconds seconds.
+ *
+ * If an event source resumes @fiber, then this function returns the
+ * event source id of the event source (possibly much sooner than
+ * within @seconds seconds).
+ *
+ * Returns: 0 if no event sources resumed @fiber, otherwise the id of
+ * the event source.
+ *
+ * Since: 2.22
+ */
+guint
+g_fiber_sleep_seconds (GFiber *fiber,
+                       guint   seconds)
+{
+  GSource *source;
+
+  source = g_timeout_source_new_seconds (seconds);
+
+  return generic_dispatch (fiber, source);
+}
+
+/**
+ * g_fiber_idle:
+ * @fiber: A #GFiber.
+ *
+ * Makes @fiber sleep until no events with higher priority than
+ * g_fiber_get_priority() are pending.
+ *
+ * If an event source resumes @fiber, then this function returns the
+ * event source id of the event source.
+ *
+ * Returns: 0 if no events with higher priority than
+ * g_fiber_get_priority() are pending; otherwise the id of the event
+ * source that resumed @fiber.
+ *
+ * Since: 2.22
+ */
+guint
+g_fiber_idle (GFiber  *fiber)
+{
+  GSource *source;
+
+  source = g_idle_source_new ();
+
+  return generic_dispatch (fiber, source);
+}
+
+#define __G_FIBER_C__
+#include "gioaliasdef.c"
diff --git a/gio/gfiber.h b/gio/gfiber.h
new file mode 100644
index 0000000..9090599
--- /dev/null
+++ b/gio/gfiber.h
@@ -0,0 +1,107 @@
+/* GIO - GLib Input, Output and Streaming Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * 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.
+ *
+ * Author: David Zeuthen <davidz@redhat.com>
+ */
+
+#if !defined (__GIO_GIO_H_INSIDE__) && !defined (GIO_COMPILATION)
+#error "Only <gio/gio.h> can be included directly."
+#endif
+
+#ifndef __G_FIBER_H
+#define __G_FIBER_H__
+
+#include <gio/giotypes.h>
+
+G_BEGIN_DECLS
+
+#define G_TYPE_FIBER         (g_fiber_get_type())
+#define G_FIBER(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_FIBER, GFiber))
+#define G_FIBER_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_FIBER, GFiberClass))
+#define G_FIBER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_FIBER, GFiberClass))
+#define G_IS_FIBER(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_FIBER))
+#define G_IS_FIBER_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_FIBER))
+
+typedef struct _GFiberClass   GFiberClass;
+typedef struct _GFiberPrivate GFiberPrivate;
+
+struct _GFiber
+{
+  GObject parent_instance;
+
+  /*< private >*/
+  GFiberPrivate *priv;
+};
+
+struct _GFiberClass
+{
+  GObjectClass parent_class;
+
+  /*< private >*/
+  /* Padding for future expansion */
+  void (*_g_reserved0) (void);
+  void (*_g_reserved1) (void);
+  void (*_g_reserved2) (void);
+  void (*_g_reserved3) (void);
+  void (*_g_reserved4) (void);
+  void (*_g_reserved5) (void);
+  void (*_g_reserved6) (void);
+  void (*_g_reserved7) (void);
+};
+
+GType                g_fiber_get_type                  (void) G_GNUC_CONST;
+void                 g_fiber_run                       (GFiberFunc           func,
+                                                        gpointer             data,
+                                                        GCancellable        *cancellable,
+                                                        GAsyncReadyCallback  callback,
+                                                        gpointer             user_data);
+void                 g_fiber_run_full                  (GFiberFunc           func,
+                                                        gpointer             data,
+                                                        GMainContext        *context,
+                                                        gint                 priority,
+                                                        GCancellable        *cancellable,
+                                                        GAsyncReadyCallback  callback,
+                                                        gpointer             user_data);
+gpointer             g_fiber_run_finish                (GFiber              *fiber,
+                                                        GAsyncResult        *res,
+                                                        GError             **error);
+
+GMainContext        *g_fiber_get_context               (GFiber              *fiber);
+gint                 g_fiber_get_priority              (GFiber              *fiber);
+GCancellable        *g_fiber_get_cancellable           (GFiber              *fiber);
+
+guint                g_fiber_get_op_id                 (GFiber              *fiber);
+GAsyncReadyCallback  g_fiber_get_async_ready_callback  (GFiber              *fiber);
+gpointer             g_fiber_get_async_ready_user_data (GFiber              *fiber);
+GAsyncResult        *g_fiber_get_async_result          (GFiber              *fiber);
+guint                g_fiber_attach_source             (GFiber              *fiber,
+                                                        GSource             *source);
+guint                g_fiber_yield                     (GFiber              *fiber);
+guint                g_fiber_sleep                     (GFiber              *fiber,
+                                                        guint                msec);
+guint                g_fiber_sleep_seconds             (GFiber              *fiber,
+                                                        guint                seconds);
+guint                g_fiber_idle                      (GFiber              *fiber);
+
+
+GFiber              *g_fiber_get_current               (void);
+
+G_END_DECLS
+
+#endif /* __G_FIBER_H__ */
diff --git a/gio/gio.h b/gio/gio.h
index ad17503..abb09be 100644
--- a/gio/gio.h
+++ b/gio/gio.h
@@ -38,6 +38,7 @@
 #include <gio/gdataoutputstream.h>
 #include <gio/gdrive.h>
 #include <gio/gemblemedicon.h>
+#include <gio/gfiber.h>
 #include <gio/gfile.h>
 #include <gio/gfileattribute.h>
 #include <gio/gfileenumerator.h>
diff --git a/gio/gio.symbols b/gio/gio.symbols
index 89a2e18..b7ef953 100644
--- a/gio/gio.symbols
+++ b/gio/gio.symbols
@@ -1215,3 +1215,25 @@ g_unix_fd_message_steal_fds
 #endif
 #endif
 #endif
+
+#if IN_HEADER(__G_FIBER_H__)
+#if IN_FILE(__G_FIBER_C__)
+g_fiber_get_type G_GNUC_CONST
+g_fiber_run
+g_fiber_run_full
+g_fiber_run_finish
+g_fiber_get_async_ready_callback
+g_fiber_get_async_ready_user_data
+g_fiber_get_async_result
+g_fiber_get_context
+g_fiber_get_priority
+g_fiber_get_cancellable
+g_fiber_get_op_id
+g_fiber_attach_source
+g_fiber_idle
+g_fiber_sleep
+g_fiber_sleep_seconds
+g_fiber_yield
+g_fiber_get_current
+#endif
+#endif
diff --git a/gio/giotypes.h b/gio/giotypes.h
index 9ee099c..96093f2 100644
--- a/gio/giotypes.h
+++ b/gio/giotypes.h
@@ -326,6 +326,28 @@ struct _GOutputVector {
   gsize size;
 };
 
+typedef struct _GFiber                        GFiber;
+
+/**
+ * GFiberFunc:
+ * @fiber: A #GFiber.
+ * @data: Data passed to function.
+ * @cancellable: A #GCancellable or %NULL.
+ * @error: Return location for errors (never %NULL) that is passed
+ * back via g_fiber_run_finish().
+ *
+ * Specifies the type of functions that can be passed to g_fiber_run()
+ * and g_fiber_run_full().
+ *
+ * Returns: The return value to be passed back via
+ * g_fiber_run_finish().
+ *
+ * Since: 2.22
+ */
+typedef gpointer (*GFiberFunc) (GFiber        *fiber,
+                                gpointer       data,
+                                GCancellable  *cancellable,
+                                GError       **error);
 
 G_END_DECLS
 
diff --git a/gio/tests/Makefile.am b/gio/tests/Makefile.am
index e49c29e..47b957e 100644
--- a/gio/tests/Makefile.am
+++ b/gio/tests/Makefile.am
@@ -29,7 +29,8 @@ TEST_PROGS +=	 		\
 	sleepy-stream		\
 	filter-streams		\
 	simple-async-result	\
-	srvtarget
+	srvtarget		\
+	fibers
 
 SAMPLE_PROGS = resolver socket-server socket-client echo-server httpd send-data
 
@@ -110,4 +111,8 @@ send_data_LDADD		  = $(progs_ldadd) \
 srvtarget_SOURCES	  = srvtarget.c
 srvtarget_LDADD		  = $(progs_ldadd)
 
+fibers_SOURCES		  = fibers.c
+fibers_LDADD		  = $(progs_ldadd) \
+	$(top_builddir)/gthread/libgthread-2.0.la
+
 DISTCLEAN_FILES = applications/mimeinfo.cache
diff --git a/gio/tests/fibers.c b/gio/tests/fibers.c
new file mode 100644
index 0000000..6aeeefd
--- /dev/null
+++ b/gio/tests/fibers.c
@@ -0,0 +1,646 @@
+/* GLib testing framework examples and tests
+ * Copyright (C) 2009 Red Hat, Inc.
+ *
+ * This work is provided "as is"; redistribution and modification
+ * in whole or in part, in any medium, physical or electronic is
+ * permitted without restriction.
+ *
+ * This work 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.
+ *
+ * In no event shall the authors or contributors be liable for any
+ * direct, indirect, incidental, special, exemplary, or consequential
+ * damages (including, but not limited to, procurement of substitute
+ * goods or services; loss of use, data, or profits; or business
+ * interruption) however caused and on any theory of liability, whether
+ * in contract, strict liability, or tort (including negligence or
+ * otherwise) arising in any way out of the use of this software, even
+ * if advised of the possibility of such damage.
+ */
+
+#include <glib/glib.h>
+#include <gio/gio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <glib/gstdio.h>
+#include <unistd.h>
+
+static GMainLoop *loop;
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gpointer
+read_file_in_fiber (GFiber        *fiber,
+                    gpointer       data,
+                    GCancellable  *cancellable,
+                    GError       **error)
+{
+  const gchar *file_name = data;
+  GFile *file;
+  GFileInputStream *file_input_stream;
+  gchar buf[1024];
+  gssize bytes_read;
+  gpointer ret;
+
+  ret = NULL;
+
+  file = g_file_new_for_commandline_arg (file_name);
+
+  g_file_read_async (file,
+                     G_PRIORITY_DEFAULT,
+                     cancellable,
+                     g_fiber_get_async_ready_callback (fiber),
+                     g_fiber_get_async_ready_user_data (fiber));
+
+  g_fiber_yield (fiber);
+
+  file_input_stream = g_file_read_finish (file,
+                                          g_fiber_get_async_result (fiber),
+                                          error);
+  if (file_input_stream == NULL)
+    goto out;
+
+  memset (buf, '\0', sizeof buf);
+  g_input_stream_read_async (G_INPUT_STREAM (file_input_stream),
+                             buf,
+                             sizeof buf,
+                             G_PRIORITY_DEFAULT,
+                             cancellable,
+                             g_fiber_get_async_ready_callback (fiber),
+                             g_fiber_get_async_ready_user_data (fiber));
+
+  g_fiber_yield (fiber);
+
+  bytes_read = g_input_stream_read_finish (G_INPUT_STREAM (file_input_stream),
+                                           g_fiber_get_async_result (fiber),
+                                           error);
+  if (bytes_read == -1)
+    goto out;
+
+  ret = g_strdup (buf);
+
+ out:
+  if (file != NULL)
+    g_object_unref (file);
+  if (file_input_stream != NULL)
+    g_object_unref (file_input_stream);
+
+  return ret;
+}
+
+static void
+read_file_in_fiber_cb_expect_error (GObject       *source_object,
+                                    GAsyncResult  *res,
+                                    gpointer       user_data)
+{
+  gpointer result;
+  GError *error;
+  GError *expected_error = user_data;
+
+  error = NULL;
+  result = g_fiber_run_finish (G_FIBER (source_object),
+                               res,
+                               &error);
+  g_assert_error (error, expected_error->domain, expected_error->code);
+  g_error_free (error);
+
+  g_main_loop_quit (loop);
+}
+
+static void
+read_file_in_fiber_cb_expect_content (GObject       *source_object,
+                                      GAsyncResult  *res,
+                                      gpointer       user_data)
+{
+  gpointer result;
+  GError *error;
+  const gchar *expected_content = user_data;
+
+  error = NULL;
+  result = g_fiber_run_finish (G_FIBER (source_object),
+                               res,
+                               &error);
+  g_assert_no_error (error);
+
+  g_assert_cmpstr (result, ==, expected_content);
+
+  g_free (result);
+
+  g_main_loop_quit (loop);
+}
+
+static void
+load_file (void)
+{
+  int fd;
+  gchar *tmpfile;
+  GError *error;
+  const gchar *test_data_contents = "Some test data to go in a temporary test file";
+
+  fd = g_file_open_tmp ("fiber_XXXXXX",
+                        &tmpfile, NULL);
+  g_assert (fd != -1);
+  close (fd);
+  g_unlink (tmpfile);
+
+  error = g_error_new (G_IO_ERROR, G_IO_ERROR_NOT_FOUND, " ");
+  g_fiber_run (read_file_in_fiber,
+               tmpfile,
+               NULL,
+               read_file_in_fiber_cb_expect_error,
+               error);
+  g_main_loop_run (loop);
+  g_free (tmpfile);
+  g_error_free (error);
+
+  fd = g_file_open_tmp ("fiber_XXXXXX",
+                        &tmpfile, NULL);
+  g_assert (fd != -1);
+  close (fd);
+  g_assert (g_file_set_contents (tmpfile,
+                                 test_data_contents, -1, NULL));
+  error = g_error_new (G_IO_ERROR, G_IO_ERROR_NOT_FOUND, " ");
+  g_fiber_run (read_file_in_fiber,
+               tmpfile,
+               NULL,
+               read_file_in_fiber_cb_expect_content,
+               (gpointer) test_data_contents);
+  g_main_loop_run (loop);
+  g_unlink (tmpfile);
+  g_free (tmpfile);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GMainLoop *loop1;
+
+static gpointer
+thread_func (gpointer data)
+{
+  GMainContext *ctx;
+  GMainLoop **loop = data;
+
+  /* create a new GMainContext and run a GMainLoop in that context */
+
+  ctx = g_main_context_new ();
+  *loop = g_main_loop_new (ctx, FALSE);
+
+  g_main_loop_run (*loop);
+
+  g_main_loop_unref (*loop);
+  g_main_context_unref (ctx);
+  *loop = NULL;
+
+  return NULL;
+}
+
+static gpointer
+fiber_in_other_thread (GFiber        *fiber,
+                       gpointer       data,
+                       GCancellable  *cancellable,
+                       GError       **error)
+{
+  GThread *thread = data; /* <- other thread */
+  guint id;
+  GFile *file;
+
+  /* check we are running in the other thead */
+  //g_assert (g_thread_self () == thread);
+
+  /* this essentially calls g_fiber_yield() - check we are still in the
+   * other thread */
+  id = g_fiber_sleep (fiber, 50);
+  g_assert (id == 0);
+  //g_assert (g_thread_self () == thread);
+
+  /* check that we are still in the other thread when using async calls
+   * and resuming */
+  file = g_file_new_for_path ("/path/that/does/not/exist");
+  g_assert (file != NULL);
+  g_file_query_info_async (file,
+                           "standard::*",
+                           G_FILE_QUERY_INFO_NONE,
+                           G_PRIORITY_DEFAULT,
+                           cancellable,
+                           g_fiber_get_async_ready_callback (fiber),
+                           g_fiber_get_async_ready_user_data (fiber));
+  id = g_fiber_yield (fiber);
+  g_assert (id != 0);
+  //g_assert (g_thread_self () == thread);
+
+  /* check the file doesn't exist */
+  g_assert (g_file_query_info_finish (file,
+                                      g_fiber_get_async_result (fiber),
+                                      error) == NULL);
+  g_assert_error (*error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND);
+
+  g_object_unref (file);
+
+  return NULL;
+}
+
+static void
+fiber_in_other_thread_cb (GObject       *source_object,
+                          GAsyncResult  *res,
+                          gpointer       user_data)
+{
+  GThread *thread = user_data; /* <- the main thread */
+
+  /* check we are running in the main thread */
+  g_assert (g_thread_self () == thread);
+
+  /* quit the main loop in the main thread */
+  g_main_loop_quit (loop);
+
+  /* quit the main loop in the other thread */
+  g_main_loop_quit (loop1);
+}
+
+static void
+threads (void)
+{
+  GThread *t1;
+
+  /* create a new thread and wait until a mainloop is running in that thread */
+  t1 = g_thread_create (thread_func,
+                        &loop1,
+                        FALSE,
+                        NULL);
+  g_assert (t1 != NULL);
+  g_assert (g_thread_self () != t1);
+  while (loop1 == NULL)
+    g_main_context_iteration (NULL, FALSE);
+
+  /* run a fiber in the new threads mainloop
+   *
+   * - Pass the new GThread object to the fiber func to verify it runs in
+   *   the right thread
+   *
+   * - Pass our own GThread object to the fiber completion func to verify
+   *   we get the callback in our own thread
+   *
+   * The fiber completion func will shut down both main loops
+   */
+  g_fiber_run_full (fiber_in_other_thread,
+                    t1,
+                    g_main_loop_get_context (loop1),
+                    G_PRIORITY_DEFAULT,
+                    NULL,
+                    fiber_in_other_thread_cb,
+                    g_thread_self ());
+
+  g_main_loop_run (loop);
+
+  /* wait for other thread to complete */
+  while (loop1 != NULL)
+    g_main_context_iteration (NULL, FALSE);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gpointer
+cancellation_fiber (GFiber        *fiber,
+                    gpointer       data,
+                    GCancellable  *cancellable,
+                    GError       **error)
+{
+  GFile *file;
+  gpointer ret;
+
+  ret = GUINT_TO_POINTER (1);
+  g_fiber_sleep (fiber, 100);
+  if (g_cancellable_is_cancelled (cancellable))
+    goto out;
+
+  g_fiber_sleep (fiber, 100);
+
+  ret = GUINT_TO_POINTER (2);
+  file = g_file_new_for_path ("/path/that/does/not/exist");
+  g_file_query_info_async (file,
+                           "standard::*",
+                           G_FILE_QUERY_INFO_NONE,
+                           G_PRIORITY_DEFAULT,
+                           cancellable,
+                           g_fiber_get_async_ready_callback (fiber),
+                           g_fiber_get_async_ready_user_data (fiber));
+  g_fiber_yield (fiber);
+  g_assert (g_file_query_info_finish (file,
+                                      g_fiber_get_async_result (fiber),
+                                      error) == NULL);
+  g_object_unref (file);
+
+ out:
+  return ret;
+}
+
+static void
+cancellation_fiber_cb_expect_cancelled (GObject       *source_object,
+                                        GAsyncResult  *res,
+                                        gpointer       user_data)
+{
+  GError *error;
+  gpointer result;
+  gpointer expected_result = user_data;
+
+  error = NULL;
+  result = g_fiber_run_finish (G_FIBER (source_object),
+                               res,
+                               &error);
+  g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+  g_error_free (error);
+  g_assert_cmpint (GPOINTER_TO_UINT (result), ==, GPOINTER_TO_UINT (expected_result));
+
+  g_main_loop_quit (loop);
+}
+
+static gboolean
+do_cancel (gpointer user_data)
+{
+  GCancellable *c = G_CANCELLABLE (user_data);
+  g_cancellable_cancel (c);
+  return FALSE;
+}
+
+static void
+cancellation (void)
+{
+  GCancellable *c;
+
+  /* first check with passing in a cancelled GCancellable */
+  c = g_cancellable_new ();
+  g_cancellable_cancel (c);
+  g_fiber_run (cancellation_fiber,
+               NULL,
+               c,
+               cancellation_fiber_cb_expect_cancelled,
+               GUINT_TO_POINTER (0));
+  g_main_loop_run (loop);
+  g_object_unref (c);
+
+  /* then check that we intercept the cancel via g_cancellable_is_cancelled */
+  c = g_cancellable_new ();
+  g_fiber_run (cancellation_fiber,
+               NULL,
+               c,
+               cancellation_fiber_cb_expect_cancelled,
+               GUINT_TO_POINTER (1));
+  g_timeout_add (50, do_cancel, c);
+  g_main_loop_run (loop);
+  g_object_unref (c);
+
+  /* then check that async functions react correctly to cancellable */
+  c = g_cancellable_new ();
+  g_fiber_run (cancellation_fiber,
+               NULL,
+               c,
+               cancellation_fiber_cb_expect_cancelled,
+               GUINT_TO_POINTER (2));
+  g_timeout_add (150, do_cancel, c);
+  g_main_loop_run (loop);
+  g_object_unref (c);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+  gchar *file_name1;
+  gchar *file_name2;
+} TwiceData;
+
+static gpointer
+read_file_twice_in_fiber (GFiber        *fiber,
+                          gpointer       _data,
+                          GCancellable  *cancellable,
+                          GError       **error)
+{
+  TwiceData *data = _data;
+  GFile *file1;
+  GFile *file2;
+  gchar *contents1;
+  gchar *contents2;
+  guint op1;
+  guint op2;
+  guint num_pending;
+
+  file1 = g_file_new_for_commandline_arg (data->file_name1);
+  file2 = g_file_new_for_commandline_arg (data->file_name2);
+
+  op1 = g_fiber_get_op_id (fiber);
+  g_file_load_contents_async (file1,
+                              cancellable,
+                              g_fiber_get_async_ready_callback (fiber),
+                              g_fiber_get_async_ready_user_data (fiber));
+
+  op2 = g_fiber_get_op_id (fiber);
+  g_file_load_contents_async (file2,
+                              cancellable,
+                              g_fiber_get_async_ready_callback (fiber),
+                              g_fiber_get_async_ready_user_data (fiber));
+
+  num_pending = 2;
+  while (num_pending > 0)
+    {
+      gint op;
+
+      op = g_fiber_yield (fiber);
+
+      if (op == op1)
+        {
+          g_assert (g_file_load_contents_finish (file1,
+                                                 g_fiber_get_async_result (fiber),
+                                                 &contents1,
+                                                 NULL,   /* length */
+                                                 NULL,   /* etag_out */
+                                                 NULL)); /* error */
+          num_pending--;
+        }
+      else if (op == op2)
+        {
+          g_assert (g_file_load_contents_finish (file2,
+                                                 g_fiber_get_async_result (fiber),
+                                                 &contents2,
+                                                 NULL,   /* length */
+                                                 NULL,   /* etag_out */
+                                                 NULL)); /* error */
+          num_pending--;
+        }
+      else
+        {
+          g_assert_not_reached ();
+        }
+    }
+
+  g_assert_cmpstr (contents1, ==, "xyz");
+  g_assert_cmpstr (contents2, ==, "123");
+
+  g_free (contents1);
+  g_free (contents2);
+
+  g_object_unref (file1);
+  g_object_unref (file2);
+
+  g_main_loop_quit (loop);
+  return NULL;
+}
+
+static void
+multiple_parallel_requests (void)
+{
+  TwiceData data;
+  const gchar *test_data_contents1 = "xyz";
+  const gchar *test_data_contents2 = "123";
+  int fd;
+
+  fd = g_file_open_tmp ("fiber_XXXXXX",
+                        &data.file_name1, NULL);
+  g_assert (fd != -1);
+  close (fd);
+  g_assert (g_file_set_contents (data.file_name1,
+                                 test_data_contents1, -1, NULL));
+
+  fd = g_file_open_tmp ("fiber_XXXXXX",
+                        &data.file_name2, NULL);
+  g_assert (fd != -1);
+  close (fd);
+  g_assert (g_file_set_contents (data.file_name2,
+                                 test_data_contents2, -1, NULL));
+
+  /* loads both files in parallel */
+  g_fiber_run (read_file_twice_in_fiber,
+               &data,
+               NULL,
+               NULL,
+               NULL);
+
+  g_main_loop_run (loop);
+
+  g_unlink (data.file_name1);
+  g_free (data.file_name1);
+
+  g_unlink (data.file_name2);
+  g_free (data.file_name2);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GStaticPrivate my_tls = G_STATIC_PRIVATE_INIT;
+
+static gpointer
+tls_fiber (GFiber        *fiber,
+           gpointer       data,
+           GCancellable  *cancellable,
+           GError       **error)
+{
+  g_assert_cmpint (42, !=, GPOINTER_TO_UINT (g_static_private_get (&my_tls)));
+
+  g_main_loop_quit (loop);
+  return NULL;
+}
+
+static void
+thread_local_storage (void)
+{
+  g_static_private_set (&my_tls, GUINT_TO_POINTER (42), NULL);
+  g_assert_cmpint (42, ==, GPOINTER_TO_UINT (g_static_private_get (&my_tls)));
+
+  /* checks that fibers in the same threads accesses different thread local storage (since they are threads) */
+  g_fiber_run (tls_fiber,
+               NULL,
+               NULL,
+               NULL,
+               NULL);
+
+  g_main_loop_run (loop);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gpointer
+gc_fib_func2 (GFiber        *fiber,
+              gpointer       data,
+              GCancellable  *cancellable,
+              GError       **error)
+{
+  GFiber *fiber_that_started_us = G_FIBER (data);
+
+  g_assert (g_fiber_get_current () == fiber);
+  g_assert (fiber_that_started_us != NULL);
+  g_assert (g_fiber_get_current () != fiber_that_started_us);
+
+  return NULL;
+}
+
+static gpointer
+gc_fib_func (GFiber        *fiber,
+             gpointer       data,
+             GCancellable  *cancellable,
+             GError       **error)
+{
+  g_assert (g_fiber_get_current () == fiber);
+
+  /* start a fiber from within the fiber */
+  g_fiber_run (gc_fib_func2,
+               fiber,
+               cancellable,
+               g_fiber_get_async_ready_callback (fiber),
+               g_fiber_get_async_ready_user_data (fiber));
+
+  /* this waits until the newly created fiber terminates */
+  g_fiber_yield (fiber);
+
+  /* ignore result (e.g. we could call g_fiber_run_finish() but that is optional) */
+
+  /* check we get the right thing after our fiber resumes */
+  g_assert (g_fiber_get_current () == fiber);
+
+  g_main_loop_quit (loop);
+  return NULL;
+}
+
+/* checks that g_fiber_get_current() works as expected - also tests fibers started by fibers */
+static void
+get_current (void)
+{
+  g_assert (g_fiber_get_current () == NULL);
+
+  g_fiber_run (gc_fib_func,
+               NULL,
+               NULL,
+               NULL,
+               NULL);
+
+  g_assert (g_fiber_get_current () == NULL);
+
+  g_main_loop_run (loop);
+
+  g_assert (g_fiber_get_current () == NULL);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+int
+main (int argc, char **argv)
+{
+  int ret;
+
+  g_thread_init (NULL);
+  g_type_init ();
+  g_test_init (&argc, &argv, NULL);
+
+  loop = g_main_loop_new (NULL, 0);
+
+  g_test_add_func ("/fibers/load_file", load_file);
+  g_test_add_func ("/fibers/threads", threads);
+  g_test_add_func ("/fibers/cancellation", cancellation);
+  g_test_add_func ("/fibers/multiple_parallel_requests", multiple_parallel_requests);
+  g_test_add_func ("/fibers/thread_local_storage", thread_local_storage);
+  g_test_add_func ("/fibers/get_current", get_current);
+
+  ret = g_test_run();
+
+  g_main_loop_unref (loop);
+
+  return ret;
+}
