[glib/wip/gsource3: 5/7] gsource: Add support for file desciptors on UNIX



commit 2666d34ff636d2bf4ec6be8fd8180ebe16491f6c
Author: Ryan Lortie <desrt desrt ca>
Date:   Mon Oct 29 18:54:22 2012 +0100

    gsource: Add support for file desciptors on UNIX
    
    Adding file descriptors to a GSource provides similar functionality to
    the old g_source_add_poll() API with two main differences.
    
    First: the list of handles is managed internally and therefore users are
    prevented from randomly modifying the ->events field.  This prepares us
    for an epoll future where changing the event mask is a syscall.
    
    Second: keeping the list internally allows us to check the ->revents for
    events for ourselves, allowing the source to skip implementing
    check/prepare.  This also prepares us for the future by allowing an
    implementation that doesn't need to iterate over all of the sources
    every time.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=686853

 docs/reference/glib/glib-sections.txt |    6 +
 glib/glib.symbols                     |    6 +
 glib/gmain.c                          |  321 ++++++++++++++++++++++++++++++++-
 glib/gmain.h                          |   22 +++
 4 files changed, 354 insertions(+), 1 deletions(-)
---
diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt
index e12a855..d5a70b2 100644
--- a/docs/reference/glib/glib-sections.txt
+++ b/docs/reference/glib/glib-sections.txt
@@ -528,6 +528,12 @@ g_source_get_context
 g_source_set_callback
 GSourceFunc
 g_source_set_callback_indirect
+g_source_set_ready_time
+g_source_get_ready_time
+g_source_add_unix_fd
+g_source_remove_unix_fd
+g_source_modify_unix_fd
+g_source_query_unix_fd
 g_source_add_poll
 g_source_remove_poll
 g_source_add_child_source
diff --git a/glib/glib.symbols b/glib/glib.symbols
index 724e8d9..b317e7c 100644
--- a/glib/glib.symbols
+++ b/glib/glib.symbols
@@ -628,6 +628,7 @@ g_main_loop_ref
 g_main_loop_run
 g_main_loop_unref
 g_source_add_child_source
+g_source_add_unix_fd
 g_source_add_poll
 g_source_attach
 g_source_destroy
@@ -638,12 +639,16 @@ g_source_get_current_time
 g_source_get_id
 g_source_get_name
 g_source_get_priority
+g_source_get_ready_time
+g_source_modify_unix_fd
 g_source_new
+g_source_query_unix_fd
 g_source_ref
 g_source_remove
 g_source_remove_by_funcs_user_data
 g_source_remove_by_user_data
 g_source_remove_child_source
+g_source_remove_unix_fd
 g_source_remove_poll
 g_source_set_callback
 g_source_set_callback_indirect
@@ -653,6 +658,7 @@ g_source_set_name
 g_source_set_name_by_id
 g_source_is_destroyed
 g_source_set_priority
+g_source_set_ready_time
 g_source_unref
 g_idle_add
 g_idle_add_full
diff --git a/glib/gmain.c b/glib/gmain.c
index 7dce503..ff271d5 100644
--- a/glib/gmain.c
+++ b/glib/gmain.c
@@ -316,6 +316,13 @@ struct _GSourcePrivate
 {
   GSList *child_sources;
   GSource *parent_source;
+
+  gint64 ready_time;
+
+  /* This is currently only used on UNIX, but we always declare it (and
+   * let it remain empty on Windows) to avoid #ifdef all over the place.
+   */
+  GSList *fds;
 };
 
 typedef struct _GSourceIter
@@ -849,6 +856,8 @@ g_source_new (GSourceFuncs *source_funcs,
 
   source->flags = G_HOOK_FLAG_ACTIVE;
 
+  source->priv->ready_time = -1;
+
   /* NULL/0 initialization for all other fields */
   
   return source;
@@ -1112,6 +1121,9 @@ g_source_attach_unlocked (GSource      *source,
       tmp_list = tmp_list->next;
     }
 
+  for (tmp_list = source->priv->fds; tmp_list; tmp_list = tmp_list->next)
+    g_main_context_add_poll_unlocked (context, source->priority, tmp_list->data);
+
   tmp_list = source->priv->child_sources;
   while (tmp_list)
     {
@@ -1197,6 +1209,9 @@ g_source_destroy_internal (GSource      *source,
 	      g_main_context_remove_poll_unlocked (context, tmp_list->data);
 	      tmp_list = tmp_list->next;
 	    }
+
+          for (tmp_list = source->priv->fds; tmp_list; tmp_list = tmp_list->next)
+            g_main_context_remove_poll_unlocked (context, tmp_list->data);
 	}
 
       while (source->priv->child_sources)
@@ -1636,6 +1651,12 @@ g_source_set_priority_unlocked (GSource      *source,
 	      
 	      tmp_list = tmp_list->next;
 	    }
+
+          for (tmp_list = source->priv->fds; tmp_list; tmp_list = tmp_list->next)
+            {
+              g_main_context_remove_poll_unlocked (context, tmp_list->data);
+              g_main_context_add_poll_unlocked (context, priority, tmp_list->data);
+            }
 	}
     }
 
@@ -1694,6 +1715,70 @@ g_source_get_priority (GSource *source)
 }
 
 /**
+ * g_source_set_ready_time:
+ * @source: a #GSource
+ * @ready_time: the monotonic time at which the source will be ready,
+ *              0 for "immediately", -1 for "never"
+ *
+ * Sets a #GSource to be dispatched when the given monotonic time is
+ * reached (or passed).  If the monotonic time is in the past (as it
+ * always will be if @ready_time is 0) then the source will be
+ * dispatched immediately.
+ *
+ * If @ready_time is -1 then the source is never woken up on the basis
+ * of the passage of time.
+ *
+ * Dispatching the source does not reset the ready time.  You should do
+ * so yourself, from the source dispatch function.
+ *
+ * Since: 2.36
+ **/
+void
+g_source_set_ready_time (GSource *source,
+                         gint64   ready_time)
+{
+  GMainContext *context;
+
+  g_return_if_fail (source != NULL);
+  g_return_if_fail (source->ref_count > 0);
+
+  if (source->priv->ready_time == ready_time)
+    return;
+
+  context = source->context;
+
+  if (context)
+    LOCK_CONTEXT (context);
+
+  source->priv->ready_time = ready_time;
+
+  if (context)
+    {
+      /* Quite likely that we need to change the timeout on the poll */
+      if (!SOURCE_BLOCKED (source))
+        g_wakeup_signal (context->wakeup);
+      UNLOCK_CONTEXT (context);
+    }
+}
+
+/**
+ * g_source_get_ready_time:
+ * @source: a #GSource
+ *
+ * Gets the "ready time" of @source, as set by
+ * g_source_set_ready_time().
+ *
+ * Returns: the monotonic ready time, 0 for "immediately", -1 for "never"
+ **/
+gint64
+g_source_get_ready_time (GSource *source)
+{
+  g_return_val_if_fail (source != NULL, -1);
+
+  return source->priv->ready_time;
+}
+
+/**
  * g_source_set_can_recurse:
  * @source: a #GSource
  * @can_recurse: whether recursion is allowed for this source
@@ -1898,6 +1983,8 @@ g_source_unref_internal (GSource      *source,
       g_slist_free (source->poll_fds);
       source->poll_fds = NULL;
 
+      g_slist_free_full (source->priv->fds, g_free);
+
       g_slice_free (GSourcePrivate, source->priv);
       source->priv = NULL;
 
@@ -2151,6 +2238,174 @@ g_source_remove_by_funcs_user_data (GSourceFuncs *funcs,
     return FALSE;
 }
 
+#ifdef G_OS_UNIX
+/**
+ * g_source_add_unix_fd:
+ * @source: a #GSource
+ * @fd: the fd to monitor
+ * @events: an event mask
+ *
+ * Monitors @fd for the IO events in @events.
+ *
+ * The tag returned by this function can be used to remove or modify the
+ * monitoring of the fd using g_source_remove_unix_fd() or
+ * g_source_modify_unix_fd().
+ *
+ * It is not necessary to remove the fd before destroying the source; it
+ * will be cleaned up automatically.
+ *
+ * As the name suggests, this function is not available on Windows.
+ *
+ * Returns: an opaque tag
+ *
+ * Since: 2.36
+ **/
+gpointer
+g_source_add_unix_fd (GSource      *source,
+                      gint          fd,
+                      GIOCondition  events)
+{
+  GMainContext *context;
+  GPollFD *poll_fd;
+
+  g_return_val_if_fail (source != NULL, NULL);
+  g_return_val_if_fail (!SOURCE_DESTROYED (source), NULL);
+
+  poll_fd = g_new (GPollFD, 1);
+  poll_fd->fd = fd;
+  poll_fd->events = events;
+  poll_fd->revents = 0;
+
+  context = source->context;
+
+  if (context)
+    LOCK_CONTEXT (context);
+
+  source->priv->fds = g_slist_prepend (source->priv->fds, poll_fd);
+
+  if (context)
+    {
+      if (!SOURCE_BLOCKED (source))
+        g_main_context_add_poll_unlocked (context, source->priority, poll_fd);
+      UNLOCK_CONTEXT (context);
+    }
+
+  return poll_fd;
+}
+
+/**
+ * g_source_modify_unix_fd:
+ * @source: a #GSource
+ * @tag: the tag from g_source_add_unix_fd()
+ * @new_events: the new event mask to watch
+ *
+ * Updates the event mask to watch for the fd identified by @tag.
+ *
+ * @tag is the tag returned from g_source_add_unix_fd().
+ *
+ * If you want to remove a fd, don't set its event mask to zero.
+ * Instead, call g_source_remove_unix_fd().
+ *
+ * As the name suggests, this function is not available on Windows.
+ *
+ * Since: 2.36
+ **/
+void
+g_source_modify_unix_fd (GSource      *source,
+                         gpointer      tag,
+                         GIOCondition  new_events)
+{
+  GMainContext *context;
+  GPollFD *poll_fd;
+
+  g_return_if_fail (source != NULL);
+  g_return_if_fail (g_slist_find (source->priv->fds, tag));
+
+  context = source->context;
+  poll_fd = tag;
+
+  poll_fd->events = new_events;
+
+  if (context)
+    g_main_context_wakeup (context);
+}
+
+/**
+ * g_source_remove_unix_fd:
+ * @source: a #GSource
+ * @tag: the tag from g_source_add_unix_fd()
+ *
+ * Reverses the effect of a previous call to g_source_add_unix_fd().
+ *
+ * You only need to call this if you want to remove an fd from being
+ * watched while keeping the same source around.  In the normal case you
+ * will just want to destroy the source.
+ *
+ * As the name suggests, this function is not available on Windows.
+ *
+ * Since: 2.36
+ **/
+void
+g_source_remove_unix_fd (GSource  *source,
+                         gpointer  tag)
+{
+  GMainContext *context;
+  GPollFD *poll_fd;
+
+  g_return_if_fail (source != NULL);
+  g_return_if_fail (g_slist_find (source->priv->fds, tag));
+
+  context = source->context;
+  poll_fd = tag;
+
+  if (context)
+    LOCK_CONTEXT (context);
+
+  source->priv->fds = g_slist_remove (source->priv->fds, poll_fd);
+
+  if (context)
+    {
+      if (!SOURCE_BLOCKED (source))
+        g_main_context_remove_poll_unlocked (context, poll_fd);
+
+      UNLOCK_CONTEXT (context);
+    }
+
+  g_free (poll_fd);
+}
+
+/**
+ * g_source_query_unix_fd:
+ * @source: a #GSource
+ * @tag: the tag from g_source_add_unix_fd()
+ *
+ * Queries the events reported for the fd corresponding to @tag on
+ * @source during the last poll.
+ *
+ * The return value of this function is only defined when the function
+ * is called from the check or dispatch functions for @source.
+ *
+ * As the name suggests, this function is not available on Windows.
+ *
+ * Returns: the conditions reported on the fd
+ *
+ * Since: 2.36
+ **/
+GIOCondition
+g_source_query_unix_fd (GSource  *source,
+                        gpointer  tag)
+{
+  GPollFD *poll_fd;
+
+  g_return_val_if_fail (source != NULL, 0);
+  g_return_val_if_fail (g_slist_find (source->priv->fds, tag), 0);
+
+  poll_fd = tag;
+
+  return poll_fd->revents;
+}
+#endif /* G_OS_UNIX */
+
 /**
  * g_get_current_time:
  * @result: #GTimeVal structure in which to store current time.
@@ -2676,6 +2931,9 @@ block_source (GSource *source)
       tmp_list = tmp_list->next;
     }
 
+  for (tmp_list = source->priv->fds; tmp_list; tmp_list = tmp_list->next)
+    g_main_context_remove_poll_unlocked (source->context, tmp_list->data);
+
   if (source->priv && source->priv->child_sources)
     {
       tmp_list = source->priv->child_sources;
@@ -2692,7 +2950,7 @@ static void
 unblock_source (GSource *source)
 {
   GSList *tmp_list;
-  
+
   g_return_if_fail (SOURCE_BLOCKED (source)); /* Source already unblocked */
   g_return_if_fail (!SOURCE_DESTROYED (source));
   
@@ -2705,6 +2963,9 @@ unblock_source (GSource *source)
       tmp_list = tmp_list->next;
     }
 
+  for (tmp_list = source->priv->fds; tmp_list; tmp_list = tmp_list->next)
+    g_main_context_add_poll_unlocked (source->context, source->priority, tmp_list->data);
+
   if (source->priv && source->priv->child_sources)
     {
       tmp_list = source->priv->child_sources;
@@ -3062,6 +3323,31 @@ g_main_context_prepare (GMainContext *context,
               result = FALSE;
             }
 
+          if (result == FALSE && source->priv->ready_time != -1)
+            {
+              if (!context->time_is_fresh)
+                {
+                  context->time = g_get_monotonic_time ();
+                  context->time_is_fresh = TRUE;
+                }
+
+              if (source->priv->ready_time <= context->time)
+                {
+                  source_timeout = 0;
+                  result = TRUE;
+                }
+              else
+                {
+                  gint timeout;
+
+                  /* rounding down will lead to spinning, so always round up */
+                  timeout = (source->priv->ready_time - context->time + 999) / 1000;
+
+                  if (source_timeout < 0 || timeout < source_timeout)
+                    source_timeout = timeout;
+                }
+            }
+
 	  if (result)
 	    {
 	      GSource *ready_source = source;
@@ -3239,6 +3525,7 @@ g_main_context_check (GMainContext *context,
 
           if (check)
             {
+              /* If the check function is set, call it. */
               context->in_check_or_prepare++;
               UNLOCK_CONTEXT (context);
 
@@ -3250,6 +3537,38 @@ g_main_context_check (GMainContext *context,
           else
             result = FALSE;
 
+          if (result == FALSE)
+            {
+              GSList *tmp_list;
+
+              /* If not already explicitly flagged ready by ->check()
+               * (or if we have no check) then we can still be ready if
+               * any of our fds poll as ready.
+               */
+              for (tmp_list = source->priv->fds; tmp_list; tmp_list = tmp_list->next)
+                {
+                  GPollFD *pollfd = tmp_list->data;
+
+                  if (pollfd->revents)
+                    {
+                      result = TRUE;
+                      break;
+                    }
+                }
+            }
+
+          if (result == FALSE && source->priv->ready_time != -1)
+            {
+              if (!context->time_is_fresh)
+                {
+                  context->time = g_get_monotonic_time ();
+                  context->time_is_fresh = TRUE;
+                }
+
+              if (source->priv->ready_time <= context->time)
+                result = TRUE;
+            }
+
 	  if (result)
 	    {
 	      GSource *ready_source = source;
diff --git a/glib/gmain.h b/glib/gmain.h
index 04cf2f8..9fc5d9f 100644
--- a/glib/gmain.h
+++ b/glib/gmain.h
@@ -462,6 +462,28 @@ GLIB_AVAILABLE_IN_ALL
 void                 g_source_set_name_by_id (guint           tag,
                                               const char     *name);
 
+GLIB_AVAILABLE_IN_2_36
+void                 g_source_set_ready_time (GSource        *source,
+                                              gint64          ready_time);
+GLIB_AVAILABLE_IN_2_36
+gint64               g_source_get_ready_time (GSource        *source);
+
+#ifdef G_OS_UNIX
+GLIB_AVAILABLE_IN_2_36
+gpointer             g_source_add_unix_fd    (GSource        *source,
+                                              gint            fd,
+                                              GIOCondition    events);
+GLIB_AVAILABLE_IN_2_36
+void                 g_source_modify_unix_fd (GSource        *source,
+                                              gpointer        tag,
+                                              GIOCondition    new_events);
+GLIB_AVAILABLE_IN_2_36
+void                 g_source_remove_unix_fd (GSource        *source,
+                                              gpointer        tag);
+GLIB_AVAILABLE_IN_2_36
+GIOCondition         g_source_query_unix_fd  (GSource        *source,
+                                              gpointer        tag);
+#endif
 
 /* Used to implement g_source_connect_closure and internally*/
 GLIB_AVAILABLE_IN_ALL



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]