gtk+ r21783 - in trunk: . gdk gdk/quartz



Author: rhult
Date: Wed Nov 12 13:30:22 2008
New Revision: 21783
URL: http://svn.gnome.org/viewvc/gtk+?rev=21783&view=rev

Log:
2008-11-12  Richard Hult  <richard imendio com>

	Bug 550942 â [patch] Rework of gdkeventloop-quartz.c

	* gdk/gdk.c:
	* gdk/gdkinternals.h: Add eventloop debug facility.
	* gdk/quartz/gdkeventloop-quartz.c: Big rework of the quartz
	mainloop integration, patch from Owen Taylor. See bug #550942 for
	the details.


Modified:
   trunk/ChangeLog
   trunk/gdk/gdk.c
   trunk/gdk/gdkinternals.h
   trunk/gdk/quartz/gdkeventloop-quartz.c

Modified: trunk/gdk/gdk.c
==============================================================================
--- trunk/gdk/gdk.c	(original)
+++ trunk/gdk/gdk.c	Wed Nov 12 13:30:22 2008
@@ -80,7 +80,8 @@
   {"cursor",	    GDK_DEBUG_CURSOR},
   {"multihead",	    GDK_DEBUG_MULTIHEAD},
   {"xinerama",	    GDK_DEBUG_XINERAMA},
-  {"draw",	    GDK_DEBUG_DRAW}
+  {"draw",	    GDK_DEBUG_DRAW},
+  {"eventloop",	    GDK_DEBUG_EVENTLOOP}
 };
 
 static const int gdk_ndebug_keys = G_N_ELEMENTS (gdk_debug_keys);

Modified: trunk/gdk/gdkinternals.h
==============================================================================
--- trunk/gdk/gdkinternals.h	(original)
+++ trunk/gdk/gdkinternals.h	Wed Nov 12 13:30:22 2008
@@ -85,7 +85,8 @@
   GDK_DEBUG_CURSOR	  = 1 <<11,
   GDK_DEBUG_MULTIHEAD	  = 1 <<12,
   GDK_DEBUG_XINERAMA	  = 1 <<13,
-  GDK_DEBUG_DRAW	  = 1 <<14
+  GDK_DEBUG_DRAW	  = 1 <<14,
+  GDK_DEBUG_EVENTLOOP     = 1 <<15
 } GdkDebugFlag;
 
 #ifndef GDK_DISABLE_DEPRECATED

Modified: trunk/gdk/quartz/gdkeventloop-quartz.c
==============================================================================
--- trunk/gdk/quartz/gdkeventloop-quartz.c	(original)
+++ trunk/gdk/quartz/gdkeventloop-quartz.c	Wed Nov 12 13:30:22 2008
@@ -8,22 +8,582 @@
 
 #include "gdkprivate-quartz.h"
 
+/* 
+ * This file implementations integration between the GLib main loop and
+ * the native system of the Core Foundation run loop and Cocoa event
+ * handling. There are basically two different cases that we need to
+ * handle: either the GLib main loop is in control (the application
+ * has called gtk_main(), or is otherwise iterating the main loop), or
+ * CFRunLoop is in control (we are in a modal operation such as window
+ * resizing or drag-and-drop.)
+ *
+ * When the GLib main loop is in control we integrate in native event
+ * handling in two ways: first we add a GSource that handles checking
+ * whether there are native events available, translating native events
+ * to GDK events, and dispatching GDK events. Second we replace the
+ * "poll function" of the GLib main loop with our own version that knows
+ * how to wait for both the file descriptors and timeouts that GLib is
+ * interested in and also for incoming native events.
+ *
+ * When CFRunLoop is in control, we integrate in GLib main loop handling
+ * by adding a "run loop observer" that gives us notification at various
+ * points in the run loop cycle. We map these points onto the corresponding
+ * stages of the GLib main loop (prepare, check, dispatch), and make the
+ * appropriate calls into GLib.
+ *
+ * Both cases share a single problem: the OS X API's don't allow us to
+ * wait simultaneously for file descriptors and for events. So when we
+ * need to do a blocking wait that includes file descriptor activity, we
+ * push the actual work of calling select() to a helper thread (the
+ * "select thread") and wait for native events in the main thread.
+ *
+ * The main known limitation of this code is that if a callback is triggered
+ * via the OS X run loop while we are "polling" (in either case described
+ * above), iteration of the GLib main loop is not possible from within
+ * that callback. If the programmer tries to do so explicitly, then they
+ * will get a warning from GLib "main loop already active in another thread".
+ */
+
+/******* State for run loop iteration *******/
+
+/* Count of number of times we've gotten an "Entry" notification for
+ * our run loop observer.
+ */
+static int current_loop_level = 0;
+
+/* Run loop level at which we acquired ownership of the GLib main
+ * loop. See note in run_loop_entry(). -1 means that we don't have
+ * ownership
+ */ 
+static int acquired_loop_level = -1;
+
+/* Between run_loop_before_waiting() and run_loop_after_waiting();
+ * whether we we need to call select_thread_collect_poll()
+ */
+static gboolean run_loop_polling_async = FALSE;
+
+/* Between run_loop_before_waiting() and run_loop_after_waiting();
+ * max_prioritiy to pass to g_main_loop_check()
+ */
+static gint run_loop_max_priority;
+
+/* Timer that we've added to wake up the run loop when a GLib timeout
+ */
+static CFRunLoopTimerRef run_loop_timer = NULL;
+
+/* These are the file descriptors that are we are polling out of
+ * the run loop. (We keep the array around and reuse it to avoid
+ * constant allocations.)
+ */
+#define RUN_LOOP_POLLFDS_INITIAL_SIZE 16
+static GPollFD *run_loop_pollfds;
+static guint run_loop_pollfds_size; /* Allocated size of the array */
+static guint run_loop_n_pollfds;    /* Number of file descriptors in the array */
+
+/******* Other global variables *******/
+
+/* Since we count on replacing the GLib main loop poll function as our
+ * method of integrating Cocoa event handling into the GLib main loop
+ * we need to make sure that the poll function is always called even
+ * when there are no file descriptors that need to be polled. To do
+ * this, we add a dummy GPollFD to our event source with a file
+ * descriptor of '-1'. Then any time that GLib is polling the event
+ * source, it will call our poll function.
+ */
 static GPollFD event_poll_fd;
+
+/* Current NSEvents that we've gotten from Cocoa but haven't yet converted
+ * to GdkEvents. We wait until our dispatch() function to do the conversion
+ * since the conversion can conceivably cause signals to be emmitted
+ * or other things that shouldn't happen inside a poll function.
+ */
 static GQueue *current_events;
 
+/* The default poll function for GLib; we replace this with our own
+ * Cocoa-aware version and then call the old version to do actual
+ * file descriptor polling. There's no actual need to chain to the
+ * old one; we could reimplement the same functionality from scratch,
+ * but since the default implementation does the right thing, why
+ * bother.
+ */
 static GPollFunc old_poll_func;
 
-static gboolean select_fd_waiting = FALSE, ready_for_poll = FALSE;
-static pthread_t select_thread = 0;
-static int wakeup_pipe[2];
-static pthread_mutex_t pollfd_mutex = PTHREAD_MUTEX_INITIALIZER;
-static pthread_cond_t ready_cond = PTHREAD_COND_INITIALIZER;
-static GPollFD *pollfds;
-static guint n_pollfds;
-static CFRunLoopSourceRef select_main_thread_source;
+/* Reference to the run loop of the main thread. (There is a unique
+ * CFRunLoop per thread.)
+ */
 static CFRunLoopRef main_thread_run_loop;
+
+/* Normally the Cocoa main loop maintains an NSAutoReleasePool and frees
+ * it on every iteration. Since we are replacing the main loop we have
+ * to provide this functionality ourself. We free and replace the
+ * auto-release pool in our sources prepare() function.
+ */
 static NSAutoreleasePool *autorelease_pool;
 
+/* Flag when we've called nextEventMatchingMask ourself; this triggers
+ * a run loop iteration, so we need to detect that and avoid triggering
+ * our "run the GLib main looop while the run loop is active machinery.
+ */
+static gboolean getting_events;
+
+/************************************************************
+ *********              Select Thread               *********
+ ************************************************************/
+
+/* The states in our state machine, see comments in select_thread_func()
+ * for descriptiions of each state
+ */
+typedef enum {
+  BEFORE_START,
+  WAITING,
+  POLLING_QUEUED,
+  POLLING_RESTART,
+  POLLING_DESCRIPTORS,
+} SelectThreadState;
+
+#ifdef G_ENABLE_DEBUG
+static const char *const state_names[]  = {
+  "BEFORE_START",
+  "WAITING",
+  "POLLING_QUEUED",
+  "POLLING_RESTART",
+  "POLLING_DESCRIPTORS"
+};
+#endif
+
+static SelectThreadState select_thread_state = BEFORE_START;
+
+static pthread_t select_thread;
+static pthread_mutex_t select_thread_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t select_thread_cond = PTHREAD_COND_INITIALIZER;
+
+#define SELECT_THREAD_LOCK() pthread_mutex_lock (&select_thread_mutex)
+#define SELECT_THREAD_UNLOCK() pthread_mutex_unlock (&select_thread_mutex)
+#define SELECT_THREAD_SIGNAL() pthread_cond_signal (&select_thread_cond)
+#define SELECT_THREAD_WAIT() pthread_cond_wait (&select_thread_cond, &select_thread_mutex)
+
+/* These are the file descriptors that the select thread is currently
+ * polling.
+ */
+static GPollFD *current_pollfds;
+static guint current_n_pollfds;
+
+/* These are the file descriptors that the select thread should pick
+ * up and start polling when it has a chance.
+ */
+static GPollFD *next_pollfds;
+static guint next_n_pollfds;
+
+/* Pipe used to wake up the select thread */
+static gint select_thread_wakeup_pipe[2];
+
+/* Run loop source used to wake up the main thread */
+static CFRunLoopSourceRef select_main_thread_source;
+
+static void
+select_thread_set_state (SelectThreadState new_state)
+{
+  gboolean old_state;
+
+  if (select_thread_state == new_state)
+    return;
+
+  GDK_NOTE (EVENTLOOP, g_print ("EventLoop: Select thread state: %s => %s\n", state_names[select_thread_state], state_names[new_state]));
+
+  old_state = select_thread_state;
+  select_thread_state = new_state;
+  if (old_state == WAITING && new_state != WAITING)
+    SELECT_THREAD_SIGNAL ();
+}
+
+static void
+signal_main_thread (void)
+{
+  GDK_NOTE (EVENTLOOP, g_print ("EventLoop: Waking up main thread\n"));
+
+  /* If we are in nextEventMatchingMask, then we need to make sure an
+   * event gets queued, otherwise it's enough to simply wake up the
+   * main thread run loop
+   */
+  if (!run_loop_polling_async)
+    CFRunLoopSourceSignal (select_main_thread_source);
+  
+  if (CFRunLoopIsWaiting (main_thread_run_loop))
+    CFRunLoopWakeUp (main_thread_run_loop);
+}
+
+static void *
+select_thread_func (void *arg)
+{
+  char c;
+  
+  SELECT_THREAD_LOCK ();
+
+  while (TRUE)
+    {
+      switch (select_thread_state)
+	{
+	case BEFORE_START:
+	  /* The select thread has not been started yet
+	   */
+	  g_assert_not_reached ();
+	  
+	case WAITING:
+	  /* Waiting for a set of file descriptors to be submitted by the main thread
+	   *
+	   *  => POLLING_QUEUED: main thread thread submits a set of file descriptors
+	   */ 
+	  SELECT_THREAD_WAIT ();
+	  break;
+	  
+	case POLLING_QUEUED:
+	  /* Waiting for a set of file descriptors to be submitted by the main thread
+	   *
+	   *  => POLLING_DESCRIPTORS: select thread picks up the file descriptors to begin polling
+	   */ 
+	  if (current_pollfds)
+	    g_free (current_pollfds);
+	  
+	  current_pollfds = next_pollfds;
+	  current_n_pollfds = next_n_pollfds;
+
+	  next_pollfds = NULL;
+	  next_n_pollfds = 0;
+
+	  select_thread_set_state (POLLING_DESCRIPTORS);
+	  break;
+	  
+	case POLLING_RESTART:
+	  /* Select thread is currently polling a set of file descriptors, main thread has
+	   * began a new iteration with the same set of file descriptors. We don't want to
+	   * wake the select thread up and wait for it to restart immediately, but to avoid
+	   * a race (described below in select_thread_start_polling()) we need to recheck after
+	   * polling completes.
+	   *
+	   * => POLLING_DESCRIPTORS: select completes, main thread rechecks by polling again
+	   * => POLLING_QUEUED: main thread submits a new set of file descriptors to be polled
+	   */
+	  select_thread_set_state (POLLING_DESCRIPTORS);
+	  break;
+
+	case POLLING_DESCRIPTORS:
+	  /* In the process of polling the file descriptors
+	   *
+	   *  => WAITING: polling completes when a file descriptor becomes active
+	   *  => POLLING_QUEUED: main thread submits a new set of file descriptors to be polled
+	   *  => POLLING_RESTART: main thread begins a new iteration with the same set file descriptors
+	   */ 
+	  SELECT_THREAD_UNLOCK ();
+	  old_poll_func (current_pollfds, current_n_pollfds, -1);
+	  SELECT_THREAD_LOCK ();
+
+	  read (select_thread_wakeup_pipe[0], &c, 1);
+
+	  if (select_thread_state == POLLING_DESCRIPTORS)
+	    {
+	      signal_main_thread ();
+	      select_thread_set_state (WAITING);
+	    }
+	  break;
+	}
+    }
+}
+
+static void 
+got_fd_activity (void *info)
+{
+  NSEvent *event;
+
+  /* Post a message so we'll break out of the message loop */
+  event = [NSEvent otherEventWithType: NSApplicationDefined
+	                     location: NSZeroPoint
+	                modifierFlags: 0
+	                    timestamp: 0
+	                 windowNumber: 0
+	                      context: nil
+                              subtype: GDK_QUARTZ_EVENT_SUBTYPE_EVENTLOOP
+	                        data1: 0 
+	                        data2: 0];
+
+  [NSApp postEvent:event atStart:YES];
+}
+
+static void
+select_thread_start (void)
+{
+  g_return_if_fail (select_thread_state == BEFORE_START);
+  
+  pipe (select_thread_wakeup_pipe);
+  fcntl (select_thread_wakeup_pipe[0], F_SETFL, O_NONBLOCK);
+
+  CFRunLoopSourceContext source_context = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, got_fd_activity };
+  select_main_thread_source = CFRunLoopSourceCreate (NULL, 0, &source_context);
+  
+  CFRunLoopAddSource (main_thread_run_loop, select_main_thread_source, kCFRunLoopCommonModes);
+
+  select_thread_state = WAITING;
+  
+  while (TRUE)
+    {
+      if (pthread_create (&select_thread, NULL, select_thread_func, NULL) == 0)
+	  break;
+
+      g_warning ("Failed to create select thread, sleeping and trying again");
+      sleep (1);
+    }
+}
+
+#ifdef G_ENABLE_DEBUG
+static void
+dump_poll_result (GPollFD *ufds,
+		  guint    nfds)
+{
+  gint i;
+
+  for (i = 0; i < nfds; i++)
+    {
+      if (ufds[i].fd >= 0 && ufds[i].revents)
+	{
+	  g_print (" %d:", ufds[i].fd);
+	  if (ufds[i].revents & G_IO_IN)
+	    g_print (" in");
+	  if (ufds[i].revents & G_IO_OUT)
+	    g_print (" out");
+	  if (ufds[i].revents & G_IO_PRI)
+	    g_print (" pri");
+	  g_print ("\n");
+	}
+    }
+}
+#endif
+
+gboolean
+pollfds_equal (GPollFD *old_pollfds,
+	       guint    old_n_pollfds,
+	       GPollFD *new_pollfds,
+	       guint    new_n_pollfds)
+{
+  gint i;
+  
+  if (old_n_pollfds != new_n_pollfds)
+    return FALSE;
+
+  for (i = 0; i < old_n_pollfds; i++)
+    {
+      if (old_pollfds[i].fd != new_pollfds[i].fd ||
+	  old_pollfds[i].events != new_pollfds[i].events)
+	return FALSE;
+    }
+
+  return TRUE;
+}
+
+/* Begins a polling operation with the specified GPollFD array; the 
+ * timeout is used only to tell if the polling operation is blocking
+ * or non-blocking.
+ *
+ * Return value:
+ *  -1: No file descriptors ready, began asynchronous poll
+ *   0: No file descriptors ready, asynchronous poll not needed
+ * > 0: Number of file descriptors ready
+ */
+static gint
+select_thread_start_poll (GPollFD *ufds,
+			  guint    nfds,			  gint     timeout)
+{
+  gint n_ready;
+  gboolean have_new_pollfds = FALSE;
+  gint poll_fd_index = -1;
+  gint i;
+
+  for (i = 0; i < nfds; i++)
+    if (ufds[i].fd == -1)
+      {
+	poll_fd_index = i;
+	break;
+      }
+  
+  if (nfds == 0 ||
+      (nfds == 1 && poll_fd_index >= 0))
+    {
+      GDK_NOTE (EVENTLOOP, g_print ("EventLoop: Nothing to poll\n"));
+      return 0;
+    }
+
+  /* If we went immediately to an async poll, then we might decide to
+   * dispatch idle functions when higher priority file descriptor sources
+   * are ready to be dispatched. So we always need to first check
+   * check synchronously with a timeout of zero, and only when no
+   * sources are immediately ready, go to the asynchronous poll.
+   *
+   * Of course, if the timeout passed in is 0, then the synchronous
+   * check is sufficient and we never need to do the asynchronous poll.
+   */
+  n_ready = old_poll_func (ufds, nfds, 0);
+  if (n_ready > 0 || timeout == 0)
+    {
+#ifdef G_ENABLE_DEBUG
+      if ((_gdk_debug_flags & GDK_DEBUG_EVENTLOOP) && n_ready > 0)
+	{
+	  g_print ("EventLoop: Found ready file descriptors before waiting\n");
+	  dump_poll_result (ufds, nfds);
+	}
+#endif
+  
+      return n_ready;
+    }
+  
+  SELECT_THREAD_LOCK ();
+
+  if (select_thread_state == BEFORE_START)
+    {
+      select_thread_start ();
+    }
+  
+  if (select_thread_state == POLLING_QUEUED)
+    {
+      /* If the select thread hasn't picked up the set of file descriptors yet
+       * then we can simply replace an old stale set with a new set.
+       */
+      if (!pollfds_equal (ufds, nfds, next_pollfds, next_n_pollfds - 1))
+	{
+	  g_free (next_pollfds);
+	  next_pollfds = NULL;
+	  next_n_pollfds = 0;
+	  
+	  have_new_pollfds = TRUE;
+	}
+    }
+  else if (select_thread_state == POLLING_RESTART || select_thread_state == POLLING_DESCRIPTORS)
+    {
+      /* If we are already in the process of polling the right set of file descriptors,
+       * there's no need for us to immediately force the select thread to stop polling
+       * and then restart again. And avoiding doing so increases the efficiency considerably
+       * in the common case where we have a set of basically inactive file descriptors that
+       * stay unchanged present as we process many events.
+       *
+       * However, we have to be careful that we don't hit the following race condition
+       *  Select Thread              Main Thread
+       *  -----------------          ---------------
+       *  Polling Completes
+       *                             Reads data or otherwise changes file descriptor state
+       *                             Checks if polling is current
+       *                             Does nothing (*)
+       *                             Releases lock
+       *  Acquires lock
+       *  Marks polling as complete
+       *  Wakes main thread
+       *                             Receives old stale file descriptor state
+       * 
+       * To avoid this, when the new set of poll descriptors is the same as the current
+       * one, we transition to the POLLING_RESTART stage at the point marked (*). When
+       * the select thread wakes up from the poll because a file descriptor is active, if
+       * the state is POLLING_RESTART it immediately begins polling same the file descriptor
+       * set again. This normally will just return the same set of active file descriptors
+       * as the first time, but in sequence described above will properly update the
+       * file descriptor state.
+       *
+       * Special case: this RESTART logic is not needed if the only FD is the internal GLib
+       * "wakeup pipe" that is presented when threads are initialized.
+       *
+       * P.S.: The harm in the above sequence is mostly that sources can be signalled
+       *   as ready when they are no longer ready. This may prompt a blocking read
+       *   from a file descriptor that hangs.
+       */
+      if (!pollfds_equal (ufds, nfds, current_pollfds, current_n_pollfds - 1))
+	have_new_pollfds = TRUE;
+      else
+	{
+	  if (!((nfds == 1 && poll_fd_index < 0 && g_thread_supported ()) ||
+		(nfds == 2 && poll_fd_index >= 0 && g_thread_supported ())))
+	    select_thread_set_state (POLLING_RESTART);
+	}
+    }
+  else
+    have_new_pollfds = TRUE;
+
+  if (have_new_pollfds)
+    {
+      GDK_NOTE (EVENTLOOP, g_print ("EventLoop: Submitting a new set of file descriptor to the select thread\n"));
+      
+      g_assert (next_pollfds == NULL);
+      
+      next_n_pollfds = nfds + 1;
+      next_pollfds = g_new (GPollFD, nfds + 1);
+      memcpy (next_pollfds, ufds, nfds * sizeof (GPollFD));
+  
+      next_pollfds[nfds].fd = select_thread_wakeup_pipe[0];
+      next_pollfds[nfds].events = G_IO_IN;
+  
+      if (select_thread_state != POLLING_QUEUED && select_thread_state != WAITING)
+	{
+	  if (select_thread_wakeup_pipe[1])
+	    {
+	      char c = 'A';
+	      write (select_thread_wakeup_pipe[1], &c, 1);
+	    }
+	}
+      
+      select_thread_set_state (POLLING_QUEUED);
+    }
+  
+  SELECT_THREAD_UNLOCK ();
+
+  return -1;
+}
+
+/* End an asynchronous polling operation started with
+ * select_thread_collect_poll(). This must be called if and only if
+ * select_thread_start_poll() return -1. The GPollFD array passed
+ * in must be identical to the one passed to select_thread_start_poll().
+ *
+ * The results of the poll are written into the GPollFD array passed in.
+ *
+ * Return Value: number of file descriptors ready
+ */
+static int
+select_thread_collect_poll (GPollFD *ufds, guint nfds)
+{
+  gint i;
+  gint n_ready = 0;
+  
+  SELECT_THREAD_LOCK ();
+
+  if (select_thread_state == WAITING) /* The poll completed */
+    {
+      for (i = 0; i < nfds; i++)
+	{
+	  if (ufds[i].fd == -1)
+	    continue;
+	  
+	  g_assert (ufds[i].fd == current_pollfds[i].fd);
+	  g_assert (ufds[i].events == current_pollfds[i].events);
+
+	  if (current_pollfds[i].revents)
+	    {
+	      ufds[i].revents = current_pollfds[i].revents;
+	      n_ready++;
+	    }
+	}
+      
+#ifdef G_ENABLE_DEBUG
+      if (_gdk_debug_flags & GDK_DEBUG_EVENTLOOP)
+	{
+	  g_print ("EventLoop: Found ready file descriptors after waiting\n");
+	  dump_poll_result (ufds, nfds);
+	}
+#endif
+    }
+
+  SELECT_THREAD_UNLOCK ();
+
+  return n_ready;
+}
+
+/************************************************************
+ *********             Main Loop Source             *********
+ ************************************************************/
+
 gboolean
 _gdk_quartz_event_loop_check_pending (void)
 {
@@ -51,21 +611,14 @@
 gdk_event_prepare (GSource *source,
 		   gint    *timeout)
 {
-  NSEvent *event;
   gboolean retval;
 
   GDK_THREADS_ENTER ();
   
   *timeout = -1;
 
-  event = [NSApp nextEventMatchingMask: NSAnyEventMask
-	                     untilDate: [NSDate distantPast]
-	                        inMode: NSDefaultRunLoopMode
-	                       dequeue: NO];
-
   retval = (_gdk_event_queue_find_first (_gdk_display) != NULL ||
-	    (current_events && current_events->head) ||
-            event != NULL);
+	    _gdk_quartz_event_loop_check_pending ());
 
   GDK_THREADS_LEAVE ();
 
@@ -79,15 +632,23 @@
 
   GDK_THREADS_ENTER ();
 
-  if (autorelease_pool)
-    [autorelease_pool release];
-  autorelease_pool = [[NSAutoreleasePool alloc] init];
-
-  if (_gdk_event_queue_find_first (_gdk_display) != NULL ||
-      _gdk_quartz_event_loop_check_pending ())
-    retval = TRUE;
-  else
-    retval = FALSE;
+  /* XXX: This check isn't right it won't handle a recursive GLib main
+   * loop run within an outer CFRunLoop run. Such loops will pile up
+   * memory. Fixing this requires setting a flag *only* when we call
+   * g_main_context_check() from within the run loop iteraton code,
+   * and also maintaining our own stack of run loops... allocating and
+   * releasing NSAutoReleasePools not properly nested with CFRunLoop
+   * runs seems to cause problems.
+   */
+  if (current_loop_level == 0)
+    {
+      if (autorelease_pool)
+	[autorelease_pool release];
+      autorelease_pool = [[NSAutoreleasePool alloc] init];
+    }
+  
+  retval = (_gdk_event_queue_find_first (_gdk_display) != NULL ||
+	    _gdk_quartz_event_loop_check_pending ());
 
   GDK_THREADS_LEAVE ();
 
@@ -127,212 +688,300 @@
   NULL
 };
 
-static void 
-got_fd_activity (void *info)
+/************************************************************
+ *********             Our Poll Function            *********
+ ************************************************************/
+
+static gint
+poll_func (GPollFD *ufds,
+	   guint    nfds,
+	   gint     timeout_)
 {
   NSEvent *event;
+  NSDate *limit_date;
+  gint n_ready;
 
-  /* Post a message so we'll break out of the message loop */
-  event = [NSEvent otherEventWithType: NSApplicationDefined
-	                     location: NSZeroPoint
-	                modifierFlags: 0
-	                    timestamp: 0
-	                 windowNumber: 0
-	                      context: nil
-                              subtype: GDK_QUARTZ_EVENT_SUBTYPE_EVENTLOOP
-	                        data1: 0 
-	                        data2: 0];
+  n_ready = select_thread_start_poll (ufds, nfds, timeout_);
+  if (n_ready > 0)
+    timeout_ = 0;
 
-  [NSApp postEvent:event atStart:YES];
+  if (timeout_ == -1)
+    limit_date = [NSDate distantFuture];
+  else if (timeout_ == 0)
+    limit_date = [NSDate distantPast];
+  else
+    limit_date = [NSDate dateWithTimeIntervalSinceNow:timeout_/1000.0];
+
+  getting_events = TRUE;
+  event = [NSApp nextEventMatchingMask: NSAnyEventMask
+	                     untilDate: limit_date
+	                        inMode: NSDefaultRunLoopMode
+                               dequeue: YES];
+  getting_events = FALSE;
+
+  if (n_ready < 0)
+    n_ready = select_thread_collect_poll (ufds, nfds);
+      
+  if (event &&
+      [event type] == NSApplicationDefined &&
+      [event subtype] == GDK_QUARTZ_EVENT_SUBTYPE_EVENTLOOP)
+    {
+      /* Just used to wake us up; if an event and a FD arrived at the same
+       * time; could have come from a previous iteration in some cases,
+       * but the spurious wake up is harmless if a little inefficient.
+       */
+      event = NULL;
+    }
+
+  if (event) 
+    {
+      if (!current_events)
+        current_events = g_queue_new ();
+      g_queue_push_head (current_events, [event retain]);
+    }
+
+  return n_ready;
 }
 
-static void *
-select_thread_func (void *arg)
+/************************************************************
+ *********  Running the main loop out of CFRunLoop  *********
+ ************************************************************/
+
+/* Wrapper around g_main_context_query() that handles reallocating
+ * run_loop_pollfds up to the proper size
+ */
+static gint
+query_main_context (GMainContext *context,
+		    int           max_priority,
+		    int          *timeout)
 {
-  int n_active_fds;
-
-  pthread_mutex_lock (&pollfd_mutex);
+  gint nfds;
+  
+  if (!run_loop_pollfds)
+    {
+      run_loop_pollfds_size = RUN_LOOP_POLLFDS_INITIAL_SIZE;
+      run_loop_pollfds = g_new (GPollFD, run_loop_pollfds_size);
+    }
 
-  while (1)
+  while ((nfds = g_main_context_query (context, max_priority, timeout,
+				       run_loop_pollfds, 
+				       run_loop_pollfds_size)) > run_loop_pollfds_size)
     {
-      char c;
-      int n;
+      g_free (run_loop_pollfds);
+      run_loop_pollfds_size = nfds;
+      run_loop_pollfds = g_new (GPollFD, nfds);
+    }
 
-      ready_for_poll = TRUE;
-      pthread_cond_signal (&ready_cond);
-      pthread_cond_wait (&ready_cond, &pollfd_mutex);
-      ready_for_poll = FALSE;
-
-      select_fd_waiting = TRUE;
-      pthread_cond_signal (&ready_cond);
-      pthread_mutex_unlock (&pollfd_mutex);
-      n_active_fds = old_poll_func (pollfds, n_pollfds, -1);
-      pthread_mutex_lock (&pollfd_mutex);
-      select_fd_waiting = FALSE;
-      n = read (wakeup_pipe[0], &c, 1);
-      if (n == 1)
-        {
-	  g_assert (c == 'A');
+  return nfds;
+}
 
-	  n_active_fds --;
-	}
-      pthread_mutex_unlock (&pollfd_mutex);
+static void
+run_loop_entry (void)
+{
+  current_loop_level++;
 
-      if (n_active_fds)
+  if (acquired_loop_level == -1)
+    {
+      if (g_main_context_acquire (NULL))
 	{
-	  /* We have active fds, signal the main thread */
-	  CFRunLoopSourceSignal (select_main_thread_source);
-	  if (CFRunLoopIsWaiting (main_thread_run_loop))
-	    CFRunLoopWakeUp (main_thread_run_loop);
+	  GDK_NOTE (EVENTLOOP, g_print ("EventLoop: Beginning tracking run loop activity\n"));
+	  acquired_loop_level = current_loop_level;
+	}
+      else
+	{
+	  /* If we fail to acquire the main context, that means someone is iterating
+	   * the main context in a different thread; we simply wait until this loop
+	   * exits and then try again at next entry. In general, iterating the loop
+	   * from a different thread is rare: it is only possible when GDK threading
+	   * is initialized and is not frequently used even then. So, we hope that
+	   * having GLib main loop iteration blocked in the combination of that and
+	   * a native modal operation is a minimal problem. We could imagine using a
+	   * thread that does g_main_context_wait() and then wakes us back up, but
+	   * the gain doesn't seem worth the complexity.
+	   */
+	  GDK_NOTE (EVENTLOOP, g_print ("EventLoop: Can't acquire main loop; skipping tracking run loop activity\n"));
 	}
-
-      pthread_mutex_lock (&pollfd_mutex);
     }
 }
 
-static gint
-poll_func (GPollFD *ufds, guint nfds, gint timeout_)
+static void
+run_loop_before_timers (void)
 {
-  NSEvent *event;
-  NSDate *limit_date;
-  gboolean poll_event_fd = FALSE;
-  int n_active = 0;
-  int i;
-
-  if (nfds > 1 || ufds[0].fd != -1)
-    {
-      if (!select_thread) {
-        /* Create source used for signalling the main thread */
-        main_thread_run_loop = CFRunLoopGetCurrent ();
-        CFRunLoopSourceContext source_context = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, got_fd_activity };
-        select_main_thread_source = CFRunLoopSourceCreate (NULL, 0, &source_context);
-        CFRunLoopAddSource (main_thread_run_loop, select_main_thread_source, kCFRunLoopDefaultMode);
-
-        pipe (wakeup_pipe);
-        fcntl(wakeup_pipe[0], F_SETFL, O_NONBLOCK);
-
-        pthread_mutex_lock (&pollfd_mutex);
-        pthread_create (&select_thread, NULL, select_thread_func, NULL);
-      } else
-        pthread_mutex_lock (&pollfd_mutex);
+}
 
-      while (!ready_for_poll)
-        pthread_cond_wait (&ready_cond, &pollfd_mutex);
+static void
+run_loop_before_sources (void)
+{
+  GMainContext *context = g_main_context_default ();
+  gint max_priority;
+  gint nfds;
+
+  /* Before we let the CFRunLoop process sources, we want to check if there
+   * are any pending GLib main loop sources more urgent than
+   * G_PRIORITY_DEFAULT that need to be dispatched. (We consider all activity
+   * from the CFRunLoop to have a priority of G_PRIORITY_DEFAULT.) If no
+   * sources are processed by the CFRunLoop, then processing will continue
+   * on to the BeforeWaiting stage where we check for lower priority sources.
+   */
+  
+  g_main_context_prepare (context, &max_priority); 
+  max_priority = MIN (max_priority, G_PRIORITY_DEFAULT);
 
-      /* We cheat and use the fake fd (if it's polled) for our pipe */
+  /* We ignore the timeout that query_main_context () returns since we'll
+   * always query again before waiting.
+   */
+  nfds = query_main_context (context, max_priority, NULL);
 
-      for (i = 0; i < nfds; i++)
-        if (ufds[i].fd == -1)
-          {
-            poll_event_fd = TRUE;
-            break;
-          }
-
-      g_free (pollfds);
-
-      if (i == nfds)
-        {
-          n_pollfds = nfds + 1;
-          pollfds = g_new (GPollFD, nfds + 1);
-          memcpy (pollfds, ufds, nfds * sizeof (GPollFD));
-        }
-      else
-        {
-          pollfds = g_memdup (ufds, nfds * sizeof (GPollFD));
-          n_pollfds = nfds;
-        }
-
-      pollfds[i].fd = wakeup_pipe[0];
-      pollfds[i].events = G_IO_IN;
-
-      /* Start our thread */
-      pthread_cond_signal (&ready_cond);
-      pthread_cond_wait (&ready_cond, &pollfd_mutex);
-      pthread_mutex_unlock (&pollfd_mutex);
+  if (nfds)
+    old_poll_func (run_loop_pollfds, nfds, 0);
+  
+  if (g_main_context_check (context, max_priority, run_loop_pollfds, nfds))
+    {
+      GDK_NOTE (EVENTLOOP, g_print ("EventLoop: Dispatching high priority sources\n"));
+      g_main_context_dispatch (context);
     }
+}
 
-  if (timeout_ == -1)
-    limit_date = [NSDate distantFuture];
-  else if (timeout_ == 0)
-    limit_date = [NSDate distantPast];
-  else
-    limit_date = [NSDate dateWithTimeIntervalSinceNow:timeout_/1000.0];
+static void
+dummy_timer_callback (CFRunLoopTimerRef  timer,
+		      void              *info)
+{
+  /* Nothing; won't normally even be called */
+}
 
-  event = [NSApp nextEventMatchingMask: NSAnyEventMask
-	                     untilDate: limit_date
-	                        inMode: NSDefaultRunLoopMode
-                               dequeue: YES];
+static void
+run_loop_before_waiting (void)
+{
+  GMainContext *context = g_main_context_default ();
+  gint timeout;
+  gint n_ready;
+
+  /* At this point, the CFRunLoop is ready to wait. We start a GMain loop
+   * iteration by calling the check() and query() stages. We start a
+   * poll, and if it doesn't complete immediately we let the run loop
+   * go ahead and sleep. Before doing that, if there was a timeout from
+   * GLib, we set up a CFRunLoopTimer to wake us up.
+   */
   
-  if (event)
+  g_main_context_prepare (context, &run_loop_max_priority); 
+  
+  run_loop_n_pollfds = query_main_context (context, run_loop_max_priority, &timeout);
+
+  n_ready = select_thread_start_poll (run_loop_pollfds, run_loop_n_pollfds, timeout);
+
+  if (n_ready > 0 || timeout == 0)
     {
-      if ([event type] == NSApplicationDefined &&
-          [event subtype] == GDK_QUARTZ_EVENT_SUBTYPE_EVENTLOOP)
-        {
-          pthread_mutex_lock (&pollfd_mutex);
-
-          for (i = 0; i < nfds; i++)
-            {
-              if (ufds[i].fd == -1)
-                continue;
-
-              g_assert (ufds[i].fd == pollfds[i].fd);
-              g_assert (ufds[i].events == pollfds[i].events);
-
-              if (pollfds[i].revents)
-                {
-                  ufds[i].revents = pollfds[i].revents;
-                  n_active ++;
-                }
-            }
-
-          pthread_mutex_unlock (&pollfd_mutex);
-
-          /* Try to get a Cocoa event too, if requested */
-          if (poll_event_fd)
-            event = [NSApp nextEventMatchingMask: NSAnyEventMask
-                                       untilDate: [NSDate distantPast]
-                                          inMode: NSDefaultRunLoopMode
-                                         dequeue: YES];
-          else
-            event = NULL;
-        }
-    }
-
-  /* There were no active fds, break out of the other thread's poll() */
-  pthread_mutex_lock (&pollfd_mutex);
-  if (select_fd_waiting && wakeup_pipe[1])
+      /* We have stuff to do, no sleeping allowed! */
+      CFRunLoopWakeUp (main_thread_run_loop);
+    }
+  else if (timeout > 0)
     {
-      char c = 'A';
+      /* We need to get the run loop to break out of it's wait when our timeout
+       * expires. We do this by adding a dummy timer that we'll remove immediately
+       * after the wait wakes up.
+       */
+      GDK_NOTE (EVENTLOOP, g_print ("EventLoop: Adding timer to wake us up in %d milliseconds\n", timeout));
+      
+      run_loop_timer = CFRunLoopTimerCreate (NULL, /* allocator */
+					     CFAbsoluteTimeGetCurrent () + timeout / 1000.,
+					     0, /* interval (0=does not repeat) */
+					     0, /* flags */
+					     0, /* order (priority) */
+					     dummy_timer_callback,
+					     NULL);
 
-      write (wakeup_pipe[1], &c, 1);
+      CFRunLoopAddTimer (main_thread_run_loop, run_loop_timer, kCFRunLoopCommonModes);
     }
-  pthread_mutex_unlock (&pollfd_mutex);
+  
+  run_loop_polling_async = n_ready < 0;
+}
 
-  if (event) 
+static void
+run_loop_after_waiting (void)
+{
+  GMainContext *context = g_main_context_default ();
+
+  /* After sleeping, we finish of the GMain loop iteratin started in before_waiting()
+   * by doing the check() and dispatch() stages.
+   */
+
+  if (run_loop_timer)
     {
-      for (i = 0; i < nfds; i++)
-        {
-          if (ufds[i].fd == -1)
-            {
-              ufds[i].revents = G_IO_IN;
-              break;
-            }
-        }
+      CFRunLoopRemoveTimer (main_thread_run_loop, run_loop_timer, kCFRunLoopCommonModes);
+      CFRelease (run_loop_timer);
+      run_loop_timer = NULL;
+    }
+  
+  if (run_loop_polling_async)
+    {
+      select_thread_collect_poll (run_loop_pollfds, run_loop_n_pollfds);
+      run_loop_polling_async = FALSE;
+    }
+  
+  if (g_main_context_check (context, run_loop_max_priority, run_loop_pollfds, run_loop_n_pollfds))
+    {
+      GDK_NOTE (EVENTLOOP, g_print ("EventLoop: Dispatching after waiting\n"));
+      g_main_context_dispatch (context);
+    }
+}
 
-      if (!current_events)
-        current_events = g_queue_new ();
-      g_queue_push_head (current_events, [event retain]);
+static void
+run_loop_exit (void)
+{
+  g_return_if_fail (current_loop_level > 0);
 
-      n_active ++;
+  if (current_loop_level == acquired_loop_level)
+    {
+      g_main_context_release (NULL);
+      acquired_loop_level = -1;
+      GDK_NOTE (EVENTLOOP, g_print ("EventLoop: Ended tracking run loop activity\n"));
     }
+  
+  current_loop_level--;
+}
 
-  return n_active;
+static void
+run_loop_observer_callback (CFRunLoopObserverRef observer,
+			    CFRunLoopActivity    activity,
+			    void                *info)
+{
+  if (getting_events) /* Activity we triggered */
+    return;
+  
+  switch (activity)
+    {
+    case kCFRunLoopEntry:
+      run_loop_entry ();
+      break;
+    case kCFRunLoopBeforeTimers:
+      run_loop_before_timers ();
+      break;
+    case kCFRunLoopBeforeSources:
+      run_loop_before_sources ();
+      break;
+    case kCFRunLoopBeforeWaiting:
+      run_loop_before_waiting ();
+      break;
+    case kCFRunLoopAfterWaiting:
+      run_loop_after_waiting ();
+      break;
+    case kCFRunLoopExit:
+      run_loop_exit ();
+      break;
+    default:
+      break;
+    }
 }
 
+/************************************************************/
+
 void
 _gdk_quartz_event_loop_init (void)
 {
   GSource *source;
+  CFRunLoopObserverRef observer;
+
+  /* Hook into the GLib main loop */
 
   event_poll_fd.events = G_IO_IN;
   event_poll_fd.fd = -1;
@@ -344,8 +993,22 @@
   g_source_attach (source, NULL);
 
   old_poll_func = g_main_context_get_poll_func (NULL);
-  g_main_context_set_poll_func (NULL, poll_func);  
+  g_main_context_set_poll_func (NULL, poll_func);
+  
+  /* Hook into the the CFRunLoop for the main thread */
+
+  main_thread_run_loop = CFRunLoopGetCurrent ();
+
+  observer = CFRunLoopObserverCreate (NULL, /* default allocator */
+				      kCFRunLoopAllActivities,
+				      true, /* repeats: not one-shot */
+				      0, /* order (priority) */
+				      run_loop_observer_callback,
+				      NULL);
+				     
+  CFRunLoopAddObserver (main_thread_run_loop, observer, kCFRunLoopCommonModes);
+  
+  /* Initialize our autorelease pool */
 
   autorelease_pool = [[NSAutoreleasePool alloc] init];
 }
-



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