Index: glib/gmain.c =================================================================== RCS file: /cvs/gnome/glib/glib/gmain.c,v retrieving revision 1.90.2.7 diff -u -r1.90.2.7 gmain.c --- glib/gmain.c 25 Aug 2003 16:20:55 -0000 1.90.2.7 +++ glib/gmain.c 15 Sep 2003 18:01:38 -0000 @@ -61,9 +61,14 @@ #include #endif /* G_OS_BEOS */ +#ifdef G_OS_UNIX +#include +#include +#endif /* Types */ typedef struct _GTimeoutSource GTimeoutSource; +typedef struct _GChildWatchSource GChildWatchSource; typedef struct _GPollRec GPollRec; typedef struct _GSourceCallback GSourceCallback; @@ -156,6 +161,14 @@ guint interval; }; +struct _GChildWatchSource +{ + GSource source; + gint pid; + gint child_status; + gint count; +}; + struct _GPollRec { gint priority; @@ -212,6 +225,12 @@ static gboolean g_timeout_dispatch (GSource *source, GSourceFunc callback, gpointer user_data); +static gboolean g_child_watch_prepare (GSource *source, + gint *timeout); +static gboolean g_child_watch_check (GSource *source); +static gboolean g_child_watch_dispatch (GSource *source, + GSourceFunc callback, + gpointer user_data); static gboolean g_idle_prepare (GSource *source, gint *timeout); static gboolean g_idle_check (GSource *source); @@ -219,9 +238,31 @@ GSourceFunc callback, gpointer user_data); +/* The main_loop lock cannot be acquired while either the + * main_context_list lock is held, or the mutex for any + * individual main loop is held. + */ G_LOCK_DEFINE_STATIC (main_loop); + static GMainContext *default_main_context; static GSList *main_contexts_without_pipe = NULL; +static GMainContext *main_context_about_to_poll = NULL; + +/* Child status monitoring code */ +enum { + CHILD_WATCH_UNINITIALIZED, + CHILD_WATCH_INITIALIZED_SINGLE, + CHILD_WATCH_INITIALIZED_THREADED +}; +static gint child_watch_init_state = CHILD_WATCH_UNINITIALIZED; +static sig_atomic_t child_watch_count = 1; +static gint child_watch_wake_up_pipe[2] = {0, 0}; + +/* The main_context_list lock cannot be acquired while + * the mutex for any individual main loop is held. + */ +G_LOCK_DEFINE_STATIC (main_context_list); +static GSList *main_context_list = NULL; #if defined(G_PLATFORM_WIN32) && defined(__GNUC__) __declspec(dllexport) @@ -234,6 +275,14 @@ NULL }; +GSourceFuncs g_child_watch_funcs = +{ + g_child_watch_prepare, + g_child_watch_check, + g_child_watch_dispatch, + NULL +}; + #if defined(G_PLATFORM_WIN32) && defined(__GNUC__) __declspec(dllexport) #endif @@ -585,6 +634,10 @@ return; } + G_LOCK (main_context_list); + main_context_list = g_slist_remove (main_context_list, context); + G_UNLOCK (main_context_list); + source = context->source_list; while (source) { @@ -644,7 +697,6 @@ g_main_context_unref_and_unlock (context); } -#ifdef G_THREADS_ENABLED static void g_main_context_init_pipe (GMainContext *context) { @@ -669,8 +721,11 @@ g_main_context_add_poll_unlocked (context, 0, &context->wake_up_rec); } -void -g_main_thread_init () +/* called when initializing threads and the child_watch source, and + * when intializing the child_watch source when single threaded + */ +static void +add_pipes_to_main_contexts (void) { GSList *curr = main_contexts_without_pipe; while (curr) @@ -681,6 +736,14 @@ g_slist_free (main_contexts_without_pipe); main_contexts_without_pipe = NULL; } + +#ifdef G_THREADS_ENABLED + +void +_g_main_thread_init () +{ + add_pipes_to_main_contexts (); +} #endif /* G_THREADS_ENABLED */ /** @@ -729,7 +792,11 @@ context); #endif - return context; + G_LOCK (main_context_list); + main_context_list = g_slist_append (main_context_list, context); + G_UNLOCK (main_context_list); + + return context; } /** @@ -1542,11 +1609,11 @@ * g_source_remove: * @tag: the id of the source to remove. * - * Removes the source with the given id from the default main - * context. The id of a #GSource is given by g_source_get_id(), - * or will be returned by the functions g_source_attach(), - * g_idle_add(), g_idle_add_full(), g_timeout_add(), - * g_timeout_add_full(), g_io_add_watch, and g_io_add_watch_full(). + * Removes the source with the given id from the default main context. The id of + * a #GSource is given by g_source_get_id(), or will be returned by the + * functions g_source_attach(), g_idle_add(), g_idle_add_full(), + * g_timeout_add(), g_timeout_add_full(), g_child_watch_add(), + * g_child_watch_add_full(), g_io_add_watch(), and g_io_add_watch_full(). * * See also g_source_destroy(). * @@ -2357,6 +2424,17 @@ UNLOCK_CONTEXT (context); + /* main_context_about_to_poll is entirely for the SIGCHLD source. We normally + * count on SIGCHLD waking up the poll when it occurs during that time. + * However, if the SIGCHLD occurs between prepare and poll, we can miss it and + * block indefinitely, waiting for the child to exit. To avoid this, we store + * the context in main_context_about_to_poll in the expectation that the + * signal handler can deal with it. We expect that no code in prepare or + * query tries to send data to the wake up pipe, and that we can safely write + * a byte down it. + */ + + main_context_about_to_poll = context; some_ready = g_main_context_prepare (context, &max_priority); while ((nfds = g_main_context_query (context, max_priority, &timeout, fds, @@ -2373,7 +2451,8 @@ timeout = 0; g_main_context_poll (context, timeout, max_priority, fds, nfds); - + main_context_about_to_poll = NULL; + g_main_context_check (context, max_priority, fds, nfds); if (dispatch) @@ -3240,6 +3319,359 @@ interval, function, data, NULL); } +/* Child watch functions */ + +static gboolean +check_for_child_exited (GSource *source) +{ + GChildWatchSource *child_watch_source; + gboolean child_exited = FALSE; + gint count; + + /* protect against another SIGCHLD in the middle of this call */ + count = child_watch_count; + + child_watch_source = (GChildWatchSource *) source; + + if (child_watch_source->count < count) + { + gint child_status; + gint waitpid_status; + + do { + waitpid_status = waitpid (child_watch_source->pid, &child_status, WNOHANG); + } while (waitpid_status == (pid_t)-1 && errno == EINTR); + if (waitpid_status == child_watch_source->pid) + { + child_watch_source->child_status = child_status; + child_exited = TRUE; + } + else if (waitpid_status == -1) + { + /* errno can be EINVAL or ECHILD. I'm not sure what EINVAL could mean + * here. */ + child_watch_source->child_status = -1; + child_exited = TRUE; + } + child_watch_source->count = count; + } + + return child_exited; +} + +static gboolean +g_child_watch_prepare (GSource *source, + gint *timeout) +{ + *timeout = -1; + + return check_for_child_exited (source); +} + + +static gboolean +g_child_watch_check (GSource *source) +{ + return check_for_child_exited (source); +} + +static gboolean +g_child_watch_dispatch (GSource *source, + GSourceFunc callback, + gpointer user_data) +{ + GChildWatchSource *child_watch_source; + GChildWatchFunc child_watch_callback = (GChildWatchFunc) callback; + + child_watch_source = (GChildWatchSource *) source; + + if (!callback) + { + g_warning ("Child watch source dispatched without callback\n" + "You must call g_source_set_callback()."); + return FALSE; + } + + (child_watch_callback) (child_watch_source->pid, child_watch_source->child_status, user_data); + + /* We never keep a child watch source around as the child is gone */ + return FALSE; +} + +static void +g_child_watch_signal_handler (int signum) +{ + /* guard against us modifying errno during the write */ + int old_errno = errno; + + child_watch_count ++; + + if (child_watch_init_state == CHILD_WATCH_INITIALIZED_THREADED) + { + write (child_watch_wake_up_pipe[1], "B", 1); + } + else + { + if (main_context_about_to_poll) + { + g_main_context_wakeup_unlocked (main_context_about_to_poll); + } + } + errno = old_errno; +} + +static void +g_child_watch_set_up_signal_handler (void) +{ + sigset_t newset; + + signal (SIGCHLD, g_child_watch_signal_handler); + + sigemptyset (&newset); + sigaddset (&newset, SIGCHLD); + sigprocmask (SIG_UNBLOCK, &newset, NULL); +} + +static void +g_child_watch_source_init_single (void) +{ + g_assert (!g_thread_supported()); + g_assert (child_watch_init_state == CHILD_WATCH_UNINITIALIZED); + + add_pipes_to_main_contexts (); + + child_watch_init_state = CHILD_WATCH_INITIALIZED_SINGLE; + + g_child_watch_set_up_signal_handler (); +} + +/* Separate thread that sits around waiting for messages from the signal + * handler. + */ +static gpointer +child_watch_helper_thread (gpointer data) +{ + while (1) + { + gchar b[128]; + GSList *list; + + read (child_watch_wake_up_pipe[0], b, sizeof(b)); + + /* We were woken up. Wake up all other contexts in all other threads */ + G_LOCK (main_context_list); + for (list = main_context_list; list; list = list->next) + { + GMainContext *context; + + context = list->data; + g_main_context_wakeup (context); + } + G_UNLOCK (main_context_list); + } + return NULL; +} + +#ifdef G_THREADS_ENABLED + +static void +g_child_watch_source_init_multi_threaded (void) +{ + GError *error = NULL; + + g_assert (g_thread_supported()); + + if (pipe (child_watch_wake_up_pipe) < 0) + g_error ("Cannot create wake up pipe: %s\n", g_strerror (errno)); + fcntl (child_watch_wake_up_pipe[1], F_SETFL, + O_NONBLOCK | fcntl (child_watch_wake_up_pipe[1], F_GETFL)); + + /* We create a helper thread that polls on the wakeup pipe indefinitely */ + if (g_thread_create_full (child_watch_helper_thread, + NULL, 0, + FALSE, FALSE, + G_THREAD_PRIORITY_NORMAL, &error) == NULL) + g_error ("Cannot create a thread to monitor child exit status: %s\n", error->message); + child_watch_init_state = CHILD_WATCH_INITIALIZED_THREADED; + + g_child_watch_set_up_signal_handler (); +} + +static void +g_child_watch_source_init_promote_single_to_threaded (void) +{ + g_child_watch_source_init_multi_threaded (); +} + +#endif + +/** + * g_child_watch_source_init: + * @void: + * + * Initializes a signal handler so that #GChildWatchSource can monitor children. + * In most programs it is not necessary to call this function, as the sources + * will initialize themselves appropriately. The one time it can be useful is + * when an external SIGCHLD handler that calls waitpid() is already installed. + * In this case, there is a potential race between the child exiting and the + * source installing its own handler. + * + **/ +void +g_child_watch_source_init (void) +{ + G_LOCK_DEFINE_STATIC (child_watch_source_init_lock); + + G_LOCK (child_watch_source_init_lock); + +#ifdef G_THREADS_ENABLED + if (g_thread_supported()) + { + if (child_watch_init_state == CHILD_WATCH_UNINITIALIZED) + g_child_watch_source_init_multi_threaded (); + else if (child_watch_init_state == CHILD_WATCH_INITIALIZED_SINGLE) + g_child_watch_source_init_promote_single_to_threaded (); + } + else + { +#endif + if (child_watch_init_state == CHILD_WATCH_UNINITIALIZED) + g_child_watch_source_init_single (); +#ifdef G_THREADS_ENABLED + } +#endif + + G_UNLOCK (child_watch_source_init_lock); +} + +/** + * g_child_watch_source_new: + * @pid: process id of a child process to watch + * + * Creates a new child_watch source. This source is used to monitor a + * child process. It will be triggered once when a SIGCHLD occurs and + * the process specified by pid has exited. Any application using + * this source should not modify the SIGCHILD signal handler. + * Instead, they should use a child watch source to keep track of the + * exit status of any children that they care about. Additionally, as + * glib installs a SIGCHLD signal handler, system calls can be + * interrupted, and should be checked for EINTR. + * + * The process id passed in must be a positive pid, and must point to + * an existing pid. Also, when multiple sources of the same pid are + * added, only one of them will be called. + * + * The source will not initially be associated with any #GMainContext and must + * be added to one with g_source_attach() before it will be executed. Unlike + * other sources, any callback associated with it will only be called at most + * once and no return value is expected. + * + * Some thread implementations are buggy and one thread + * cannot call waitpid() on a child created in a different thread; if + * you are using this functionality in a threaded program, you may + * need to structure your program so that child watches are always + * added to a GMainContext running in the same thread as where the + * child was created. + * Return value: the newly-created child watch source + **/ +GSource * +g_child_watch_source_new (gint pid) +{ + GSource *source; + GChildWatchSource *child_watch_source; + + g_return_val_if_fail (pid > 0, NULL); + + source = g_source_new (&g_child_watch_funcs, sizeof (GChildWatchSource)); + child_watch_source = (GChildWatchSource *)source; + + g_child_watch_source_init (); + + child_watch_source->pid = pid; + /* We want to run on the first pass no matter what, to catch the case where + * the child has exited before the signal handler has been installed. */ + child_watch_source->count = child_watch_count - 1; + + return source; +} + +/** + * g_child_watch_add_full: + * @priority: the priority of the child watch source. Typically this will be in the + * range between #G_PRIORITY_DEFAULT_IDLE and #G_PRIORITY_HIGH_IDLE. + * @pid: process id of a child process to watch + * @function: function to call + * @data: data to pass to @function + * @notify: function to call when the idle is removed, or %NULL + * + * Sets a function to be called when the child indicated by pid exits, at a + * default priority, #G_PRIORITY_DEFAULT. See g_child_watch_source_new() for + * additional comments regarding this source. + * + * Return value: the id of event source. + **/ +guint +g_child_watch_add_full (gint priority, + gint pid, + GChildWatchFunc function, + gpointer data, + GDestroyNotify notify) +{ + GSource *source; + guint id; + + g_return_val_if_fail (function != NULL, 0); + + source = g_child_watch_source_new (pid); + + if (priority != G_PRIORITY_DEFAULT) + g_source_set_priority (source, priority); + + g_source_set_callback (source, (GSourceFunc) function, data, notify); + id = g_source_attach (source, NULL); + g_source_unref (source); + + return id; +} + +/** + * g_child_watch_add: + * @pid: process id of a child process to watch + * @function: function to call + * @data: data to pass to @function + * + * Sets a function to be called when the child indicated by pid exits, at a + * default priority, #G_PRIORITY_DEFAULT. + * + * + * gint pid = fork (); + * if (pid > 0) + * { + * g_child_watch_add (pid, function, data); + * } + * else if (pid == 0) /* child */ + * { + * /* The child process */ + * sleep (1); + * _exit (0); + * } + * else + * /* error */ + * + * + * See g_child_watch_source_new() for additional inforamtion. + * + * Return value: the id of event source. + **/ +guint +g_child_watch_add (gint pid, + GChildWatchFunc function, + gpointer data) +{ + return g_child_watch_add_full (G_PRIORITY_DEFAULT, pid, function, data, NULL); +} + + /* Idle functions */ static gboolean Index: glib/gmain.h =================================================================== RCS file: /cvs/gnome/glib/glib/gmain.h,v retrieving revision 1.15.2.1 diff -u -r1.15.2.1 gmain.h --- glib/gmain.h 5 Jun 2003 22:14:25 -0000 1.15.2.1 +++ glib/gmain.h 15 Sep 2003 18:01:38 -0000 @@ -32,6 +32,9 @@ typedef struct _GSourceFuncs GSourceFuncs; typedef gboolean (*GSourceFunc) (gpointer data); +typedef void (*GChildWatchFunc) (gint pid, + gint status, + gpointer data); struct _GSource { @@ -243,8 +246,10 @@ /* Specific source types */ -GSource *g_idle_source_new (void); -GSource *g_timeout_source_new (guint interval); +GSource *g_idle_source_new (void); +void g_child_watch_source_init (void); +GSource *g_child_watch_source_new (gint pid); +GSource *g_timeout_source_new (guint interval); /* Miscellaneous functions */ @@ -278,25 +283,34 @@ gboolean g_source_remove_by_funcs_user_data (GSourceFuncs *funcs, gpointer user_data); -/* Idles and timeouts */ -guint g_timeout_add_full (gint priority, - guint interval, - GSourceFunc function, - gpointer data, - GDestroyNotify notify); -guint g_timeout_add (guint interval, - GSourceFunc function, - gpointer data); -guint g_idle_add (GSourceFunc function, - gpointer data); -guint g_idle_add_full (gint priority, - GSourceFunc function, - gpointer data, - GDestroyNotify notify); -gboolean g_idle_remove_by_data (gpointer data); +/* Idles, child watches and timeouts */ +guint g_timeout_add_full (gint priority, + guint interval, + GSourceFunc function, + gpointer data, + GDestroyNotify notify); +guint g_timeout_add (guint interval, + GSourceFunc function, + gpointer data); +guint g_child_watch_add_full (gint priority, + gint pid, + GChildWatchFunc function, + gpointer data, + GDestroyNotify notify); +guint g_child_watch_add (gint pid, + GChildWatchFunc function, + gpointer data); +guint g_idle_add (GSourceFunc function, + gpointer data); +guint g_idle_add_full (gint priority, + GSourceFunc function, + gpointer data, + GDestroyNotify notify); +gboolean g_idle_remove_by_data (gpointer data); /* Hook for GClosure / GSource integration. Don't touch */ GLIB_VAR GSourceFuncs g_timeout_funcs; +GLIB_VAR GSourceFuncs g_child_watch_funcs; GLIB_VAR GSourceFuncs g_idle_funcs; G_END_DECLS Index: gobject/gsourceclosure.c =================================================================== RCS file: /cvs/gnome/glib/gobject/gsourceclosure.c,v retrieving revision 1.5 diff -u -r1.5 gsourceclosure.c --- gobject/gsourceclosure.c 22 Nov 2001 18:55:05 -0000 1.5 +++ gobject/gsourceclosure.c 15 Sep 2003 18:01:38 -0000 @@ -140,7 +140,8 @@ if (source->source_funcs == &g_io_watch_funcs) closure_callback = (GSourceFunc)io_watch_closure_callback; else if (source->source_funcs == &g_timeout_funcs || - source->source_funcs == &g_idle_funcs) + source->source_funcs == &g_idle_funcs || + source->source_funcs == &g_child_watch_funcs) closure_callback = source_closure_callback; } Index: ChangeLog =================================================================== RCS file: /cvs/gnome/glib/ChangeLog,v retrieving revision 1.1255.2.98 diff -u -r1.1255.2.98 ChangeLog --- ChangeLog 3 Sep 2003 19:19:13 -0000 1.1255.2.98 +++ ChangeLog 15 Sep 2003 18:01:38 -0000 @@ -1,4 +1,20 @@ -2003-09-03 Tor Lillqvist +Fri Sep 12 13:50:24 2003 Jonathan Blandford + + * glib/gmain.c (g_child_watch_source_new): Source to handle + SIGCHLD messages. This way multiple libraries can monitor their + children's exit status without fighting over who installs the + signal handler. + + * glib/gmain.c (g_child_watch_add_full): function to create a + GChildWatch source and attach it to a loop. + + * glib/gmain.c (g_child_watch_add): simple function to create a + GChildWatch source and attach it to a GMainLoop. + + * glib/gmain.c (g_child_watch_source_init): initialization + function for GChildWatchSource + +003-09-03 Tor Lillqvist * glibconfig.h.win32.in: Use G_GINT64_CONSTANT to define G_MININT64 etc, like in the configure-generated glibnconfig.h.