/*
    GSK - a library to write servers
    Copyright (C) 2001-2004 Dave Benson

    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

    Contact:
        daveb@ffem.org <Dave Benson>
*/

#include <sys/wait.h>
#include <signal.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>

#include "gskmainloop.h"
#include "gskmacros.h"
#include "gskrbtreemacros.h"
#include "config.h"
#include "gskghelpers.h"
#include "gskerrno.h"
#include "gskerror.h"
#include "gskinit.h"
#include "gskdebug.h"
#include "cycle.h"
#include "debug.h"

/* --- prototypes --- */
static GObjectClass *parent_class = NULL;

/* lifetime of a source;
      - created
      - run (maybe recursively) (maybe repeatedly)
      - removed from list of sources / io changed
      - user destroy function run
      - memory for source free'd
 */

/* --- a main-loop source --- */
typedef enum
{
  GSK_SOURCE_TYPE_IDLE,
  GSK_SOURCE_TYPE_TIMER,
  GSK_SOURCE_TYPE_IO,
  GSK_SOURCE_TYPE_SIGNAL,
  GSK_SOURCE_TYPE_PROCESS
} GskSourceType;

struct _GskSource
{
  GskSourceType          type;
  /* are we calling the users' callback? */
  guint                  run_count : 16;

  /* is the user's destroy function called? */
  guint                  is_destroyed : 1;

  /* was this remove'd while running? */
  guint                  must_remove : 1;

  /* are we allowed to recursively invoke this source? */
  guint                  is_reentrant : 1;

  /* only for timers */
  guint                  timer_adjusted_while_running : 1;
  guint                  timer_is_red : 1;
  guint                  timer_in_tree : 1;

  GskMainLoop           *main_loop;

  gpointer               user_data;
  GDestroyNotify         destroy;

  union
  {
    struct 
    {
      int                    fd;
      GIOCondition           events;
      GskMainLoopIOFunc      func;
    } io;
    struct
    {
      GTimeVal               expire_time;
      gint64                 milli_period;
      GskMainLoopTimeoutFunc func;
      GskSource             *left, *right, *parent;
    } timer;
    struct
    {
      int                    process_id;
      GskMainLoopWaitPidFunc func;
      GskSource             *prev;
      GskSource             *next;
    } process;
    struct
    {
      int                    number;
      GskMainLoopSignalFunc  func;
      GskSource             *prev;
      GskSource             *next;
    } signal;
    struct
    {
      GskMainLoopIdleFunc    func;
      GskSource             *prev;
      GskSource             *next;
    } idle;
  } data;
};

struct _GskMainLoopContextList
{
  GMainContext *context;
  guint num_fds_alloced;
  GPollFD *poll_fds;
  GskSource **sources;		/* one for each poll_fd */
  guint num_fds_received;
  gint priority;
  GskMainLoopContextList *next;
};

/* --- helper macros --- */
#define MAIN_LOOP_CLASS(main_loop) 	GSK_MAIN_LOOP_GET_CLASS(main_loop)
#define DEBUG_POLL(args)		\
		_GSK_DEBUG_PRINTF (GSK_DEBUG_MAIN_LOOP,args)

/* gskrbtree of timers */
#define TIMER_GET_IS_RED(source)    ((source)->timer_is_red)
#define TIMER_SET_IS_RED(source, v) (source)->timer_is_red=v
#define TIMEVALS_COMPARE(a,b)                           \
              (a).tv_sec < (b).tv_sec ? -1              \
            : (a).tv_sec > (b).tv_sec ? +1              \
            : (a).tv_usec < (b).tv_usec ? -1            \
            : (a).tv_usec > (b).tv_usec ? +1            \
            : 0
#define TIMER_COMPARE(a,b, rv) \
  rv = TIMEVALS_COMPARE(a->data.timer.expire_time, b->data.timer.expire_time); \
  if (rv == 0) { if (a < b) rv = -1; else if (a > b) rv = 1; }
#define GET_MAIN_LOOP_TIMER_TREE(main_loop) \
  (main_loop)->timers, GskSource *, TIMER_GET_IS_RED, TIMER_SET_IS_RED,  \
  data.timer.parent, data.timer.left, data.timer.right, TIMER_COMPARE


static inline GIOCondition
get_io_events (GskMainLoop *main_loop,
	       guint        fd)
{
  return (main_loop->read_sources->pdata[fd] ? (G_IO_IN|G_IO_HUP)  : 0)
       | (main_loop->write_sources->pdata[fd] ? (G_IO_OUT|G_IO_HUP) : 0);
}

static inline void
gsk_main_loop_change_signal (GskMainLoop  *main_loop,
			     guint         signal,
			     gboolean      add)
{
  GskMainLoopChange change;
  change.type = GSK_MAIN_LOOP_EVENT_SIGNAL;
  change.data.signal.number = signal;
  change.data.signal.add = add;
  (*MAIN_LOOP_CLASS (main_loop)->change) (main_loop, &change);
  DEBUG_POLL (("gsk_main_loop_change_signal: %s watch on signal %d",
	      (add ? "adding" : "removing"), signal));
}

static inline void
gsk_main_loop_change_io (GskMainLoop  *main_loop,
			 GIOCondition  old_events,
			 guint         fd)
{
  GskMainLoopChange change;
  change.type = GSK_MAIN_LOOP_EVENT_IO;
  change.data.io.fd = fd;
  change.data.io.old_events = old_events;
  change.data.io.events = get_io_events (main_loop, fd);

  (*MAIN_LOOP_CLASS (main_loop)->change) (main_loop, &change);

  DEBUG_POLL (("gsk_main_loop_change_io: fd=%d: old events:%s%s; "
	       "new events=%s%s",
	       fd,
	       (old_events & G_IO_IN) ? " in" : "",
	       (old_events & G_IO_OUT) ? " out" : "",
	       (change.data.io.events & G_IO_IN) ? " in" : "",
	       (change.data.io.events & G_IO_OUT) ? " out" : ""));
}

static inline void
gsk_main_loop_change_process (GskMainLoop  *main_loop,
			      guint         pid,
                              gboolean      did_exit,
			      gboolean      add)
{
  GskMainLoopChange change;
  if (did_exit)
    g_assert (!add);
  change.type = GSK_MAIN_LOOP_EVENT_PROCESS;
  change.data.process.pid = pid;
  change.data.process.add = add;
  change.data.process.did_exit = did_exit;
  (*MAIN_LOOP_CLASS (main_loop)->change) (main_loop, &change);
  DEBUG_POLL (("gsk_main_loop_change_process: %s watch on %d",
	       (add ? "adding" : "removing"), pid));
}

static guint
gsk_main_loop_run_io_sources (GskMainLoop     *main_loop,
			      guint            fd,
			      GIOCondition     condition)
{
  GskSource *read_source = NULL;
  GskSource *write_source = NULL;
  g_return_val_if_fail (main_loop->read_sources->len > fd, 0);
  if (condition & G_IO_IN) 
    read_source = main_loop->read_sources->pdata[fd];
  if (condition & G_IO_OUT) 
    write_source = main_loop->write_sources->pdata[fd];
  if (read_source == NULL && write_source == NULL)
    {
      g_message ("WARNING: got event %u for unknown file-descriptor %d",
                 (guint) condition, fd);
      return 0;
    }
  if (read_source == write_source)
    {
      read_source->run_count++;
      if (!(*read_source->data.io.func) (fd, G_IO_IN | G_IO_OUT,
			                 read_source->user_data))
	read_source->must_remove = 1;
      read_source->run_count--;
      if (read_source->run_count == 0 && read_source->must_remove)
	gsk_source_remove (read_source);
      return 1;
    }
  else 
    {
      if (read_source != NULL)
	{
	  read_source->run_count++;
	  if (!(*read_source->data.io.func) (fd, G_IO_IN,
				             read_source->user_data))
	    read_source->must_remove = 1;
	  read_source->run_count--;
	  if (read_source->run_count == 0 && read_source->must_remove)
	    gsk_source_remove (read_source);
	}
      if (write_source != NULL)
	{
	  write_source->run_count++;
	  if (!(*write_source->data.io.func) (fd, G_IO_OUT,
				              write_source->user_data))
	    write_source->must_remove = 1;
	  write_source->run_count--;
	  if (write_source->run_count == 0 && write_source->must_remove)
	    gsk_source_remove (write_source);
	}
      return (read_source && write_source) ? 2 : 1;
    }
}

static guint
gsk_main_loop_run_signal_sources (GskMainLoop     *main_loop,
			          guint            signal)
{
  GskSource *at;
  guint rv = 0;
  if (main_loop->signal_source_lists->len <= signal)
    return rv;
  at = main_loop->signal_source_lists->pdata[signal];
  if (at != NULL)
    at->run_count++;
  while (at != NULL)
    {
      GskSource *next;
      rv++;
      if (!(*at->data.signal.func) (signal, at->user_data))
	at->must_remove = 1;
      next = at->data.signal.next;
      if (next != NULL)
	next->run_count++;
      at->run_count--;
      if (at->run_count == 0 && at->must_remove)
	gsk_source_remove (at);
      at = next;
    }
  return rv;
}

static guint
gsk_main_loop_run_process_sources (GskMainLoop     *main_loop,
			           GskMainLoopWaitInfo *wait_info)
{
  GskSource *at;
  guint rv = 0;

  g_hash_table_remove (main_loop->alive_pids, GUINT_TO_POINTER (wait_info->pid));
  at = g_hash_table_lookup (main_loop->process_source_lists,
			  GUINT_TO_POINTER (wait_info->pid));
  if (at != NULL)
    at->run_count++;
  while (at != NULL)
    {
      GskSource *next;
      rv++;
      (*at->data.process.func) (wait_info, at->user_data);
      at->must_remove = 1;
      next = at->data.process.next;
      if (next != NULL)
	next->run_count++;
      at->run_count--;
      if (at->run_count == 0 && at->must_remove)
	gsk_source_remove (at);
      at = next;
    }
  return rv;
}

#define INITIAL_MAX_EVENTS		2048

static inline void
g_time_val_add_millis (GTimeVal  *in_out,
		       guint64    millis)
{
  /* the two cases in this if() statement are
     equivalent, if millis is really a 32-bit number.
     but on x86 which lacks good 64-bit divides,
     the first branch is much faster.

     1<<32 milliseconds is about 50 days,
     so MOST timeouts are in the first case. */

  /* TODO: we should really decide at compile
     time if we have fast 64-bit int support,
     and if so, only use the second branch,
     to save code space.  Of course, i have
     no idea how to detect it, but it could be based 
     on "cpu".
     
     TODO: another nice thing would be support for
     microsecond precision timers. */
#ifndef HAVE_FAST_64BIT_INT_SUPPORT     /* TODO: define this on non-x86 */
  if (G_LIKELY ((millis>>32) == 0))
    {
      guint ms = (guint) millis;
      in_out->tv_sec += ms / 1000;
      in_out->tv_usec += (guint)(ms % 1000) * 1000;
    }
  else
#endif
    {
      in_out->tv_sec += millis / 1000;
      in_out->tv_usec += (guint)(millis % 1000) * 1000;
    }

  /* check for overflow of the microseconds column. */
  if (in_out->tv_usec > 1000 * 1000)
    {
      in_out->tv_usec -= 1000 * 1000;
      in_out->tv_sec++;
    }
}

#ifdef HAVE_TICK_COUNTER
static guint64 last_tick;
static GTimeVal last_tick_time;
typedef enum
{
  TICK_STATE_INIT,
  TICK_STATE_HAS_LAST_TICK,
  TICK_STATE_HAS_TICK_RATE,
  TICK_STATE_READY,
  TICK_STATE_FALLBACK
} TickState;

#if defined(TICKS_PER_USEC)
static guint usecs_per_tick = USECS_PER_TICK;
static guint usecs_per_tick_shift = USECS_PER_TICK_SHIFT;
static TickState tick_state = TICK_STATE_HAS_TICK_RATE;
static guint64 max_tick_delta = ((1000000ULL * TICKS_PER_USEC_b20) >> 20);
#else
static guint usecs_per_tick;
static guint usecs_per_tick_shift;
static TickState tick_state = TICK_STATE_INIT;
static guint64 max_tick_delta;
#endif

#if GTIMEVAL_EQUALS_SYS_TIMEVAL
#define _gsk_g_get_current_time(tv) gettimeofday((struct timeval*)(tv), NULL)
#else
#define _gsk_g_get_current_time(tv) g_get_current_time(tv)
#endif

void
gsk_get_current_time (GTimeVal *tv)
{
  switch (tick_state)
    {
    case TICK_STATE_INIT:
      _gsk_g_get_current_time (&last_tick_time);
      last_tick = getticks ();
      *tv = last_tick_time;
      tick_state = TICK_STATE_HAS_LAST_TICK;
      break;
    case TICK_STATE_HAS_LAST_TICK:
      _gsk_g_get_current_time (tv);
      if (tv->tv_sec > last_tick_time.tv_sec + 3)
        {
          /* compute tick rate */
          gdouble dusec = ((double)tv->tv_usec - last_tick_time.tv_usec)
                        + 1e6 * ((double)tv->tv_sec - last_tick_time.tv_sec);
          guint64 this_ticks = getticks ();
          gint64 dticks = this_ticks - last_tick;
          gdouble ticks_per_usec_d = (gdouble)dticks / dusec;
          if (ticks_per_usec_d <= 1.0)
            {
              g_message ("ticking too slow... not using cpu timer");
              tick_state = TICK_STATE_FALLBACK;
              break;
            }
          usecs_per_tick_shift = 30;
          usecs_per_tick = (double)(1 << usecs_per_tick_shift) / ticks_per_usec_d;
          max_tick_delta = (guint64)(1000000ULL * ticks_per_usec_d);
          tick_state = TICK_STATE_READY;
          if (usecs_per_tick == 0)
            {
              g_message ("ticking miscalc");
              tick_state = TICK_STATE_FALLBACK;
              break;
            }
          last_tick = this_ticks;
          last_tick_time = *tv;
        }
      break;
    case TICK_STATE_HAS_TICK_RATE:
      _gsk_g_get_current_time (&last_tick_time);
      last_tick = getticks ();
      *tv = last_tick_time;
      tick_state = TICK_STATE_READY;
      break;
    case TICK_STATE_READY:
      {
        guint64 this_ticks = getticks ();
        guint64 delta = this_ticks - last_tick;
        if (delta > max_tick_delta)
          {
            last_tick = this_ticks;
            _gsk_g_get_current_time (&last_tick_time);
            *tv = last_tick_time;
            //g_message ("resync: %u.%06u [ticks=%llu]",(guint)last_tick_time.tv_sec,(guint)last_tick_time.tv_usec,last_tick);
          }
        else
          {
            guint usecs = (guint)(((guint64)delta * usecs_per_tick) >> usecs_per_tick_shift);
            //g_message ("delta=%llu; cached time: usecs=%u", delta,usecs);
            *tv = last_tick_time;
            tv->tv_usec += usecs;
            while (tv->tv_usec >= 1000000)
              {
                tv->tv_usec -= 1000000;
                tv->tv_sec += 1;
              }
          }
        break;
      }
    case TICK_STATE_FALLBACK:
      _gsk_g_get_current_time (tv);
      break;

    default:
      g_assert_not_reached ();
    }
}
#else
void
gsk_get_current_time (GTimeVal *tv)
{
  _gsk_g_get_current_time (tv);
}
#endif


/**
 * gsk_main_loop_update_current_time:
 * @main_loop: main loop whose notion of time should be updated.
 *
 * Update the main loop's cached time value by querying the
 * system clock.
 */
void
gsk_main_loop_update_current_time (GskMainLoop *main_loop)
{
  gsk_get_current_time (&main_loop->current_time);
}

/**
 * gsk_main_loop_do_waitpid:
 * @pid: the process id to wait for.
 * @wait_info: place to collect termination status of the process.
 *
 * Do a waitpid system call on the process and munge the
 * data into @wait_info for the caller to use.
 *
 * returns: whether the waitpid succeeded.
 */
gboolean
gsk_main_loop_do_waitpid (int                  pid,
                          GskMainLoopWaitInfo *wait_info)
{
  int status;

retry:
  pid = waitpid (pid, &status, WNOHANG);
  if (pid < 0)
    {
      if (gsk_errno_is_ignorable (errno))
        goto retry;

      /*gsk_log_errno ("error running waitpid");*/
      return FALSE;
    }
  if (pid == 0)
    return FALSE;
  wait_info->pid = pid;
  wait_info->exited = WIFEXITED (status);
  if (wait_info->exited)
    {
      wait_info->d.exit_status = WEXITSTATUS (status);
      wait_info->dumped_core = FALSE;
    }
  else
    {
      wait_info->d.signal = WTERMSIG (status);
#ifdef WCOREDUMP
      wait_info->dumped_core = WCOREDUMP (status);
#else
      wait_info->dumped_core = FALSE;
#endif
    }
  return TRUE;
}


static gboolean
handle_poll_fd_set (int          fd,
		    GIOCondition condition,
		    gpointer     data)
{
  GPollFD *poll_fd = data;
  g_assert (poll_fd->fd == fd);
  poll_fd->revents = condition;
  return TRUE;
}

/**
 * gsk_main_loop_run:
 * @main_loop: the main loop to run.
 * @timeout: the maximum number of milliseconds to run, or -1 for no maximum.
 * @t_waited_out: the number of milliseconds out used, if non-NULL.
 *
 * Run the main loop once, for a specified number of milliseconds.
 *
 * returns: the number of sources processed.
 */
guint
gsk_main_loop_run          (GskMainLoop       *main_loop,
			    gint               timeout,
			    guint             *t_waited_out)
{
  GskMainLoopEvent *events;
  GskMainLoopClass *class = MAIN_LOOP_CLASS (main_loop);
  guint num_events;
  guint rv = 0;
  GTimeVal old_time;
  GTimeVal *current_time;
  guint i;
  GskSource *at;
  GskMainLoopContextList *list;
  GskMainLoopContextList **plist;

  g_return_val_if_fail (!main_loop->is_running, 0);
  main_loop->is_running = 1;

  gsk_main_loop_update_current_time (main_loop);
  current_time = &main_loop->current_time;
  old_time = *current_time;
  if (main_loop->first_idle != NULL)
    timeout = 0;
  GSK_RBTREE_FIRST (GET_MAIN_LOOP_TIMER_TREE (main_loop), at);
  if (at != NULL)
    {
      /* This rounds upward to the nearest millisecond */
      GTimeVal *exp_time = &at->data.timer.expire_time;
      if (exp_time->tv_sec < current_time->tv_sec
       || (exp_time->tv_sec == current_time->tv_sec
           && exp_time->tv_usec <= current_time->tv_usec))
        timeout = 0;
      else
        {
          guint ts = exp_time->tv_sec - current_time->tv_sec;
          gint tus = exp_time->tv_usec - current_time->tv_usec;
          gint t;
          if (tus < 0)
            {
              tus += 1000000;
              ts--;
            }
          t = ts * 1000 + (tus + 999) / 1000;
          if (timeout < 0 || t < timeout)
            timeout = t;
        }
    }

  /* TODO: this doesn't work for reentrant main-loops. */
  events = main_loop->event_array_cache;

  for (plist = &main_loop->first_context; *plist != NULL; )
    {
      GskMainLoopContextList *list = *plist;
      GMainContext *context = list->context;
      gint c_timeout;

      /* XXX: how can we tell if g_main_loop_quit was called? */
#if 0
      if (context->has_quit)	
	{
	  *plist = list->next;
	  if (*plist == NULL && main_loop->first_context == NULL)
	    main_loop->last_context = NULL;

	  g_free (list->poll_fds);
	  g_free (list->sources);
	  g_free (list);
	  continue;
	}
#endif

      if (!g_main_context_prepare (context, &list->priority))
	{
	  /* i think that TRUE means we can ditch calling 'query';
	   * but we always call query anyways.
	   */
	}

      /* hmm, is this allowed? */
retry_query:
      list->num_fds_received = g_main_context_query (context, list->priority,
						     &c_timeout,
						     list->poll_fds,
						     list->num_fds_alloced);
      /* if we filled the entire array, double its size */
      if (list->num_fds_received == list->num_fds_alloced)
	{
	  guint new_count = list->num_fds_alloced * 2;
	  list->poll_fds = g_renew (GPollFD, list->poll_fds, new_count);
	  list->sources = g_renew (GskSource *, list->sources, new_count);
	  list->num_fds_alloced = new_count;
	  goto retry_query;
	}

      if (timeout < 0 || (c_timeout >= 0 && c_timeout < timeout))
	timeout = c_timeout;

      /* add indication of interest in fd_events/n_events */
      for (i = 0; i < list->num_fds_received; i++)
	{
	  GPollFD *poll_fd = &list->poll_fds[i];
	  poll_fd->revents = 0;
	  list->sources[i] = gsk_main_loop_add_io (main_loop,
						   poll_fd->fd,
						   poll_fd->events,
						   handle_poll_fd_set,
						   poll_fd,
						   NULL);
	}
      plist = &((*plist)->next);
    }
  num_events = (*class->poll) (main_loop, main_loop->max_events, events, timeout);
  gsk_main_loop_update_current_time (main_loop);
  /* run i/o, signal and process handlers */
  for (i = 0; i < num_events; i++)
    {
      switch (events[i].type)
	{
        case GSK_MAIN_LOOP_EVENT_IO:
	  rv += gsk_main_loop_run_io_sources (main_loop,
					      events[i].data.io.fd,
					      events[i].data.io.events);
	  break;
        case GSK_MAIN_LOOP_EVENT_SIGNAL:
	  rv += gsk_main_loop_run_signal_sources (main_loop,
					          events[i].data.signal);
	  break;
        case GSK_MAIN_LOOP_EVENT_PROCESS:
	  rv += gsk_main_loop_run_process_sources (main_loop,
					           &events[i].data.process_wait_info);
	  break;
	}
    }

  /* deal with the glib-style main contexts */
  for (list = main_loop->first_context; list != NULL; list = list->next)
    {
      GMainContext *context = list->context;
      for (i = 0; i < list->num_fds_received; i++)
	gsk_source_remove (list->sources[i]);
      g_main_context_check (context, list->priority, list->poll_fds, list->num_fds_received);
      g_main_context_dispatch (context);
    }

  /* run idle functions */
  at = main_loop->first_idle;
  if (at != NULL)
    at->run_count++;
  while (at != NULL)
    {
      GskSource *next;
      if (!(*at->data.idle.func) (at->user_data))
	at->must_remove = 1;
      rv++;
      next = at->data.idle.next;
      if (next)
	next->run_count++;
      at->run_count--;
      if (at->run_count == 0 && at->must_remove)
	gsk_source_remove (at);
      at = next;
    }

  /* expire timers */
  for (;;)
    {
      GSK_RBTREE_FIRST (GET_MAIN_LOOP_TIMER_TREE (main_loop), at);
      if (at == NULL)
        break;
      if (at->data.timer.expire_time.tv_sec > current_time->tv_sec
       || (at->data.timer.expire_time.tv_sec == current_time->tv_sec
        && at->data.timer.expire_time.tv_usec >= current_time->tv_usec))
	break;

      at->run_count++;
      g_assert (at->timer_in_tree);
      GSK_RBTREE_REMOVE (GET_MAIN_LOOP_TIMER_TREE (main_loop), at);
      at->timer_in_tree = 0;
      if (!(*at->data.timer.func) (at->user_data))
	at->must_remove = 1;
      rv++;
      at->run_count--;
      if (at->run_count == 0 && at->must_remove)
	gsk_source_remove (at);
      else
	{
          if (at->timer_adjusted_while_running)
	    at->timer_adjusted_while_running = 0;
	  else
	    g_time_val_add_millis (&at->data.timer.expire_time, 
				   at->data.timer.milli_period);
          g_assert (!at->timer_in_tree);
          {
            GskSource *unused;
            GSK_RBTREE_INSERT (GET_MAIN_LOOP_TIMER_TREE (main_loop),
                               at, unused);
            at->timer_in_tree = 1;
          }
	}
    }

  g_return_val_if_fail (main_loop->is_running, rv);
  main_loop->is_running = 0;

  if (t_waited_out != NULL)
    {
      *t_waited_out = (main_loop->current_time.tv_sec - old_time.tv_sec) * 1000
	            + (main_loop->current_time.tv_usec - old_time.tv_usec) / 1000;
    }

  if (main_loop->max_events == num_events)
    {
      /* If we are pushing the number-of-events threshold,
	 then double the size of our buffer. */
      main_loop->max_events *= 2;
      main_loop->event_array_cache
	= g_renew (GskMainLoopEvent,
		   main_loop->event_array_cache, main_loop->max_events);
    }
  return rv;
}

static GMemChunk *gsk_source_chunk = NULL;
G_LOCK_DEFINE_STATIC (gsk_source_chunk);

static inline GskSource  *
gsk_source_new   (GskSourceType     type,
		  GskMainLoop      *main_loop,
		  gpointer          user_data,
		  GDestroyNotify    destroy)
{
  GskSource *rv;
  G_LOCK (gsk_source_chunk);
  if (gsk_source_chunk == NULL)
    gsk_source_chunk = g_mem_chunk_create (GskSource, 16, G_ALLOC_AND_FREE);
  rv = g_mem_chunk_alloc (gsk_source_chunk);
  G_UNLOCK (gsk_source_chunk);
  rv->type = type;
  rv->user_data = user_data;
  rv->destroy = destroy;
  rv->main_loop = main_loop;
  rv->run_count = 0;
  rv->must_remove = 0;
  rv->is_destroyed = 0;
  rv->is_reentrant = 0;
  return rv;
}
static inline void
gsk_source_free (GskSource *gsk_source)
{
  G_LOCK (gsk_source_chunk);
  g_mem_chunk_free (gsk_source_chunk, gsk_source);
  G_UNLOCK (gsk_source_chunk);
}

/**
 * gsk_main_loop_add_idle:
 * @main_loop: the loop to add the idle function to.
 * @source_func: the function to call.
 * @user_data: parameter to be passed to @source_func
 * @destroy: to be called when the source is destroyed.
 *
 * This adds an idle function to the main loop.
 * An idle function is a function which gets called every
 * time the main loop is run.  Furthermore, while there are
 * idle functions, the main loop will never block.
 *
 * One popular use of idle functions is to defer
 * an operation, usually because either something is not
 * in a good state to call immediately,
 * or because there may be many requests that should
 * be handled at one time.
 *
 * returns: a #GskSource which can be removed (or ignored).
 */
GskSource *
gsk_main_loop_add_idle     (GskMainLoop       *main_loop,
			    GskMainLoopIdleFunc source_func,
			    gpointer           user_data,
			    GDestroyNotify     destroy)
{
  GskSource *source = gsk_source_new (GSK_SOURCE_TYPE_IDLE, main_loop, user_data, destroy);
  source->data.idle.func = source_func;
  source->data.idle.prev = main_loop->last_idle;
  source->data.idle.next = NULL;
  if (main_loop->last_idle == NULL)
    main_loop->first_idle = source;
  else
    main_loop->last_idle->data.idle.next = source;
  main_loop->last_idle = source;
  return source;
}

/**
 * gsk_main_loop_add_signal:
 * @main_loop: the loop to add the unix signal handler function to.
 * @signal_number: the number of the signal handler, like SIGINT.
 * @signal_func: the function to run synchronously when a unix signal is raised.
 * @user_data: data to be passed to @signal_func.
 * @destroy: to be called when the source is destroyed.
 *
 * Add a signal handler to the main loop.
 *
 * Please note that unlike a normal unix signal handler (as provided by
 * signal(2) or sigaction(2)), this handler will be run synchronously,
 * so you can call non-reentrant methods.
 *
 * Also, because unix signal delivery is unreliable, if the signal is raised a
 * few times in rapid succession, you may miss some callbacks.
 * 
 * It is ok to connect multiple times to a single signal simulataneously.
 *
 * returns: a #GskSource which can be removed (or ignored).
 */
GskSource *
gsk_main_loop_add_signal   (GskMainLoop       *main_loop,
			    int                signal_number,
			    GskMainLoopSignalFunc signal_func,
			    gpointer           user_data,
			    GDestroyNotify     destroy)
{
  GskSource *source;
  GskSource *first;
  GskSource *last;
  //g_return_val_if_fail (signal_number != SIGCHLD, NULL);
  source = gsk_source_new (GSK_SOURCE_TYPE_SIGNAL, main_loop, user_data, destroy);
  if (main_loop->signal_source_lists->len <= (guint) signal_number)
    g_ptr_array_set_size (main_loop->signal_source_lists, signal_number + 1);
  first = main_loop->signal_source_lists->pdata[signal_number];
  last = first;

  /* XXX: really need to save last elements!!! */
  if (last != NULL)
    while (last->data.signal.next != NULL)
      last = last->data.signal.next;

  source->data.signal.number = signal_number;
  source->data.signal.func = signal_func;
  source->data.signal.prev = last;
  source->data.signal.next = NULL;
  if (last == NULL)
    {
      gsk_main_loop_change_signal (main_loop, signal_number, TRUE);
      main_loop->signal_source_lists->pdata[signal_number] = source;
    }
  else
    last->data.signal.next = source;
  main_loop->num_sources++;
  return source;
}

/**
 * gsk_main_loop_add_waitpid:
 * @main_loop: the loop to add the child-process termination handler function to.
 * @process_id: the child's process-id to wait for.
 * @waitpid_func: function to call when the process terminates in some way or another.
 * @user_data: data to be passed to @waitpid_func
 * @destroy: to be called when the source is destroyed.
 *
 * Add a handler to trap process termination.
 *
 * Only one handler is allowed per child process.
 *
 * The handler will be invoked synchronously.
 *
 * returns: #GskSource which can be removed (or ignored).
 */
GskSource *
gsk_main_loop_add_waitpid  (GskMainLoop       *main_loop,
			    int                process_id,
			    GskMainLoopWaitPidFunc waitpid_func,
			    gpointer           user_data,
			    GDestroyNotify     destroy)
{
  GskSource *source = gsk_source_new (GSK_SOURCE_TYPE_PROCESS, main_loop, user_data, destroy);
  GskSource *first;
  GskSource *last;
  first = g_hash_table_lookup (main_loop->process_source_lists,
                               GUINT_TO_POINTER (process_id));
  /* XXX: really need to save last elements!!! */
  last = first;
  if (last != NULL)
    while (last->data.process.next != NULL)
      last = last->data.process.next;
  source->data.process.process_id = process_id;
  source->data.process.prev = last;
  source->data.process.next = NULL;
  source->data.process.func = waitpid_func;
  if (last == NULL)
    {
      gsk_main_loop_change_process (main_loop, process_id, FALSE, TRUE);
      g_hash_table_insert (main_loop->process_source_lists,
			   GUINT_TO_POINTER (process_id),
			   source);
    }
  else
    last->data.process.next = source;
  main_loop->num_sources++;
  return source;
}

/**
 * gsk_main_loop_add_io:
 * @main_loop: the loop to add the i/o watch to.
 * @fd: the file-descriptor to watch for events.
 * @events: initial I/O events to watch for.
 * @io_func: a function to call when the currently requested
 * events occur.
 * @user_data: data to be passed to @io_func
 * @destroy: to be called when the source is destroyed.
 *
 * Add a handler to input or output on a file-descriptor (a socket or a pipe, usually).
 *
 * Only one handler trap is allowed per file-descriptor.
 *
 * The handler will be re-invoked until the event subsides.
 * For example, if you read only part of the data when a input event is raised,
 * the @io_func will be invoked again at every iteration of the main-loop
 * until there is no data available.
 *
 * returns: a #GskSource which can be removed or altered.
 */
GskSource *
gsk_main_loop_add_io       (GskMainLoop       *main_loop,
			    int                fd,
			    guint              events,
			    GskMainLoopIOFunc  io_func,
			    gpointer           user_data,
			    GDestroyNotify     destroy)
{
  GskSource *source;
  GIOCondition old_events;
  g_return_val_if_fail (fd >= 0, NULL);
  if ((guint) fd >= main_loop->read_sources->len)
    {
      g_ptr_array_set_size (main_loop->read_sources, fd + 1);
      g_ptr_array_set_size (main_loop->write_sources, fd + 1);
    }
  old_events = get_io_events (main_loop, fd);
  g_return_val_if_fail ((old_events & events & (G_IO_IN|G_IO_OUT)) == 0, NULL);
  source = gsk_source_new (GSK_SOURCE_TYPE_IO, main_loop, user_data, destroy);
  source->data.io.fd = fd;
  source->data.io.events = events;
  source->data.io.func = io_func;
  if ((events & G_IO_IN) == G_IO_IN)
    {
      g_return_val_if_fail (main_loop->read_sources->pdata[fd] == NULL, NULL);
      main_loop->read_sources->pdata[fd] = source;
    }
  if ((events & G_IO_OUT) == G_IO_OUT)
    {
      g_return_val_if_fail (main_loop->write_sources->pdata[fd] == NULL, NULL);
      main_loop->write_sources->pdata[fd] = source;
    }
  gsk_main_loop_change_io (main_loop, old_events, fd);
  main_loop->num_sources++;
  return source;
}

/**
 * gsk_source_adjust_io:
 * @source: the I/O source which now wants to watch different events.
 * @events: the new events to watch for the I/O source.
 *
 * This changes the types of events being watched by the main-loop.
 *
 * Note:  each new file-descriptor needs a new GskSource.
 * You must reuse this GskSource for a new file-descriptor
 * even if it happens to have the same numeric value
 * as a file-descriptor you closed.
 * (The reason why:  GSK automatically coagulates
 * multiple adjust_io calls.  This is fine with all main-loops.
 * However, kqueue(2) on BSD, and possibly others, automatically
 * unregister all interest in an event if the file-descriptor closes.
 * Hence, if the file-descriptor is re-opened and re-used with the
 * same GskSource, GSK will not be able to determine
 * that anything has changed, and will not issue a new #GskMainLoopChange.
 * This will break main-loops that are sensitive to exact which file-descriptor
 * (not just the number) was registered.)
 */
void
gsk_source_adjust_io    (GskSource         *source,
			 guint              events)
{
  guint fd;
  GIOCondition old_events;
  GskMainLoop *main_loop = source->main_loop;

  g_return_if_fail (source != NULL);
  g_return_if_fail (source->type == GSK_SOURCE_TYPE_IO);
  g_return_if_fail (main_loop->read_sources->len > (guint) source->data.io.fd);

  fd = source->data.io.fd;

  if ((events & (G_IO_IN|G_IO_OUT)) == (source->data.io.events & (G_IO_IN|G_IO_OUT)))
    return;
  old_events = get_io_events (main_loop, fd);
  if ((events & G_IO_IN) == G_IO_IN)
    {
      GskSource *old = main_loop->read_sources->pdata[fd];
      g_return_if_fail (old == source || old == NULL);
      main_loop->read_sources->pdata[fd] = source;
    }
  else if (main_loop->read_sources->pdata[fd] == source)
    main_loop->read_sources->pdata[fd] = NULL;

  if ((events & G_IO_OUT) == G_IO_OUT)
    {
      GskSource *old = main_loop->write_sources->pdata[fd];
      g_return_if_fail (old == source || old == NULL);
      main_loop->write_sources->pdata[fd] = source;
    }
  else if (main_loop->write_sources->pdata[fd] == source)
    main_loop->write_sources->pdata[fd] = NULL;
  source->data.io.events = events;
  gsk_main_loop_change_io (main_loop, old_events, fd);
}

/**
 * gsk_source_add_io_events:
 * @source: the input/output source whose events-of-interest set
 * should be expanded.
 * @events: new events which should cause @source to wake-up.
 *
 * Cause this source to be notified if any of the events in
 * the @events parameter are set, in addition to the events which
 * already caused this source to be woken up.
 */
void
gsk_source_add_io_events (GskSource *source,
			  guint      events)
{
  g_return_if_fail (source != NULL);
  g_return_if_fail (source->type == GSK_SOURCE_TYPE_IO);
  gsk_source_adjust_io (source, source->data.io.events | events);
}

/**
 * gsk_source_remove_io_events:
 * @source: the input/output source whose events-of-interest set
 * should be reduced.
 * @events: new events which should stop causing @source to wake-up.
 *
 * Cause this source to stop being notified if any of the events in
 * the @events parameter are set.
 */
void
gsk_source_remove_io_events (GskSource *source,
			     guint      events)
{
  g_return_if_fail (source != NULL);
  g_return_if_fail (source->type == GSK_SOURCE_TYPE_IO);
  gsk_source_adjust_io (source, source->data.io.events & (~events));
}

/**
 * gsk_main_loop_add_timer:
 * @main_loop: the main-loop which should keep track and run the timeout.
 * @timer_func: function to call when the requested amount of time elapses.
 * @timer_data: data to pass to @timer_func.
 * @timer_destroy: optional function to call to destroy the @timer_data.
 * @millis_expire: number of milliseconds to wait before running @timer_func.
 * @milli_period: period between subsequent invocation of the timeout.
 * This may be -1 to indicate that the timeout is a one-shot.
 *
 * Add a timeout function to the main-loop.
 * This is a function that will be called after
 * a fixed amount of time passes, and then may be called
 * at a regular interval thereafter.
 *
 * returns: #GskSource which can be removed or altered.
 */
#undef gsk_main_loop_add_timer
GskSource *
gsk_main_loop_add_timer64  (GskMainLoop       *main_loop,
			    GskMainLoopTimeoutFunc timer_func,
			    gpointer           timer_data,
			    GDestroyNotify     timer_destroy,
			    gint64             millis_expire,
			    gint64             milli_period)
{
  GskSource *source = gsk_source_new (GSK_SOURCE_TYPE_TIMER, main_loop, timer_data, timer_destroy);
  GskSource *unused;
  source->data.timer.expire_time = main_loop->current_time;
  g_time_val_add_millis (&source->data.timer.expire_time, millis_expire);
  source->data.timer.milli_period = milli_period;
  source->data.timer.func = timer_func;
  source->timer_adjusted_while_running = FALSE;
  GSK_RBTREE_INSERT (GET_MAIN_LOOP_TIMER_TREE (main_loop), source, unused);
  source->timer_in_tree = 1;
  main_loop->num_sources++;
  return source;
}
GskSource *
gsk_main_loop_add_timer    (GskMainLoop       *main_loop,
			    GskMainLoopTimeoutFunc timer_func,
			    gpointer           timer_data,
			    GDestroyNotify     timer_destroy,
			    gint               millis_expire,
			    gint               milli_period)
{
  return gsk_main_loop_add_timer64 (main_loop,
                                    timer_func, timer_data, timer_destroy,
                                    millis_expire, milli_period);
}

/**
 * gsk_main_loop_add_timer_absolute:
 * @main_loop: the main-loop which should keep track and run the timeout.
 * @timer_func: function to call when the requested amount of time elapses.
 * @timer_data: data to pass to @timer_func.
 * @timer_destroy: optional function to call to destroy the @timer_data.
 * @unixtime: number of seconds since Jan 1, 1970 GMT that will have passed when the 
 * timer should expire.
 * @unixtime_micro: fractional part of @unixtime, in microseconds.
 *
 * Add a timeout function to the main-loop.
 * The @timer_func will be called as soon as we detect that the specified time has passed.
 *
 * The time to wait until is (@unixtime + @unixtime_micro * 10^{-6}) seconds after
 * New Years, Jan 1, 1970 GMT.
 *
 * returns: a #GskSource which can be removed or altered.
 */
GskSource *
gsk_main_loop_add_timer_absolute (GskMainLoop       *main_loop,
				  GskMainLoopTimeoutFunc timer_func,
				  gpointer           timer_data,
				  GDestroyNotify     timer_destroy,
				  int                unixtime,
				  int                unixtime_micro)
{
  GskSource *source = gsk_source_new (GSK_SOURCE_TYPE_TIMER, main_loop, timer_data, timer_destroy);
  GskSource *unused;
  source->data.timer.expire_time.tv_sec = unixtime;
  source->data.timer.expire_time.tv_usec = unixtime_micro;
  source->data.timer.milli_period = -1;
  source->data.timer.func = timer_func;
  source->timer_adjusted_while_running = 0;
  GSK_RBTREE_INSERT (GET_MAIN_LOOP_TIMER_TREE (main_loop), source, unused);
  source->timer_in_tree = 1;
  main_loop->num_sources++;
  return source;
}

/**
 * gsk_source_adjust_timer:
 * @timer_source: the timeout source returned by gsk_main_loop_add_timer() or gsk_main_loop_add_timer_absolute().
 * @millis_expire: the number of milliseconds from now that the timer should run.
 * @milli_period: the period between subsequent runs of the timer, or -1 to indicate that 
 * the timer is a one-shot.
 *
 * Adjust the timeout and period for an already existing timer source.
 * (You may only call this on timer sources.)
 */
#undef gsk_source_adjust_timer                              /* compatibility hack */
#define gsk_source_adjust_timer gsk_source_adjust_timer64   /* compatibility hack */
void
gsk_source_adjust_timer   (GskSource         *timer_source,
			   gint64             millis_expire,
			   gint64             milli_period)
{
  GskMainLoop *main_loop = timer_source->main_loop;
  g_return_if_fail (timer_source->type == GSK_SOURCE_TYPE_TIMER);
  if (timer_source->timer_in_tree)
    {
      GSK_RBTREE_REMOVE (GET_MAIN_LOOP_TIMER_TREE (main_loop), timer_source);
      timer_source->timer_in_tree = 0;
    }
  timer_source->data.timer.expire_time = main_loop->current_time;
  g_time_val_add_millis (&timer_source->data.timer.expire_time, millis_expire);
  timer_source->data.timer.milli_period = milli_period;
  if (timer_source->run_count == 0)
    {
      if (!timer_source->timer_in_tree)
        {
          GskSource *unused;
          GSK_RBTREE_INSERT (GET_MAIN_LOOP_TIMER_TREE (main_loop),
                             timer_source,
                             unused);
          timer_source->timer_in_tree = 1;
        }
    }
  else
    timer_source->timer_adjusted_while_running = 1;
}
#undef gsk_source_adjust_timer                  /* compatibility hack */
void                                            /* compatibility hack */
gsk_source_adjust_timer   (GskSource         *timer_source,
			   gint               millis_expire,
			   gint               milli_period)
{
  gsk_source_adjust_timer64 (timer_source, millis_expire, milli_period);
}

static inline void
gsk_main_loop_block_io (GskMainLoop *main_loop,
			GskSource   *source)
{
  switch (source->type)
    {
      case GSK_SOURCE_TYPE_IDLE:
	if (source->data.idle.prev != NULL)
	  source->data.idle.prev->data.idle.next = source->data.idle.next;
	else
	  main_loop->first_idle = source->data.idle.next;
	if (source->data.idle.next != NULL)
	  source->data.idle.next->data.idle.prev = source->data.idle.prev;
	else
	  main_loop->last_idle = source->data.idle.prev;
	break;

      case GSK_SOURCE_TYPE_TIMER:
        if (source->timer_in_tree)
          {
            GSK_RBTREE_REMOVE (GET_MAIN_LOOP_TIMER_TREE (main_loop), source);
            source->timer_in_tree = 0;
          }
	break;

      case GSK_SOURCE_TYPE_IO:
	{
	  int fd = source->data.io.fd;
	  GIOCondition old_events = get_io_events (main_loop, fd);
	  if ((source->data.io.events & G_IO_IN) == G_IO_IN)
	    {
	      g_return_if_fail (main_loop->read_sources->pdata[fd] == source);
	      main_loop->read_sources->pdata[fd] = NULL;
	    }
	  if ((source->data.io.events & G_IO_OUT) == G_IO_OUT)
	    {
	      g_return_if_fail (main_loop->write_sources->pdata[fd] == source);
	      main_loop->write_sources->pdata[fd] = NULL;
	    }
	  gsk_main_loop_change_io (main_loop, old_events, fd);
	}
	break;

      case GSK_SOURCE_TYPE_SIGNAL:
	{
	  int signo = source->data.signal.number;
	  if (source->data.signal.prev != NULL)
	    source->data.signal.prev->data.signal.next = source->data.signal.next;
	  else
	    {
	      main_loop->signal_source_lists->pdata[signo]
		= source->data.signal.next;
	      if (main_loop->signal_source_lists->pdata[signo] == NULL)
		gsk_main_loop_change_signal (main_loop, signo, FALSE);
	    }
	  if (source->data.signal.next != NULL)
	    source->data.signal.next->data.signal.prev = source->data.signal.prev;
	  break;
	}

      case GSK_SOURCE_TYPE_PROCESS:
	{
	  guint pid = source->data.process.process_id;
	  if (source->data.process.prev != NULL)
	    source->data.process.prev->data.process.next
	      = source->data.process.next;
	  else
	    {
	      if (source->data.process.next == NULL)
		{
		  g_hash_table_remove (main_loop->process_source_lists,
				       GUINT_TO_POINTER (pid));
                  if (g_hash_table_lookup (main_loop->alive_pids,
                                           GUINT_TO_POINTER (pid)))
                    {
		      gsk_main_loop_change_process (main_loop, pid, FALSE, FALSE);
                      g_hash_table_remove (main_loop->alive_pids,
                                           GUINT_TO_POINTER (pid));
                    }
                  else
		    gsk_main_loop_change_process (main_loop, pid, TRUE, FALSE);
		}
	      else
		g_hash_table_insert (main_loop->process_source_lists,
				     GUINT_TO_POINTER (pid),
				     source->data.process.next);
	    }
	  if (source->data.process.next != NULL)
	    source->data.process.next->data.process.prev
	      = source->data.process.prev;
	  break;
	}
    }
}

/**
 * gsk_source_peek_main_loop:
 * @source: the source to query.
 *
 * Get the main-loop where the source was created.
 *
 * returns: the main-loop associated with the source.
 */
GskMainLoop *gsk_source_peek_main_loop (GskSource *source)
{
  return source->main_loop;
}

/**
 * gsk_source_remove:
 * @source: the source to remove and destroy.
 *
 * Destroy a main loop's source.
 *
 * If the source is currently running,
 * it's destroy method will not be called until the
 * source's callback returns.  (This way, important data won't be 
 * deleted unexpectedly in the middle of the user's callback.)
 */
void
gsk_source_remove (GskSource         *source)
{
  GskMainLoop *main_loop = source->main_loop;
  if (source->run_count > 0)
    {
      /* Remove fd interest immediately, even if reenterring.

         This is important because the
         caller may be planning to close the file descriptor. */
      if (source->type == GSK_SOURCE_TYPE_IO)
        {
          int fd = source->data.io.fd;
	  GIOCondition old_events = get_io_events (main_loop, fd);
          if (old_events)
            {
              if (source->data.io.events & G_IO_IN)
                main_loop->read_sources->pdata[fd] = NULL;
              if (source->data.io.events & G_IO_OUT)
                main_loop->write_sources->pdata[fd] = NULL;
              source->data.io.events = 0;
              gsk_main_loop_change_io (main_loop, old_events, fd);
            }
        }
      source->must_remove = 1;
      return;
    }

  if (!source->is_destroyed)
    {
      source->is_destroyed = 1;
      if (source->destroy != NULL)
	(*source->destroy) (source->user_data);
    }

  /* remove from lists and change i/o handling */
  gsk_main_loop_block_io (main_loop, source);
  main_loop->num_sources--;

  gsk_source_free (source);
}

/**
 * gsk_main_loop_quit:
 * @main_loop: the main-loop which is being asked to quit.
 *
 * Set the main-loop flag that indicates that it should really stop running.
 *
 * If you are executing a GskMainLoop using gsk_main_loop_run(),
 * then you should probably check gsk_main_loop_should_continue() at every iteration
 * to ensure that you should not have quit by now.
 */
void
gsk_main_loop_quit (GskMainLoop       *main_loop)
{
  main_loop->quit = 1;
}

/**
 * gsk_main_loop_should_continue:
 * @main_loop: the main-loop to query.
 *
 * Query whether the main-loop should keep running or not.
 *
 * returns: whether to keep running this main-loop.
 */
gboolean
gsk_main_loop_should_continue (GskMainLoop       *main_loop)
{
#if 0
  if (main_loop->quit)
    return FALSE;
  return main_loop->num_sources > 0 || main_loop->first_context != NULL;
#else
  return !main_loop->quit;
#endif
}

/* XXX: this kind of trick should be done constantly (whenever a source
        of the appropriate type is removed) */
static void
check_if_all_handlers_clear (GskMainLoop *main_loop)
{
  guint max_len;
  guint i;

  /* if all signals are untrapped, reset the array */
  max_len = 0;
  for (i = 0; i < main_loop->signal_source_lists->len; i++)
    if (main_loop->signal_source_lists->pdata[i] != NULL)
      max_len = i + 1;
  g_ptr_array_set_size (main_loop->signal_source_lists, max_len);

  /* if all fds are untrapped, reset the array */
  max_len = 0;
  for (i = 0; i < main_loop->read_sources->len; i++)
    if (main_loop->read_sources->pdata[i] != NULL
     || main_loop->write_sources->pdata[i] != NULL)
      max_len = i + 1;
  g_ptr_array_set_size (main_loop->read_sources, max_len);
  g_ptr_array_set_size (main_loop->write_sources, max_len);
}

void
gsk_main_loop_destroy_all_sources (GskMainLoop *main_loop)
{
  /* temporary variables */
  GskSource *at;
  GSList *list, *at_list;
  guint i;

  /* Destroy idle functions */
  at = main_loop->first_idle;
  while (at != NULL)
    {
      GskSource *next = at->data.idle.next;
      if (next != NULL)
	next->run_count++;
      gsk_source_remove (at);
      if (next != NULL)
	next->run_count--;

      at = next;     /* possibly must_remove is set, but it's ok */
    }

  /* Destroy timers */
  while (main_loop->timers)
    gsk_source_remove (main_loop->timers);

  /* Destroy i/o handlers */
  for (i = 0; i < main_loop->read_sources->len; i++)
    {
      at = main_loop->read_sources->pdata[i];
      if (at != NULL)
	gsk_source_remove (at);
      at = main_loop->write_sources->pdata[i];
      if (at != NULL)
	gsk_source_remove (at);
    }

  /* Destroy signal handlers */
  for (i = 0; i < main_loop->signal_source_lists->len; i++)
    {
      at = main_loop->signal_source_lists->pdata[i];
      while (at != NULL)
	{
	  GskSource *next = at->data.signal.next;
	  if (next != NULL)
	    next->run_count++;
	  gsk_source_remove (at);
	  if (next != NULL)
	    next->run_count--;

	  at = next;     /* possibly must_remove is set, but it's ok */
	}
    }


  /* Destroy waitpid handlers */
  list = gsk_g_hash_table_key_slist (main_loop->process_source_lists);
  for (at_list = list; at_list != NULL; at_list = at_list->next)
    {
      at = g_hash_table_lookup (main_loop->process_source_lists, at_list->data);
      while (at != NULL)
	{
	  GskSource *next = at->data.idle.next;
	  if (next != NULL)
	    next->run_count++;
	  gsk_source_remove (at);
	  if (next != NULL)
	    next->run_count--;

	  at = next;     /* possibly must_remove is set, but it's ok */
	}
    }
  g_slist_free (list);

  check_if_all_handlers_clear (main_loop);
}

#define CHECK_INVARIANTS(main_loop)				              \
  G_STMT_START{							              \
    g_assert (main_loop->write_sources->len == main_loop->read_sources->len); \
  }G_STMT_END

static void
gsk_main_loop_finalize (GObject *object)
{
  GskMainLoop *main_loop = GSK_MAIN_LOOP (object);
  gsk_main_loop_destroy_all_sources (main_loop);

  g_assert (main_loop->first_idle == NULL);
  g_assert (main_loop->last_idle == NULL);
  g_assert (main_loop->timers == NULL);
  g_assert (g_hash_table_size (main_loop->process_source_lists) == 0);
  g_assert (main_loop->running_source == NULL);
  CHECK_INVARIANTS (main_loop);

  g_hash_table_destroy (main_loop->process_source_lists);
  g_ptr_array_free (main_loop->read_sources, TRUE);
  g_ptr_array_free (main_loop->write_sources, TRUE);
  g_ptr_array_free (main_loop->signal_source_lists, TRUE);
  g_free (main_loop->event_array_cache);

  g_hash_table_destroy (main_loop->alive_pids);

  (*parent_class->finalize) (object);
}

/* --- functions --- */
static void
gsk_main_loop_init (GskMainLoop *main_loop)
{
  main_loop->timers = NULL;
  main_loop->read_sources = g_ptr_array_new ();
  main_loop->write_sources = g_ptr_array_new ();
  main_loop->signal_source_lists = g_ptr_array_new ();
  main_loop->process_source_lists = g_hash_table_new (NULL, NULL);
  main_loop->alive_pids = g_hash_table_new (NULL, NULL);
  main_loop->max_events = INITIAL_MAX_EVENTS;
  main_loop->event_array_cache = g_new (GskMainLoopEvent, main_loop->max_events);
  gsk_main_loop_update_current_time (main_loop);
}

static void
gsk_main_loop_class_init (GskMainLoopClass *class)
{
  GObjectClass *object_class = G_OBJECT_CLASS (class);
  parent_class = g_type_class_peek_parent (class);
  object_class->finalize = gsk_main_loop_finalize;
}

GType gsk_main_loop_get_type()
{
  static GType main_loop_type = 0;
  if (!main_loop_type)
    {
      static const GTypeInfo main_loop_info =
      {
	sizeof(GskMainLoopClass),
	(GBaseInitFunc) NULL,
	(GBaseFinalizeFunc) NULL,
	(GClassInitFunc) gsk_main_loop_class_init,
	NULL,		/* class_finalize */
	NULL,		/* class_data */
	sizeof (GskMainLoop),
	0,		/* n_preallocs */
	(GInstanceInitFunc) gsk_main_loop_init,
	NULL		/* value_table */
      };
      GType parent = G_TYPE_OBJECT;
      main_loop_type = g_type_register_static (parent,
                                                  "GskMainLoop",
						  &main_loop_info, 0);
    }
  return main_loop_type;
}

/* --- GskMainLoopWaitInfo type registration --- */
static GskMainLoopWaitInfo *
gsk_main_loop_wait_info_copy (const GskMainLoopWaitInfo *wait_info)
{
  return g_memdup (wait_info, sizeof (GskMainLoopWaitInfo));
}

GType gsk_main_loop_wait_info_get_type(void)
{
  static GType rv = 0;
  if (rv == 0)
    rv = g_boxed_type_register_static ("GskMainLoopWaitInfo",
                                       (GBoxedCopyFunc) gsk_main_loop_wait_info_copy,
                                       (GBoxedFreeFunc) g_free);
  return rv;
}


/* --- Default Construction --- */
#include "main-loops/gskmainloopkqueue.h"
#include "main-loops/gskmainloopdevpoll.h"
#include "main-loops/gskmainloopepoll.h"
#include "main-loops/gskmainlooppoll.h"
#include "main-loops/gskmainloopselect.h"

static struct
{
  GType (*get_type_func) () G_GNUC_CONST;
  const char *env_value;
  gboolean supports_threads;
} main_loop_types[] = {
#if HAVE_EPOLL_SUPPORT
  { gsk_main_loop_epoll_get_type,    "epoll",   TRUE  },
#endif
#if HAVE_SYS_DEV_POLL
  { gsk_main_loop_dev_poll_get_type, "devpoll", TRUE  },
#endif
#if HAVE_KQUEUE
  { gsk_main_loop_kqueue_get_type,   "kqueue",  FALSE },
#endif
#if HAVE_POLL
  { gsk_main_loop_poll_get_type,     "poll",    TRUE  },
#endif
#if HAVE_SELECT
  { gsk_main_loop_select_get_type,   "select",  TRUE  },
#endif
  { NULL, NULL, FALSE }
};


GskMainLoop *
gsk_main_loop_new (GskMainLoopCreateFlags create_flags)
{
  gboolean threads = (create_flags & GSK_MAIN_LOOP_NEEDS_THREADS) != 0;
  GType type = 0;
  /* Try the GSK_MAIN_LOOP_TYPE environment variable */
  guint i = 0;
  const char *env_type = g_getenv ("GSK_MAIN_LOOP_TYPE");

  if (env_type != NULL)
    {
      GSK_SKIP_WHITESPACE (env_type);
      if (*env_type == '\0')
	env_type = NULL;
    }

  if (env_type != NULL)
    {
      GskMainLoop *main_loop;
      while (main_loop_types[i].get_type_func != NULL)
	{
	  if (strcmp (env_type, main_loop_types[i].env_value) == 0
	   && (!threads || main_loop_types[i].supports_threads))
	    {
	      type = (*main_loop_types[i].get_type_func) ();
	      break;
	    }
	  i++;
	}
      if (type == 0)
	{
	  if (strcmp (env_type, "kqueue") == 0)
	    g_warning ("kqueue doesn't support threads; "
		       "falling back to poll");
	  else
	    g_warning ("GSK_MAIN_LOOP_TYPE set to %s: unsupported", env_type);
	}
      else
	{
	  GskMainLoopClass *class;
	  main_loop = g_object_new (type, NULL);
	  class = GSK_MAIN_LOOP_GET_CLASS (main_loop);
	  if (class->setup == NULL || (*class->setup) (main_loop))
	    return main_loop;
	  else
	    {
	      g_warning ("could not setup main-loop of type %s", env_type);
	    }
	  g_object_unref (main_loop);
	}
    }

  /* Try autoconf-detected defaults, in our preferred order. */
  for (i = 0; main_loop_types[i].get_type_func != NULL; i++)
    {
      if (!threads || main_loop_types[i].supports_threads)
	{
	  GskMainLoop *main_loop;
	  GskMainLoopClass *class;
	  type = (*main_loop_types[i].get_type_func) ();
	  main_loop = g_object_new (type, NULL);
	  class = GSK_MAIN_LOOP_GET_CLASS (main_loop);
	  main_loop->is_setup = 1;
	  if (class->setup != NULL && !((*class->setup) (main_loop)))
	    {
	      g_object_unref (main_loop);
	      continue;
	    }
	  return main_loop;
	}
      i++;
    }

  g_warning ("No type of GskMainLoop can be constructed");
  return NULL;
}

/* --- GMainLoop support --- */
/**
 * gsk_main_loop_add_context:
 * @main_loop: main-loop which will take responsibility for @context.
 * @context: a GMainContext that should be handled by gsk_main_loop_run().
 * 
 * Indicate that a particular #GskMainLoop will take care of
 * invoking the necessary methods of @context.
 */
void
gsk_main_loop_add_context (GskMainLoop  *main_loop,
			   GMainContext *context)
{
  GskMainLoopContextList *node = g_new (GskMainLoopContextList, 1);
  node->context = context;
  node->num_fds_alloced = 16;
  node->poll_fds = g_new (GPollFD, node->num_fds_alloced);
  node->sources = g_new (GskSource *, node->num_fds_alloced);
  node->num_fds_received = 0;
  node->priority = G_PRIORITY_DEFAULT;

  node->next = NULL;
  if (main_loop->last_context != NULL)
    main_loop->last_context->next = node;
  else
    main_loop->first_context = node;
  main_loop->last_context = node;
}

/* --- per-thread default main-loop --- */

/* globals for non-threaded main-loops */
static GskMainLoop *non_thread_main_loop = NULL;

/* globals for per-thread main-loops */
static GPrivate *private_main_loop_key;

/**
 * gsk_main_loop_default:
 *
 * Get the main-loop which is associated with the current thread.
 *
 * returns: a pointer to the main-loop.  This function does not
 * increase the ref-count on the main-loop, so you do not need 
 * to call g_object_unref() on the return value.
 */
GskMainLoop *
gsk_main_loop_default ()
{
  if (gsk_init_get_support_threads ())
    {
      GskMainLoop *main_loop = g_private_get (private_main_loop_key);
      if (main_loop != NULL)
	return main_loop;
      main_loop = gsk_main_loop_new (GSK_MAIN_LOOP_NEEDS_THREADS);
      g_assert (main_loop != NULL);
      g_private_set (private_main_loop_key, main_loop);
      return main_loop;
    }
  else
    {
      if (non_thread_main_loop != NULL)
	return non_thread_main_loop;
      non_thread_main_loop = gsk_main_loop_new (0);
      g_assert (non_thread_main_loop != NULL);
      return non_thread_main_loop;
    }
}

/* --- fork wrapper --- */
void
_gsk_main_loop_fork_notify ()
{
  if (!gsk_init_get_support_threads ())
    {
      /* NOTE: we don't free the memory for the main-loop,
	 but it should not amount to much.

	 However, critical resources that may not be shared,
	 like file-descriptors should probably be registered
	 with the gsk_fork_* functions. */

      non_thread_main_loop = NULL;
    }
}

/* --- initialization --- */
void
_gsk_main_loop_init (void)
{
  if (gsk_init_get_support_threads ())
    private_main_loop_key = g_private_new (g_object_unref);
}


syntax highlighted by Code2HTML, v. 0.9.1