diff --git a/.cvsignore b/.cvsignore index 34f2b1883..f08502b7a 100644 --- a/.cvsignore +++ b/.cvsignore @@ -28,6 +28,8 @@ stamp-h.in testgdate testgdateparser testglib +timeloop +timeloop-basic annotations logs glib.rc diff --git a/ChangeLog b/ChangeLog index 41e6843f5..92184925f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,35 @@ +Tue Dec 5 12:23:04 2000 Owen Taylor + + * gmain.[hc]: Major change in API for creating sources + to handle multiple main loops (GMainContext *). + + GSources are now exposed as GSource * and implemented + with structure derivation. + + * giochannel.[ch]: Changed vtable for GIOChannel to correspond + to the new mainloop API, add g_io_channel_create_watch(). + + * gtypes.h: Move GTimeVal here. + + * gthread.h: Remove gmain.h include to avoid circularity. + + * giounix.c: Update for new GMain API. + + * giowin32.c: Update for new GMain API. (No check for + proper compilation or working.) + + * timeloop.c timeloop-basic.c: A benchmarking program for + the main loop comparing the main loop against a + hand-written (timeloop-basic.c) variant. + + * tests/mainloop-test.c: New torture test of mainloop. + + * docs/Changes-2.0.txt: Started. Added text about + changes to GMain. + + * gmain.c (g_main_add_poll_unlocked): Initial fd->revents + to zero. (#8482, Benjamin Kahn) + 2000-12-01 Tor Lillqvist * {.,*}/makefile.msc.in: Include make.msc from GLib's build subdir. diff --git a/ChangeLog.pre-2-0 b/ChangeLog.pre-2-0 index 41e6843f5..92184925f 100644 --- a/ChangeLog.pre-2-0 +++ b/ChangeLog.pre-2-0 @@ -1,3 +1,35 @@ +Tue Dec 5 12:23:04 2000 Owen Taylor + + * gmain.[hc]: Major change in API for creating sources + to handle multiple main loops (GMainContext *). + + GSources are now exposed as GSource * and implemented + with structure derivation. + + * giochannel.[ch]: Changed vtable for GIOChannel to correspond + to the new mainloop API, add g_io_channel_create_watch(). + + * gtypes.h: Move GTimeVal here. + + * gthread.h: Remove gmain.h include to avoid circularity. + + * giounix.c: Update for new GMain API. + + * giowin32.c: Update for new GMain API. (No check for + proper compilation or working.) + + * timeloop.c timeloop-basic.c: A benchmarking program for + the main loop comparing the main loop against a + hand-written (timeloop-basic.c) variant. + + * tests/mainloop-test.c: New torture test of mainloop. + + * docs/Changes-2.0.txt: Started. Added text about + changes to GMain. + + * gmain.c (g_main_add_poll_unlocked): Initial fd->revents + to zero. (#8482, Benjamin Kahn) + 2000-12-01 Tor Lillqvist * {.,*}/makefile.msc.in: Include make.msc from GLib's build subdir. diff --git a/ChangeLog.pre-2-10 b/ChangeLog.pre-2-10 index 41e6843f5..92184925f 100644 --- a/ChangeLog.pre-2-10 +++ b/ChangeLog.pre-2-10 @@ -1,3 +1,35 @@ +Tue Dec 5 12:23:04 2000 Owen Taylor + + * gmain.[hc]: Major change in API for creating sources + to handle multiple main loops (GMainContext *). + + GSources are now exposed as GSource * and implemented + with structure derivation. + + * giochannel.[ch]: Changed vtable for GIOChannel to correspond + to the new mainloop API, add g_io_channel_create_watch(). + + * gtypes.h: Move GTimeVal here. + + * gthread.h: Remove gmain.h include to avoid circularity. + + * giounix.c: Update for new GMain API. + + * giowin32.c: Update for new GMain API. (No check for + proper compilation or working.) + + * timeloop.c timeloop-basic.c: A benchmarking program for + the main loop comparing the main loop against a + hand-written (timeloop-basic.c) variant. + + * tests/mainloop-test.c: New torture test of mainloop. + + * docs/Changes-2.0.txt: Started. Added text about + changes to GMain. + + * gmain.c (g_main_add_poll_unlocked): Initial fd->revents + to zero. (#8482, Benjamin Kahn) + 2000-12-01 Tor Lillqvist * {.,*}/makefile.msc.in: Include make.msc from GLib's build subdir. diff --git a/ChangeLog.pre-2-12 b/ChangeLog.pre-2-12 index 41e6843f5..92184925f 100644 --- a/ChangeLog.pre-2-12 +++ b/ChangeLog.pre-2-12 @@ -1,3 +1,35 @@ +Tue Dec 5 12:23:04 2000 Owen Taylor + + * gmain.[hc]: Major change in API for creating sources + to handle multiple main loops (GMainContext *). + + GSources are now exposed as GSource * and implemented + with structure derivation. + + * giochannel.[ch]: Changed vtable for GIOChannel to correspond + to the new mainloop API, add g_io_channel_create_watch(). + + * gtypes.h: Move GTimeVal here. + + * gthread.h: Remove gmain.h include to avoid circularity. + + * giounix.c: Update for new GMain API. + + * giowin32.c: Update for new GMain API. (No check for + proper compilation or working.) + + * timeloop.c timeloop-basic.c: A benchmarking program for + the main loop comparing the main loop against a + hand-written (timeloop-basic.c) variant. + + * tests/mainloop-test.c: New torture test of mainloop. + + * docs/Changes-2.0.txt: Started. Added text about + changes to GMain. + + * gmain.c (g_main_add_poll_unlocked): Initial fd->revents + to zero. (#8482, Benjamin Kahn) + 2000-12-01 Tor Lillqvist * {.,*}/makefile.msc.in: Include make.msc from GLib's build subdir. diff --git a/ChangeLog.pre-2-2 b/ChangeLog.pre-2-2 index 41e6843f5..92184925f 100644 --- a/ChangeLog.pre-2-2 +++ b/ChangeLog.pre-2-2 @@ -1,3 +1,35 @@ +Tue Dec 5 12:23:04 2000 Owen Taylor + + * gmain.[hc]: Major change in API for creating sources + to handle multiple main loops (GMainContext *). + + GSources are now exposed as GSource * and implemented + with structure derivation. + + * giochannel.[ch]: Changed vtable for GIOChannel to correspond + to the new mainloop API, add g_io_channel_create_watch(). + + * gtypes.h: Move GTimeVal here. + + * gthread.h: Remove gmain.h include to avoid circularity. + + * giounix.c: Update for new GMain API. + + * giowin32.c: Update for new GMain API. (No check for + proper compilation or working.) + + * timeloop.c timeloop-basic.c: A benchmarking program for + the main loop comparing the main loop against a + hand-written (timeloop-basic.c) variant. + + * tests/mainloop-test.c: New torture test of mainloop. + + * docs/Changes-2.0.txt: Started. Added text about + changes to GMain. + + * gmain.c (g_main_add_poll_unlocked): Initial fd->revents + to zero. (#8482, Benjamin Kahn) + 2000-12-01 Tor Lillqvist * {.,*}/makefile.msc.in: Include make.msc from GLib's build subdir. diff --git a/ChangeLog.pre-2-4 b/ChangeLog.pre-2-4 index 41e6843f5..92184925f 100644 --- a/ChangeLog.pre-2-4 +++ b/ChangeLog.pre-2-4 @@ -1,3 +1,35 @@ +Tue Dec 5 12:23:04 2000 Owen Taylor + + * gmain.[hc]: Major change in API for creating sources + to handle multiple main loops (GMainContext *). + + GSources are now exposed as GSource * and implemented + with structure derivation. + + * giochannel.[ch]: Changed vtable for GIOChannel to correspond + to the new mainloop API, add g_io_channel_create_watch(). + + * gtypes.h: Move GTimeVal here. + + * gthread.h: Remove gmain.h include to avoid circularity. + + * giounix.c: Update for new GMain API. + + * giowin32.c: Update for new GMain API. (No check for + proper compilation or working.) + + * timeloop.c timeloop-basic.c: A benchmarking program for + the main loop comparing the main loop against a + hand-written (timeloop-basic.c) variant. + + * tests/mainloop-test.c: New torture test of mainloop. + + * docs/Changes-2.0.txt: Started. Added text about + changes to GMain. + + * gmain.c (g_main_add_poll_unlocked): Initial fd->revents + to zero. (#8482, Benjamin Kahn) + 2000-12-01 Tor Lillqvist * {.,*}/makefile.msc.in: Include make.msc from GLib's build subdir. diff --git a/ChangeLog.pre-2-6 b/ChangeLog.pre-2-6 index 41e6843f5..92184925f 100644 --- a/ChangeLog.pre-2-6 +++ b/ChangeLog.pre-2-6 @@ -1,3 +1,35 @@ +Tue Dec 5 12:23:04 2000 Owen Taylor + + * gmain.[hc]: Major change in API for creating sources + to handle multiple main loops (GMainContext *). + + GSources are now exposed as GSource * and implemented + with structure derivation. + + * giochannel.[ch]: Changed vtable for GIOChannel to correspond + to the new mainloop API, add g_io_channel_create_watch(). + + * gtypes.h: Move GTimeVal here. + + * gthread.h: Remove gmain.h include to avoid circularity. + + * giounix.c: Update for new GMain API. + + * giowin32.c: Update for new GMain API. (No check for + proper compilation or working.) + + * timeloop.c timeloop-basic.c: A benchmarking program for + the main loop comparing the main loop against a + hand-written (timeloop-basic.c) variant. + + * tests/mainloop-test.c: New torture test of mainloop. + + * docs/Changes-2.0.txt: Started. Added text about + changes to GMain. + + * gmain.c (g_main_add_poll_unlocked): Initial fd->revents + to zero. (#8482, Benjamin Kahn) + 2000-12-01 Tor Lillqvist * {.,*}/makefile.msc.in: Include make.msc from GLib's build subdir. diff --git a/ChangeLog.pre-2-8 b/ChangeLog.pre-2-8 index 41e6843f5..92184925f 100644 --- a/ChangeLog.pre-2-8 +++ b/ChangeLog.pre-2-8 @@ -1,3 +1,35 @@ +Tue Dec 5 12:23:04 2000 Owen Taylor + + * gmain.[hc]: Major change in API for creating sources + to handle multiple main loops (GMainContext *). + + GSources are now exposed as GSource * and implemented + with structure derivation. + + * giochannel.[ch]: Changed vtable for GIOChannel to correspond + to the new mainloop API, add g_io_channel_create_watch(). + + * gtypes.h: Move GTimeVal here. + + * gthread.h: Remove gmain.h include to avoid circularity. + + * giounix.c: Update for new GMain API. + + * giowin32.c: Update for new GMain API. (No check for + proper compilation or working.) + + * timeloop.c timeloop-basic.c: A benchmarking program for + the main loop comparing the main loop against a + hand-written (timeloop-basic.c) variant. + + * tests/mainloop-test.c: New torture test of mainloop. + + * docs/Changes-2.0.txt: Started. Added text about + changes to GMain. + + * gmain.c (g_main_add_poll_unlocked): Initial fd->revents + to zero. (#8482, Benjamin Kahn) + 2000-12-01 Tor Lillqvist * {.,*}/makefile.msc.in: Include make.msc from GLib's build subdir. diff --git a/Makefile.am b/Makefile.am index e29f0a6dc..a4224af38 100644 --- a/Makefile.am +++ b/Makefile.am @@ -165,10 +165,11 @@ libglib_1_3_la_LDFLAGS = \ -version-info $(LT_CURRENT):$(LT_REVISION):$(LT_AGE) \ -export-dynamic -noinst_PROGRAMS = testglib testgdate testgdateparser +noinst_PROGRAMS = testglib testgdate testgdateparser timeloop testglib_LDADD = libglib-1.3.la testgdate_LDADD = libglib-1.3.la testgdateparser_LDADD = libglib-1.3.la +timeloop_LDADD = libglib-1.3.la m4datadir = $(datadir)/aclocal m4data_DATA = glib-2.0.m4 diff --git a/docs/Changes-2.0.txt b/docs/Changes-2.0.txt new file mode 100644 index 000000000..8bf2819ba --- /dev/null +++ b/docs/Changes-2.0.txt @@ -0,0 +1,24 @@ +* The event loop functionality GMain has extensively been revised to + support multiple separate main loops in separate threads. All sources + (timeouts, idle functions, etc.) are associated with a GMainContext. + + Compatibility functions exist so that most application code dealing with + the main loop will continue to work. However, code that creates + new custom types of sources will require modification. + + The main changes here are: + + - Sources are now exposed as GSource *, rather than simply as numeric + IDS. + + - New types of sources are created by structure "derivation" from GSource, + so the source_data parameter to the GSource vfuncs has been + replaced with a GSource *. + + - Sources are first created, then later added to a specific GMainContext + + - Dispatching has been modified so both the callback and data are passed + in to the ->dispatch() vfunc. + + To go along with this change, the vtable for GIOChannel has changed and + add_watch() has been replaced by create_watch(). \ No newline at end of file diff --git a/gfileutils.c b/gfileutils.c index ce802426d..569983f4a 100644 --- a/gfileutils.c +++ b/gfileutils.c @@ -27,6 +27,7 @@ #include #endif #include +#include #include #include #include diff --git a/giochannel.c b/giochannel.c index 29a200231..853a0adc7 100644 --- a/giochannel.c +++ b/giochannel.c @@ -104,6 +104,15 @@ g_io_channel_close (GIOChannel *channel) channel->funcs->io_close (channel); } +GSource * +g_io_create_watch (GIOChannel *channel, + GIOCondition condition) +{ + g_return_val_if_fail (channel != NULL, NULL); + + return channel->funcs->io_create_watch (channel, condition); +} + guint g_io_add_watch_full (GIOChannel *channel, gint priority, @@ -112,10 +121,17 @@ g_io_add_watch_full (GIOChannel *channel, gpointer user_data, GDestroyNotify notify) { + GSource *source; + g_return_val_if_fail (channel != NULL, 0); - return channel->funcs->io_add_watch (channel, priority, condition, - func, user_data, notify); + source = g_io_create_watch (channel, condition); + + if (priority != G_PRIORITY_DEFAULT) + g_source_set_priority (source, priority); + g_source_set_callback (source, (GSourceFunc)func, user_data, notify); + + return g_source_attach (source, NULL); } guint @@ -124,5 +140,5 @@ g_io_add_watch (GIOChannel *channel, GIOFunc func, gpointer user_data) { - return g_io_add_watch_full (channel, 0, condition, func, user_data, NULL); + return g_io_add_watch_full (channel, G_PRIORITY_DEFAULT, condition, func, user_data, NULL); } diff --git a/giochannel.h b/giochannel.h index 7992a69e4..e9a64c0b3 100644 --- a/giochannel.h +++ b/giochannel.h @@ -27,6 +27,7 @@ #ifndef __G_IOCHANNEL_H__ #define __G_IOCHANNEL_H__ +#include #include G_BEGIN_DECLS @@ -71,25 +72,21 @@ typedef gboolean (*GIOFunc) (GIOChannel *source, gpointer data); struct _GIOFuncs { - GIOError (*io_read) (GIOChannel *channel, - gchar *buf, - guint count, - guint *bytes_read); - GIOError (*io_write) (GIOChannel *channel, - gchar *buf, - guint count, - guint *bytes_written); - GIOError (*io_seek) (GIOChannel *channel, - gint offset, - GSeekType type); - void (*io_close) (GIOChannel *channel); - guint (*io_add_watch) (GIOChannel *channel, - gint priority, - GIOCondition condition, - GIOFunc func, - gpointer user_data, - GDestroyNotify notify); - void (*io_free) (GIOChannel *channel); + GIOError (*io_read) (GIOChannel *channel, + gchar *buf, + guint count, + guint *bytes_read); + GIOError (*io_write) (GIOChannel *channel, + gchar *buf, + guint count, + guint *bytes_written); + GIOError (*io_seek) (GIOChannel *channel, + gint offset, + GSeekType type); + void (*io_close) (GIOChannel *channel); + GSource * (*io_create_watch) (GIOChannel *channel, + GIOCondition condition); + void (*io_free) (GIOChannel *channel); }; void g_io_channel_init (GIOChannel *channel); @@ -113,6 +110,8 @@ guint g_io_add_watch_full (GIOChannel *channel, GIOFunc func, gpointer user_data, GDestroyNotify notify); +GSource *g_io_create_watch (GIOChannel *channel, + GIOCondition condition); guint g_io_add_watch (GIOChannel *channel, GIOCondition condition, GIOFunc func, diff --git a/giounix.c b/giounix.c index 2a06dac80..ca98e0c86 100644 --- a/giounix.c +++ b/giounix.c @@ -43,12 +43,15 @@ typedef struct _GIOUnixChannel GIOUnixChannel; typedef struct _GIOUnixWatch GIOUnixWatch; -struct _GIOUnixChannel { +struct _GIOUnixChannel +{ GIOChannel channel; gint fd; }; -struct _GIOUnixWatch { +struct _GIOUnixWatch +{ + GSource source; GPollFD pollfd; GIOChannel *channel; GIOCondition condition; @@ -56,37 +59,29 @@ struct _GIOUnixWatch { }; -static GIOError g_io_unix_read (GIOChannel *channel, - gchar *buf, - guint count, - guint *bytes_written); - -static GIOError g_io_unix_write(GIOChannel *channel, - gchar *buf, - guint count, - guint *bytes_written); -static GIOError g_io_unix_seek (GIOChannel *channel, - gint offset, - GSeekType type); -static void g_io_unix_close (GIOChannel *channel); -static void g_io_unix_free (GIOChannel *channel); -static guint g_io_unix_add_watch (GIOChannel *channel, - gint priority, - GIOCondition condition, - GIOFunc func, - gpointer user_data, - GDestroyNotify notify); -static gboolean g_io_unix_prepare (gpointer source_data, - GTimeVal *current_time, - gint *timeout, - gpointer user_data); -static gboolean g_io_unix_check (gpointer source_data, - GTimeVal *current_time, - gpointer user_data); -static gboolean g_io_unix_dispatch (gpointer source_data, - GTimeVal *current_time, - gpointer user_data); -static void g_io_unix_destroy (gpointer source_data); +static GIOError g_io_unix_read (GIOChannel *channel, + gchar *buf, + guint count, + guint *bytes_written); +static GIOError g_io_unix_write (GIOChannel *channel, + gchar *buf, + guint count, + guint *bytes_written); +static GIOError g_io_unix_seek (GIOChannel *channel, + gint offset, + GSeekType type); +static void g_io_unix_close (GIOChannel *channel); +static void g_io_unix_free (GIOChannel *channel); +static GSource *g_io_unix_create_watch (GIOChannel *channel, + GIOCondition condition); + +static gboolean g_io_unix_prepare (GSource *source, + gint *timeout); +static gboolean g_io_unix_check (GSource *source); +static gboolean g_io_unix_dispatch (GSource *source, + GSourceFunc callback, + gpointer user_data); +static void g_io_unix_destroy (GSource *source); GSourceFuncs unix_watch_funcs = { g_io_unix_prepare, @@ -100,51 +95,53 @@ GIOFuncs unix_channel_funcs = { g_io_unix_write, g_io_unix_seek, g_io_unix_close, - g_io_unix_add_watch, + g_io_unix_create_watch, g_io_unix_free, }; static gboolean -g_io_unix_prepare (gpointer source_data, - GTimeVal *current_time, - gint *timeout, - gpointer user_data) +g_io_unix_prepare (GSource *source, + gint *timeout) { *timeout = -1; return FALSE; } static gboolean -g_io_unix_check (gpointer source_data, - GTimeVal *current_time, - gpointer user_data) +g_io_unix_check (GSource *source) { - GIOUnixWatch *data = source_data; + GIOUnixWatch *watch = (GIOUnixWatch *)source; - return (data->pollfd.revents & data->condition); + return (watch->pollfd.revents & watch->condition); } static gboolean -g_io_unix_dispatch (gpointer source_data, - GTimeVal *current_time, - gpointer user_data) +g_io_unix_dispatch (GSource *source, + GSourceFunc callback, + gpointer user_data) { - GIOUnixWatch *data = source_data; + GIOFunc func = (GIOFunc)callback; + GIOUnixWatch *watch = (GIOUnixWatch *)source; - return (*data->callback)(data->channel, - data->pollfd.revents & data->condition, - user_data); + if (!func) + { + g_warning ("IO watch dispatched without callback\n" + "You must call g_source_connect()."); + return FALSE; + } + + return (*func) (watch->channel, + watch->pollfd.revents & watch->condition, + user_data); } static void -g_io_unix_destroy (gpointer source_data) +g_io_unix_destroy (GSource *source) { - GIOUnixWatch *data = source_data; + GIOUnixWatch *watch = (GIOUnixWatch *)source; - g_main_remove_poll (&data->pollfd); - g_io_channel_unref (data->channel); - g_free (data); + g_io_channel_unref (watch->channel); } static GIOError @@ -269,29 +266,29 @@ g_io_unix_free (GIOChannel *channel) g_free (unix_channel); } -static guint -g_io_unix_add_watch (GIOChannel *channel, - gint priority, - GIOCondition condition, - GIOFunc func, - gpointer user_data, - GDestroyNotify notify) +static GSource * +g_io_unix_create_watch (GIOChannel *channel, + GIOCondition condition) { - GIOUnixWatch *watch = g_new (GIOUnixWatch, 1); GIOUnixChannel *unix_channel = (GIOUnixChannel *)channel; + GSource *source; + GIOUnixWatch *watch; + + + source = g_source_new (&unix_watch_funcs, sizeof (GIOUnixWatch)); + watch = (GIOUnixWatch *)source; watch->channel = channel; g_io_channel_ref (channel); - - watch->callback = func; + watch->condition = condition; watch->pollfd.fd = unix_channel->fd; watch->pollfd.events = condition; - g_main_add_poll (&watch->pollfd, priority); + g_source_add_poll (source, &watch->pollfd); - return g_source_add (priority, TRUE, &unix_watch_funcs, watch, user_data, notify); + return source; } GIOChannel * diff --git a/giowin32.c b/giowin32.c index 560d714ba..f4e74ccc8 100644 --- a/giowin32.c +++ b/giowin32.c @@ -102,6 +102,7 @@ struct _GIOWin32Channel { #define UNLOCK(mutex) LeaveCriticalSection (&mutex) struct _GIOWin32Watch { + GSource source; GPollFD pollfd; GIOChannel *channel; GIOCondition condition; @@ -319,10 +320,8 @@ buffer_read (GIOWin32Channel *channel, } static gboolean -g_io_win32_prepare (gpointer source_data, - GTimeVal *current_time, - gint *timeout, - gpointer user_data) +g_io_win32_prepare (GSource *source, + gint *timeout) { *timeout = -1; @@ -330,12 +329,10 @@ g_io_win32_prepare (gpointer source_data, } static gboolean -g_io_win32_check (gpointer source_data, - GTimeVal *current_time, - gpointer user_data) +g_io_win32_check (GSource *source) { - GIOWin32Watch *data = source_data; - GIOWin32Channel *channel = (GIOWin32Channel *) data->channel; + GIOWin32Watch *watch = (GIOWin32Watch *)source; + GIOWin32Channel *channel = (GIOWin32Channel *) watch->channel; /* If the thread has died, we have encountered EOF. If the buffer * also is emtpty set the HUP bit. @@ -345,34 +342,31 @@ g_io_win32_check (gpointer source_data, if (channel->debug) g_print ("g_io_win32_check: setting G_IO_HUP thread %#x rdp=%d wrp=%d\n", channel->thread_id, channel->rdp, channel->wrp); - data->pollfd.revents |= G_IO_HUP; + watch->pollfd.revents |= G_IO_HUP; return TRUE; } - return (data->pollfd.revents & data->condition); + return (watch->pollfd.revents & watch->condition); } static gboolean -g_io_win32_dispatch (gpointer source_data, - GTimeVal *current_time, - gpointer user_data) - +g_io_win32_dispatch (GSource *source, + GSourceFunc callback, + gpointer user_data) { - GIOWin32Watch *data = source_data; + GIOWin32Watch *watch = (GIOWin32Watch *)source; - return (*data->callback) (data->channel, - data->pollfd.revents & data->condition, - user_data); + return (*callback) (watch->channel, + watch->pollfd.revents & watch->condition, + user_data); } static void -g_io_win32_destroy (gpointer source_data) +g_io_win32_destroy (GSource *source) { - GIOWin32Watch *data = source_data; + GIOWin32Watch *watch = (GIOWin32Watch *)source; - g_main_remove_poll (&data->pollfd); - g_io_channel_unref (data->channel); - g_free (data); + g_io_channel_unref (watch->channel); } static GSourceFuncs win32_watch_funcs = { @@ -382,22 +376,21 @@ static GSourceFuncs win32_watch_funcs = { g_io_win32_destroy }; -static guint -g_io_win32_add_watch (GIOChannel *channel, - gint priority, - GIOCondition condition, - GIOFunc func, - gpointer user_data, - GDestroyNotify notify, - int (*reader) (int, guchar *, int)) +static GSource * +g_io_win32_create_watch (GIOChannel *channel, + GIOCondition condition, + int (*reader) (int, guchar *, int))) { - GIOWin32Watch *watch = g_new (GIOWin32Watch, 1); GIOWin32Channel *win32_channel = (GIOWin32Channel *) channel; + GIOWin32Watch *watch; + GSource *source; + + source = g_source_new (&win32_watch_funcs, sizeof (GIOWin32Watch)); + watch = (GIOWin32Watch *)source; watch->channel = channel; g_io_channel_ref (channel); - watch->callback = func; watch->condition = condition; if (win32_channel->data_avail_event == NULL) @@ -407,16 +400,15 @@ g_io_win32_add_watch (GIOChannel *channel, watch->pollfd.events = condition; if (win32_channel->debug) - g_print ("g_io_win32_add_watch: fd:%d handle:%#x\n", + g_print ("g_io_win32_create_watch: fd:%d handle:%#x\n", win32_channel->fd, watch->pollfd.fd); if (win32_channel->thread_id == 0) create_reader_thread (win32_channel, reader); - g_main_add_poll (&watch->pollfd, priority); + g_source_add_poll (source, &watch->pollfd); - return g_source_add (priority, TRUE, &win32_watch_funcs, watch, - user_data, notify); + return source; } static GIOError @@ -490,29 +482,27 @@ g_io_win32_free (GIOChannel *channel) g_free (win32_channel); } -static guint -g_io_win32_msg_add_watch (GIOChannel *channel, - gint priority, - GIOCondition condition, - GIOFunc func, - gpointer user_data, - GDestroyNotify notify) +static GSource * +g_io_win32_msg_create_watch (GIOChannel *channel, + GIOCondition condition) { - GIOWin32Watch *watch = g_new (GIOWin32Watch, 1); + GIOWin32Watch *watch; + GSource *source; + + source = g_source_new (&win32_watch_funcs, sizeof (GIOWin32Watch)); + watch = (GIOWin32Watch *)source; watch->channel = channel; g_io_channel_ref (channel); - watch->callback = func; watch->condition = condition; watch->pollfd.fd = G_WIN32_MSG_HANDLE; watch->pollfd.events = condition; - g_main_add_poll (&watch->pollfd, priority); + g_source_add_poll (source, &watch->pollfd); - return g_source_add (priority, TRUE, &win32_watch_funcs, - watch, user_data, notify); + return source; } static GIOError @@ -653,16 +643,11 @@ fd_reader (int fd, return read (fd, buf, len); } -static guint -g_io_win32_fd_add_watch (GIOChannel *channel, - gint priority, - GIOCondition condition, - GIOFunc func, - gpointer user_data, - GDestroyNotify notify) +static GSource * +g_io_win32_fd_create_watch (GIOChannel *channel, + GIOCondition condition) { - return g_io_win32_add_watch (channel, priority, condition, - func, user_data, notify, fd_reader); + return g_io_win32_create_watch (channel, condition, fd_reader); } static GIOError @@ -752,16 +737,11 @@ sock_reader (int fd, return recv (fd, buf, len, 0); } -static guint -g_io_win32_sock_add_watch (GIOChannel *channel, - gint priority, - GIOCondition condition, - GIOFunc func, - gpointer user_data, - GDestroyNotify notify) +static GSource * +g_io_win32_sock_create_watch (GIOChannel *channel, + GIOCondition condition) { - return g_io_win32_add_watch (channel, priority, condition, - func, user_data, notify, sock_reader); + return g_io_win32_add_watch (channel, condition, sock_reader); } static GIOFuncs win32_channel_msg_funcs = { @@ -769,7 +749,7 @@ static GIOFuncs win32_channel_msg_funcs = { g_io_win32_msg_write, g_io_win32_no_seek, g_io_win32_msg_close, - g_io_win32_msg_add_watch, + g_io_win32_msg_create_watch, g_io_win32_free }; @@ -778,7 +758,7 @@ static GIOFuncs win32_channel_fd_funcs = { g_io_win32_fd_write, g_io_win32_fd_seek, g_io_win32_fd_close, - g_io_win32_fd_add_watch, + g_io_win32_fd_create_watch, g_io_win32_free }; @@ -787,7 +767,7 @@ static GIOFuncs win32_channel_sock_funcs = { g_io_win32_sock_write, g_io_win32_no_seek, g_io_win32_sock_close, - g_io_win32_sock_add_watch, + g_io_win32_sock_create_watch, g_io_win32_free }; diff --git a/glib/Makefile.am b/glib/Makefile.am index e29f0a6dc..a4224af38 100644 --- a/glib/Makefile.am +++ b/glib/Makefile.am @@ -165,10 +165,11 @@ libglib_1_3_la_LDFLAGS = \ -version-info $(LT_CURRENT):$(LT_REVISION):$(LT_AGE) \ -export-dynamic -noinst_PROGRAMS = testglib testgdate testgdateparser +noinst_PROGRAMS = testglib testgdate testgdateparser timeloop testglib_LDADD = libglib-1.3.la testgdate_LDADD = libglib-1.3.la testgdateparser_LDADD = libglib-1.3.la +timeloop_LDADD = libglib-1.3.la m4datadir = $(datadir)/aclocal m4data_DATA = glib-2.0.m4 diff --git a/glib/gfileutils.c b/glib/gfileutils.c index ce802426d..569983f4a 100644 --- a/glib/gfileutils.c +++ b/glib/gfileutils.c @@ -27,6 +27,7 @@ #include #endif #include +#include #include #include #include diff --git a/glib/giochannel.c b/glib/giochannel.c index 29a200231..853a0adc7 100644 --- a/glib/giochannel.c +++ b/glib/giochannel.c @@ -104,6 +104,15 @@ g_io_channel_close (GIOChannel *channel) channel->funcs->io_close (channel); } +GSource * +g_io_create_watch (GIOChannel *channel, + GIOCondition condition) +{ + g_return_val_if_fail (channel != NULL, NULL); + + return channel->funcs->io_create_watch (channel, condition); +} + guint g_io_add_watch_full (GIOChannel *channel, gint priority, @@ -112,10 +121,17 @@ g_io_add_watch_full (GIOChannel *channel, gpointer user_data, GDestroyNotify notify) { + GSource *source; + g_return_val_if_fail (channel != NULL, 0); - return channel->funcs->io_add_watch (channel, priority, condition, - func, user_data, notify); + source = g_io_create_watch (channel, condition); + + if (priority != G_PRIORITY_DEFAULT) + g_source_set_priority (source, priority); + g_source_set_callback (source, (GSourceFunc)func, user_data, notify); + + return g_source_attach (source, NULL); } guint @@ -124,5 +140,5 @@ g_io_add_watch (GIOChannel *channel, GIOFunc func, gpointer user_data) { - return g_io_add_watch_full (channel, 0, condition, func, user_data, NULL); + return g_io_add_watch_full (channel, G_PRIORITY_DEFAULT, condition, func, user_data, NULL); } diff --git a/glib/giochannel.h b/glib/giochannel.h index 7992a69e4..e9a64c0b3 100644 --- a/glib/giochannel.h +++ b/glib/giochannel.h @@ -27,6 +27,7 @@ #ifndef __G_IOCHANNEL_H__ #define __G_IOCHANNEL_H__ +#include #include G_BEGIN_DECLS @@ -71,25 +72,21 @@ typedef gboolean (*GIOFunc) (GIOChannel *source, gpointer data); struct _GIOFuncs { - GIOError (*io_read) (GIOChannel *channel, - gchar *buf, - guint count, - guint *bytes_read); - GIOError (*io_write) (GIOChannel *channel, - gchar *buf, - guint count, - guint *bytes_written); - GIOError (*io_seek) (GIOChannel *channel, - gint offset, - GSeekType type); - void (*io_close) (GIOChannel *channel); - guint (*io_add_watch) (GIOChannel *channel, - gint priority, - GIOCondition condition, - GIOFunc func, - gpointer user_data, - GDestroyNotify notify); - void (*io_free) (GIOChannel *channel); + GIOError (*io_read) (GIOChannel *channel, + gchar *buf, + guint count, + guint *bytes_read); + GIOError (*io_write) (GIOChannel *channel, + gchar *buf, + guint count, + guint *bytes_written); + GIOError (*io_seek) (GIOChannel *channel, + gint offset, + GSeekType type); + void (*io_close) (GIOChannel *channel); + GSource * (*io_create_watch) (GIOChannel *channel, + GIOCondition condition); + void (*io_free) (GIOChannel *channel); }; void g_io_channel_init (GIOChannel *channel); @@ -113,6 +110,8 @@ guint g_io_add_watch_full (GIOChannel *channel, GIOFunc func, gpointer user_data, GDestroyNotify notify); +GSource *g_io_create_watch (GIOChannel *channel, + GIOCondition condition); guint g_io_add_watch (GIOChannel *channel, GIOCondition condition, GIOFunc func, diff --git a/glib/giounix.c b/glib/giounix.c index 2a06dac80..ca98e0c86 100644 --- a/glib/giounix.c +++ b/glib/giounix.c @@ -43,12 +43,15 @@ typedef struct _GIOUnixChannel GIOUnixChannel; typedef struct _GIOUnixWatch GIOUnixWatch; -struct _GIOUnixChannel { +struct _GIOUnixChannel +{ GIOChannel channel; gint fd; }; -struct _GIOUnixWatch { +struct _GIOUnixWatch +{ + GSource source; GPollFD pollfd; GIOChannel *channel; GIOCondition condition; @@ -56,37 +59,29 @@ struct _GIOUnixWatch { }; -static GIOError g_io_unix_read (GIOChannel *channel, - gchar *buf, - guint count, - guint *bytes_written); - -static GIOError g_io_unix_write(GIOChannel *channel, - gchar *buf, - guint count, - guint *bytes_written); -static GIOError g_io_unix_seek (GIOChannel *channel, - gint offset, - GSeekType type); -static void g_io_unix_close (GIOChannel *channel); -static void g_io_unix_free (GIOChannel *channel); -static guint g_io_unix_add_watch (GIOChannel *channel, - gint priority, - GIOCondition condition, - GIOFunc func, - gpointer user_data, - GDestroyNotify notify); -static gboolean g_io_unix_prepare (gpointer source_data, - GTimeVal *current_time, - gint *timeout, - gpointer user_data); -static gboolean g_io_unix_check (gpointer source_data, - GTimeVal *current_time, - gpointer user_data); -static gboolean g_io_unix_dispatch (gpointer source_data, - GTimeVal *current_time, - gpointer user_data); -static void g_io_unix_destroy (gpointer source_data); +static GIOError g_io_unix_read (GIOChannel *channel, + gchar *buf, + guint count, + guint *bytes_written); +static GIOError g_io_unix_write (GIOChannel *channel, + gchar *buf, + guint count, + guint *bytes_written); +static GIOError g_io_unix_seek (GIOChannel *channel, + gint offset, + GSeekType type); +static void g_io_unix_close (GIOChannel *channel); +static void g_io_unix_free (GIOChannel *channel); +static GSource *g_io_unix_create_watch (GIOChannel *channel, + GIOCondition condition); + +static gboolean g_io_unix_prepare (GSource *source, + gint *timeout); +static gboolean g_io_unix_check (GSource *source); +static gboolean g_io_unix_dispatch (GSource *source, + GSourceFunc callback, + gpointer user_data); +static void g_io_unix_destroy (GSource *source); GSourceFuncs unix_watch_funcs = { g_io_unix_prepare, @@ -100,51 +95,53 @@ GIOFuncs unix_channel_funcs = { g_io_unix_write, g_io_unix_seek, g_io_unix_close, - g_io_unix_add_watch, + g_io_unix_create_watch, g_io_unix_free, }; static gboolean -g_io_unix_prepare (gpointer source_data, - GTimeVal *current_time, - gint *timeout, - gpointer user_data) +g_io_unix_prepare (GSource *source, + gint *timeout) { *timeout = -1; return FALSE; } static gboolean -g_io_unix_check (gpointer source_data, - GTimeVal *current_time, - gpointer user_data) +g_io_unix_check (GSource *source) { - GIOUnixWatch *data = source_data; + GIOUnixWatch *watch = (GIOUnixWatch *)source; - return (data->pollfd.revents & data->condition); + return (watch->pollfd.revents & watch->condition); } static gboolean -g_io_unix_dispatch (gpointer source_data, - GTimeVal *current_time, - gpointer user_data) +g_io_unix_dispatch (GSource *source, + GSourceFunc callback, + gpointer user_data) { - GIOUnixWatch *data = source_data; + GIOFunc func = (GIOFunc)callback; + GIOUnixWatch *watch = (GIOUnixWatch *)source; - return (*data->callback)(data->channel, - data->pollfd.revents & data->condition, - user_data); + if (!func) + { + g_warning ("IO watch dispatched without callback\n" + "You must call g_source_connect()."); + return FALSE; + } + + return (*func) (watch->channel, + watch->pollfd.revents & watch->condition, + user_data); } static void -g_io_unix_destroy (gpointer source_data) +g_io_unix_destroy (GSource *source) { - GIOUnixWatch *data = source_data; + GIOUnixWatch *watch = (GIOUnixWatch *)source; - g_main_remove_poll (&data->pollfd); - g_io_channel_unref (data->channel); - g_free (data); + g_io_channel_unref (watch->channel); } static GIOError @@ -269,29 +266,29 @@ g_io_unix_free (GIOChannel *channel) g_free (unix_channel); } -static guint -g_io_unix_add_watch (GIOChannel *channel, - gint priority, - GIOCondition condition, - GIOFunc func, - gpointer user_data, - GDestroyNotify notify) +static GSource * +g_io_unix_create_watch (GIOChannel *channel, + GIOCondition condition) { - GIOUnixWatch *watch = g_new (GIOUnixWatch, 1); GIOUnixChannel *unix_channel = (GIOUnixChannel *)channel; + GSource *source; + GIOUnixWatch *watch; + + + source = g_source_new (&unix_watch_funcs, sizeof (GIOUnixWatch)); + watch = (GIOUnixWatch *)source; watch->channel = channel; g_io_channel_ref (channel); - - watch->callback = func; + watch->condition = condition; watch->pollfd.fd = unix_channel->fd; watch->pollfd.events = condition; - g_main_add_poll (&watch->pollfd, priority); + g_source_add_poll (source, &watch->pollfd); - return g_source_add (priority, TRUE, &unix_watch_funcs, watch, user_data, notify); + return source; } GIOChannel * diff --git a/glib/giowin32.c b/glib/giowin32.c index 560d714ba..f4e74ccc8 100644 --- a/glib/giowin32.c +++ b/glib/giowin32.c @@ -102,6 +102,7 @@ struct _GIOWin32Channel { #define UNLOCK(mutex) LeaveCriticalSection (&mutex) struct _GIOWin32Watch { + GSource source; GPollFD pollfd; GIOChannel *channel; GIOCondition condition; @@ -319,10 +320,8 @@ buffer_read (GIOWin32Channel *channel, } static gboolean -g_io_win32_prepare (gpointer source_data, - GTimeVal *current_time, - gint *timeout, - gpointer user_data) +g_io_win32_prepare (GSource *source, + gint *timeout) { *timeout = -1; @@ -330,12 +329,10 @@ g_io_win32_prepare (gpointer source_data, } static gboolean -g_io_win32_check (gpointer source_data, - GTimeVal *current_time, - gpointer user_data) +g_io_win32_check (GSource *source) { - GIOWin32Watch *data = source_data; - GIOWin32Channel *channel = (GIOWin32Channel *) data->channel; + GIOWin32Watch *watch = (GIOWin32Watch *)source; + GIOWin32Channel *channel = (GIOWin32Channel *) watch->channel; /* If the thread has died, we have encountered EOF. If the buffer * also is emtpty set the HUP bit. @@ -345,34 +342,31 @@ g_io_win32_check (gpointer source_data, if (channel->debug) g_print ("g_io_win32_check: setting G_IO_HUP thread %#x rdp=%d wrp=%d\n", channel->thread_id, channel->rdp, channel->wrp); - data->pollfd.revents |= G_IO_HUP; + watch->pollfd.revents |= G_IO_HUP; return TRUE; } - return (data->pollfd.revents & data->condition); + return (watch->pollfd.revents & watch->condition); } static gboolean -g_io_win32_dispatch (gpointer source_data, - GTimeVal *current_time, - gpointer user_data) - +g_io_win32_dispatch (GSource *source, + GSourceFunc callback, + gpointer user_data) { - GIOWin32Watch *data = source_data; + GIOWin32Watch *watch = (GIOWin32Watch *)source; - return (*data->callback) (data->channel, - data->pollfd.revents & data->condition, - user_data); + return (*callback) (watch->channel, + watch->pollfd.revents & watch->condition, + user_data); } static void -g_io_win32_destroy (gpointer source_data) +g_io_win32_destroy (GSource *source) { - GIOWin32Watch *data = source_data; + GIOWin32Watch *watch = (GIOWin32Watch *)source; - g_main_remove_poll (&data->pollfd); - g_io_channel_unref (data->channel); - g_free (data); + g_io_channel_unref (watch->channel); } static GSourceFuncs win32_watch_funcs = { @@ -382,22 +376,21 @@ static GSourceFuncs win32_watch_funcs = { g_io_win32_destroy }; -static guint -g_io_win32_add_watch (GIOChannel *channel, - gint priority, - GIOCondition condition, - GIOFunc func, - gpointer user_data, - GDestroyNotify notify, - int (*reader) (int, guchar *, int)) +static GSource * +g_io_win32_create_watch (GIOChannel *channel, + GIOCondition condition, + int (*reader) (int, guchar *, int))) { - GIOWin32Watch *watch = g_new (GIOWin32Watch, 1); GIOWin32Channel *win32_channel = (GIOWin32Channel *) channel; + GIOWin32Watch *watch; + GSource *source; + + source = g_source_new (&win32_watch_funcs, sizeof (GIOWin32Watch)); + watch = (GIOWin32Watch *)source; watch->channel = channel; g_io_channel_ref (channel); - watch->callback = func; watch->condition = condition; if (win32_channel->data_avail_event == NULL) @@ -407,16 +400,15 @@ g_io_win32_add_watch (GIOChannel *channel, watch->pollfd.events = condition; if (win32_channel->debug) - g_print ("g_io_win32_add_watch: fd:%d handle:%#x\n", + g_print ("g_io_win32_create_watch: fd:%d handle:%#x\n", win32_channel->fd, watch->pollfd.fd); if (win32_channel->thread_id == 0) create_reader_thread (win32_channel, reader); - g_main_add_poll (&watch->pollfd, priority); + g_source_add_poll (source, &watch->pollfd); - return g_source_add (priority, TRUE, &win32_watch_funcs, watch, - user_data, notify); + return source; } static GIOError @@ -490,29 +482,27 @@ g_io_win32_free (GIOChannel *channel) g_free (win32_channel); } -static guint -g_io_win32_msg_add_watch (GIOChannel *channel, - gint priority, - GIOCondition condition, - GIOFunc func, - gpointer user_data, - GDestroyNotify notify) +static GSource * +g_io_win32_msg_create_watch (GIOChannel *channel, + GIOCondition condition) { - GIOWin32Watch *watch = g_new (GIOWin32Watch, 1); + GIOWin32Watch *watch; + GSource *source; + + source = g_source_new (&win32_watch_funcs, sizeof (GIOWin32Watch)); + watch = (GIOWin32Watch *)source; watch->channel = channel; g_io_channel_ref (channel); - watch->callback = func; watch->condition = condition; watch->pollfd.fd = G_WIN32_MSG_HANDLE; watch->pollfd.events = condition; - g_main_add_poll (&watch->pollfd, priority); + g_source_add_poll (source, &watch->pollfd); - return g_source_add (priority, TRUE, &win32_watch_funcs, - watch, user_data, notify); + return source; } static GIOError @@ -653,16 +643,11 @@ fd_reader (int fd, return read (fd, buf, len); } -static guint -g_io_win32_fd_add_watch (GIOChannel *channel, - gint priority, - GIOCondition condition, - GIOFunc func, - gpointer user_data, - GDestroyNotify notify) +static GSource * +g_io_win32_fd_create_watch (GIOChannel *channel, + GIOCondition condition) { - return g_io_win32_add_watch (channel, priority, condition, - func, user_data, notify, fd_reader); + return g_io_win32_create_watch (channel, condition, fd_reader); } static GIOError @@ -752,16 +737,11 @@ sock_reader (int fd, return recv (fd, buf, len, 0); } -static guint -g_io_win32_sock_add_watch (GIOChannel *channel, - gint priority, - GIOCondition condition, - GIOFunc func, - gpointer user_data, - GDestroyNotify notify) +static GSource * +g_io_win32_sock_create_watch (GIOChannel *channel, + GIOCondition condition) { - return g_io_win32_add_watch (channel, priority, condition, - func, user_data, notify, sock_reader); + return g_io_win32_add_watch (channel, condition, sock_reader); } static GIOFuncs win32_channel_msg_funcs = { @@ -769,7 +749,7 @@ static GIOFuncs win32_channel_msg_funcs = { g_io_win32_msg_write, g_io_win32_no_seek, g_io_win32_msg_close, - g_io_win32_msg_add_watch, + g_io_win32_msg_create_watch, g_io_win32_free }; @@ -778,7 +758,7 @@ static GIOFuncs win32_channel_fd_funcs = { g_io_win32_fd_write, g_io_win32_fd_seek, g_io_win32_fd_close, - g_io_win32_fd_add_watch, + g_io_win32_fd_create_watch, g_io_win32_free }; @@ -787,7 +767,7 @@ static GIOFuncs win32_channel_sock_funcs = { g_io_win32_sock_write, g_io_win32_no_seek, g_io_win32_sock_close, - g_io_win32_sock_add_watch, + g_io_win32_sock_create_watch, g_io_win32_free }; diff --git a/glib/gmain.c b/glib/gmain.c index ad03a35c0..d87022c30 100644 --- a/glib/gmain.c +++ b/glib/gmain.c @@ -63,9 +63,9 @@ /* Types */ -typedef struct _GTimeoutData GTimeoutData; -typedef struct _GSource GSource; +typedef struct _GTimeoutSource GTimeoutSource; typedef struct _GPollRec GPollRec; +typedef struct _GSourceCallback GSourceCallback; typedef enum { @@ -73,23 +73,76 @@ typedef enum G_SOURCE_CAN_RECURSE = 1 << (G_HOOK_FLAG_USER_SHIFT + 1) } GSourceFlags; -struct _GSource +struct _GMainContext { - GHook hook; - gint priority; - gpointer source_data; +#ifdef G_THREADS_ENABLED + /* The following lock is used for both the list of sources + * and the list of poll records + */ + GMutex *mutex; + GThread *thread; +#endif + + GPtrArray *pending_dispatches; + gint timeout; /* Timeout for current iteration */ + + guint next_id; + GSource *source_list; + gint in_check_or_prepare; + + GPollRec *poll_records; + GPollRec *poll_free_list; + GMemChunk *poll_chunk; + guint n_poll_records; + GPollFD *cached_poll_array; + gint cached_poll_array_size; + +#ifdef G_THREADS_ENABLED +#ifndef G_OS_WIN32 +/* this pipe is used to wake up the main loop when a source is added. + */ + gint wake_up_pipe[2]; +#else /* G_OS_WIN32 */ + HANDLE wake_up_semaphore; +#endif /* G_OS_WIN32 */ + + GPollFD wake_up_rec; + gboolean poll_waiting; + +/* Flag indicating whether the set of fd's changed during a poll */ + gboolean poll_changed; +#endif /* G_THREADS_ENABLED */ + + GPollFunc poll_func; + + GTimeVal current_time; + gboolean time_is_current; +}; + +struct _GSourceCallback +{ + guint ref_count; + GSourceFunc func; + gpointer data; + GDestroyNotify notify; }; struct _GMainLoop { + GMainContext *context; gboolean is_running; + +#ifdef G_THREADS_ENABLED + GMutex *mutex; + GCond *sem_cond; +#endif G_THREADS_ENABLED }; -struct _GTimeoutData +struct _GTimeoutSource { + GSource source; GTimeVal expiration; gint interval; - GSourceFunc callback; }; struct _GPollRec @@ -99,57 +152,71 @@ struct _GPollRec GPollRec *next; }; +#ifdef G_THREADS_ENABLED +#define LOCK_CONTEXT(context) g_mutex_lock(context->mutex) +#define UNLOCK_CONTEXT(context) g_mutex_unlock(context->mutex) +#define LOCK_LOOP(loop) g_mutex_lock(loop->mutex) +#define UNLOCK_LOOP(loop) g_mutex_unlock(loop->mutex) +#else +#define LOCK_CONTEXT(context) (void)0 +#define UNLOCK_CONTEXT(context) (void)0 +#define LOCK_LOOP(context) (void)0 +#define UNLOCK_LOOP(context) (void)0 +#endif + +#define SOURCE_DESTROYED(source) (((source)->flags & G_HOOK_FLAG_ACTIVE) == 0) + +#define SOURCE_UNREF(source, context) \ + G_STMT_START { \ + if ((source)->ref_count > 1) \ + (source)->ref_count--; \ + else \ + g_source_unref_internal ((source), (context), TRUE); \ + } G_STMT_END + + /* Forward declarations */ -static gint g_source_compare (GHook *a, - GHook *b); -static void g_source_destroy_func (GHookList *hook_list, - GHook *hook); -static void g_main_poll (gint timeout, - gboolean use_priority, - gint priority); -static void g_main_add_poll_unlocked (gint priority, - GPollFD *fd); -static void g_main_wakeup (void); +static void g_source_unref_internal (GSource *source, + GMainContext *context, + gboolean have_lock); +static void g_source_destroy_internal (GSource *source, + GMainContext *context, + gboolean have_lock); +static void g_main_context_poll (GMainContext *context, + gint timeout, + gint priority, + GPollFD *fds, + gint n_fds); +static void g_main_context_add_poll_unlocked (GMainContext *context, + gint priority, + GPollFD *fd); +static void g_main_context_remove_poll_unlocked (GMainContext *context, + GPollFD *fd); +static void g_main_context_wakeup (GMainContext *context); -static gboolean g_timeout_prepare (gpointer source_data, - GTimeVal *current_time, - gint *timeout, - gpointer user_data); -static gboolean g_timeout_check (gpointer source_data, - GTimeVal *current_time, - gpointer user_data); -static gboolean g_timeout_dispatch (gpointer source_data, - GTimeVal *dispatch_time, - gpointer user_data); -static gboolean g_idle_prepare (gpointer source_data, - GTimeVal *current_time, - gint *timeout, - gpointer user_data); -static gboolean g_idle_check (gpointer source_data, - GTimeVal *current_time, - gpointer user_data); -static gboolean g_idle_dispatch (gpointer source_data, - GTimeVal *dispatch_time, - gpointer user_data); +static gboolean g_timeout_prepare (GSource *source, + gint *timeout); +static gboolean g_timeout_check (GSource *source); +static gboolean g_timeout_dispatch (GSource *source, + GSourceFunc callback, + gpointer user_data); +static gboolean g_idle_prepare (GSource *source, + gint *timeout); +static gboolean g_idle_check (GSource *source); +static gboolean g_idle_dispatch (GSource *source, + GSourceFunc callback, + gpointer user_data); -/* Data */ - -static GSList *pending_dispatches = NULL; -static GHookList source_list = { 0 }; -static gint in_check_or_prepare = 0; - -/* The following lock is used for both the list of sources - * and the list of poll records - */ G_LOCK_DEFINE_STATIC (main_loop); +static GMainContext *default_main_context; static GSourceFuncs timeout_funcs = { g_timeout_prepare, g_timeout_check, g_timeout_dispatch, - g_free, + NULL }; static GSourceFuncs idle_funcs = @@ -157,36 +224,16 @@ static GSourceFuncs idle_funcs = g_idle_prepare, g_idle_check, g_idle_dispatch, - NULL, + NULL }; -static GPollRec *poll_records = NULL; -static GPollRec *poll_free_list = NULL; -static GMemChunk *poll_chunk; -static guint n_poll_records = 0; - -#ifdef G_THREADS_ENABLED -#ifndef G_OS_WIN32 -/* this pipe is used to wake up the main loop when a source is added. - */ -static gint wake_up_pipe[2] = { -1, -1 }; -#else /* G_OS_WIN32 */ -static HANDLE wake_up_semaphore = NULL; -#endif /* G_OS_WIN32 */ -static GPollFD wake_up_rec; -static gboolean poll_waiting = FALSE; - -/* Flag indicating whether the set of fd's changed during a poll */ -static gboolean poll_changed = FALSE; -#endif /* G_THREADS_ENABLED */ - #ifdef HAVE_POLL /* SunOS has poll, but doesn't provide a prototype. */ # if defined (sun) && !defined (__SVR4) extern gint poll (GPollFD *ufds, guint nfsd, gint timeout); # endif /* !sun */ -static GPollFunc poll_func = (GPollFunc) poll; #else /* !HAVE_POLL */ + #ifdef G_OS_WIN32 static gint @@ -436,185 +483,946 @@ g_poll (GPollFD *fds, #endif /* !G_OS_WIN32 */ -static GPollFunc poll_func = g_poll; #endif /* !HAVE_POLL */ +/* Called to clean up when a thread terminates + */ +static void +g_main_context_destroy (GMainContext *context) +{ + GSource *source; + + /* We need the lock here only because g_source_destroy expects + * to be able to unlock when destroying the source's data + */ + LOCK_CONTEXT (context); + source = context->source_list; + while (source) + { + GSource *next = source->next; + g_source_destroy_internal (source, context, TRUE); + source = next; + } + UNLOCK_CONTEXT (context); + +#ifdef G_THREADS_ENABLED + g_mutex_free (context->mutex); +#endif + + g_ptr_array_free (context->pending_dispatches, TRUE); + g_free (context->cached_poll_array); + + g_mem_chunk_destroy (context->poll_chunk); + + if (g_thread_supported()) + { +#ifndef G_OS_WIN32 + close (context->wake_up_pipe[0]); + close (context->wake_up_pipe[1]); +#else + CloseHandle (context->wake_up_semaphore); +#endif + } + + g_free (context); +} + +/** + * g_main_context_get: + * @thread: a #GThread + * + * Retrieves the main loop context for a particular thread. This + * will create the main context for the thread if none previously + * existed. The context will exist until the thread terminates. + * + * Return value: the main loop context for @thread. + **/ +GMainContext * +g_main_context_get (GThread *thread) +{ + static GStaticPrivate private_key = G_STATIC_PRIVATE_INIT; + GMainContext *context; + + g_return_val_if_fail (thread != NULL, NULL); + + if (g_thread_supported ()) + context = g_static_private_get_for_thread (&private_key, thread); + else + context = default_main_context; + + if (!context) + { + context = g_new0 (GMainContext, 1); + +#ifdef G_THREADS_ENABLED + if (g_thread_supported ()) + context->mutex = g_mutex_new(); + + context->thread = thread; +#endif + + context->next_id = 1; + + context->source_list = NULL; + +#if HAVE_POLL + context->poll_func = (GPollFunc)poll; +#else + context->poll_func = g_poll; +#endif + + context->cached_poll_array = NULL; + context->cached_poll_array_size = 0; + + context->pending_dispatches = g_ptr_array_new (); + + context->time_is_current = FALSE; + +#ifdef G_THREADS_ENABLED + if (g_thread_supported ()) + { +#ifndef G_OS_WIN32 + if (pipe (context->wake_up_pipe) < 0) + g_error ("Cannot create pipe main loop wake-up: %s\n", + g_strerror (errno)); + + context->wake_up_rec.fd = context->wake_up_pipe[0]; + context->wake_up_rec.events = G_IO_IN; + g_main_context_add_poll_unlocked (context, 0, &context->wake_up_rec); +#else + if ((context->wake_up_semaphore = CreateSemaphore (NULL, 0, 100, NULL)) == NULL) + g_error ("Cannot create wake-up semaphore: %s", g_win32_error_message (GetLastError ())); + context->wake_up_rec.fd = (gint) context->wake_up_semaphore; + context->wake_up_rec.events = G_IO_IN; +#ifdef G_MAIN_POLL_DEBUG + g_print ("wake-up semaphore: %#x\n", (guint) context->wake_up_semaphore); +#endif + g_main_context_add_poll_unlocked (context, 0, &context->wake_up_rec); +#endif + } +#endif + + if (g_thread_supported ()) + g_static_private_set_for_thread (&private_key, thread, + context, + (GDestroyNotify)g_main_context_destroy); + else + default_main_context = context; + } + + return context; +} + +/** + * g_main_context_default: + * + * Return the default main context. This is the main context used + * for main loop functions when a main loop is not explicitly + * specified. + * + * Return value: the default main context. + **/ +GMainContext * +g_main_context_default (void) +{ + /* Slow, but safe */ + + G_LOCK (main_loop); + + if (!default_main_context) + default_main_context = g_main_context_get (g_thread_self ()); + + G_UNLOCK (main_loop); + + return default_main_context; +} + /* Hooks for adding to the main loop */ -/* Use knowledge of insert_sorted algorithm here to make - * sure we insert at the end of equal priority items - */ -static gint -g_source_compare (GHook *a, - GHook *b) +/** + * g_source_new: + * @source_funcs: structure containing functions that implement + * the sources behavior. + * @struct_size: size of the #GSource structure to create + * + * Create a new GSource structure. The size is specified to + * allow creating structures derived from GSource that contain + * additional data. The size passed in must be at least + * sizeof(GSource). + * + * The source will not initially be associated with any #GMainContext + * and must be added to one with g_source_add() before it will be + * executed. + * + * Return value: the newly create #GSource + **/ +GSource * +g_source_new (GSourceFuncs *source_funcs, + guint struct_size) { - GSource *source_a = (GSource *)a; - GSource *source_b = (GSource *)b; - - return (source_a->priority < source_b->priority) ? -1 : 1; -} - -/* HOLDS: main_loop lock */ -static void -g_source_destroy_func (GHookList *hook_list, - GHook *hook) -{ - GSource *source = (GSource*) hook; - GDestroyNotify destroy; - - G_UNLOCK (main_loop); - - destroy = hook->destroy; - if (destroy) - destroy (hook->data); - - destroy = ((GSourceFuncs*) hook->func)->destroy; - if (destroy) - destroy (source->source_data); - - G_LOCK (main_loop); -} - -guint -g_source_add (gint priority, - gboolean can_recurse, - GSourceFuncs *funcs, - gpointer source_data, - gpointer user_data, - GDestroyNotify notify) -{ - guint return_val; GSource *source; - G_LOCK (main_loop); + g_return_val_if_fail (source_funcs != NULL, NULL); + g_return_val_if_fail (struct_size >= sizeof (GSource), NULL); + + source = (GSource*) g_malloc0 (struct_size); - if (!source_list.is_setup) + source->source_funcs = source_funcs; + source->ref_count = 1; + + source->priority = G_PRIORITY_DEFAULT; + + source->flags = G_HOOK_FLAG_ACTIVE; + + /* NULL/0 initialization for all other fields */ + + return source; +} + +/* Holds context's lock + */ +static void +g_source_list_add (GSource *source, + GMainContext *context) +{ + GSource *tmp_source, *last_source; + + last_source = NULL; + tmp_source = context->source_list; + while (tmp_source && tmp_source->priority <= source->priority) { - g_hook_list_init (&source_list, sizeof (GSource)); - - source_list.hook_destroy = G_HOOK_DEFERRED_DESTROY; - source_list.hook_free = g_source_destroy_func; + last_source = tmp_source; + tmp_source = tmp_source->next; } - source = (GSource*) g_hook_alloc (&source_list); - source->priority = priority; - source->source_data = source_data; - source->hook.func = funcs; - source->hook.data = user_data; - source->hook.destroy = notify; + source->next = tmp_source; + if (tmp_source) + tmp_source->prev = source; - g_hook_insert_sorted (&source_list, - (GHook *)source, - g_source_compare); + source->prev = last_source; + if (last_source) + last_source->next = source; + else + context->source_list = source; +} - if (can_recurse) - source->hook.flags |= G_SOURCE_CAN_RECURSE; +/* Holds context's lock + */ +static void +g_source_list_remove (GSource *source, + GMainContext *context) +{ + if (source->prev) + source->prev->next = source->next; + else + context->source_list = source->next; - return_val = source->hook.hook_id; + if (source->next) + source->next->prev = source->prev; + + source->prev = NULL; + source->next = NULL; +} + +/** + * g_source_attach: + * @source: a #GSource + * @context: a #GMainContext (if %NULL, the default context will be used) + * + * Adds a #GSource to a @context so that it will be executed within + * that context. + * + * Return value: the ID for the source within the #GMainContext + **/ +guint +g_source_attach (GSource *source, + GMainContext *context) +{ + guint result = 0; + GSList *tmp_list; + + g_return_val_if_fail (source->context == NULL, 0); + g_return_val_if_fail (!SOURCE_DESTROYED (source), 0); + + if (!context) + context = g_main_context_default (); + + LOCK_CONTEXT (context); + + source->context = context; + result = source->id = context->next_id++; + + g_source_list_add (source, context); + + tmp_list = source->poll_fds; + while (tmp_list) + { + g_main_context_add_poll_unlocked (context, source->priority, tmp_list->data); + tmp_list = tmp_list->next; + } #ifdef G_THREADS_ENABLED /* Now wake up the main loop if it is waiting in the poll() */ - g_main_wakeup (); + g_main_context_wakeup (context); #endif - - G_UNLOCK (main_loop); - return return_val; + UNLOCK_CONTEXT (context); + + return result; } +static void +g_source_destroy_internal (GSource *source, + GMainContext *context, + gboolean have_lock) +{ + if (!have_lock) + LOCK_CONTEXT (context); + + if (!SOURCE_DESTROYED (source)) + { + gpointer old_cb_data; + GSourceCallbackFuncs *old_cb_funcs; + + source->flags &= ~G_HOOK_FLAG_ACTIVE; + + old_cb_data = source->callback_data; + old_cb_funcs = source->callback_funcs; + + source->callback_data = NULL; + source->callback_funcs = NULL; + + if (old_cb_funcs) + { + UNLOCK_CONTEXT (context); + old_cb_funcs->unref (old_cb_data); + LOCK_CONTEXT (context); + } + + g_source_unref_internal (source, context, TRUE); + } + + if (!have_lock) + UNLOCK_CONTEXT (context); +} + +/** + * g_source_destroy: + * @source: a #GSource + * + * Remove a source from its #GMainContext, if any, and mark it as + * destroyed. The source cannot be subsequently added to another + * context. + **/ +void +g_source_destroy (GSource *source) +{ + GMainContext *context; + + g_return_if_fail (source != NULL); + + context = source->context; + + if (context) + g_source_destroy_internal (source, context, FALSE); + else + source->flags &= ~G_HOOK_FLAG_ACTIVE; +} + +/** + * g_source_get_id: + * @source: a #GSource + * + * Return the numeric ID for a particular source. The ID of a source + * is unique within a particular main loop context. The reverse + * mapping from ID to source is done by g_main_context_find_source_by_id(). + * + * Return value: the ID for the source + **/ +guint +g_source_get_id (GSource *source) +{ + guint result; + + g_return_val_if_fail (source != NULL, 0); + g_return_val_if_fail (source->context != NULL, 0); + + LOCK_CONTEXT (source->context); + result = source->id; + UNLOCK_CONTEXT (source->context); + + return result; +} + +/** + * g_source_get_context: + * @source: a #GSource + * + * Get the #GMainContext with which the source is associated. + * Calling this function on a destroyed source is an error. + * + * Return value: the #GMainContext with which the source is associated, + * or %NULL if the context has not yet been added + * to a source. + **/ +GMainContext * +g_source_get_context (GSource *source) +{ + g_return_val_if_fail (!SOURCE_DESTROYED (source), NULL); + + return source->context; +} + +/** + * g_main_source_add_poll: + * @source:a #GSource + * @fd: a #GPollFD structure holding information about a file + * descriptor to watch. + * + * Add a file descriptor to the set of file descriptors polled * for + * this source. This is usually combined with g_source_new() to add an + * event source. The event source's check function will typically test + * the revents field in the #GPollFD struct and return %TRUE if events need + * to be processed. + **/ +void +g_source_add_poll (GSource *source, + GPollFD *fd) +{ + GMainContext *context; + + g_return_if_fail (source != NULL); + g_return_if_fail (fd != NULL); + + context = source->context; + + if (context) + LOCK_CONTEXT (context); + + source->poll_fds = g_slist_prepend (source->poll_fds, fd); + + if (context) + { + g_main_context_add_poll_unlocked (context, source->priority, fd); + UNLOCK_CONTEXT (context); + } +} + +/** + * g_source_set_callback_indirect: + * @source: the source + * @callback_data: pointer to callback data "object" + * @callback_funcs: functions for reference counting callback_data + * and getting the callback and data + * + * Set the callback function storing the data as a refcounted callback + * "object". This is used to implement g_source_set_callback_closure() + * and internally. Note that calling g_source_set_callback_indirect() assumes + * an initial reference count on @callback_data, and thus + * @callback_funcs->unref will eventually be called once more + * than @callback_funcs->ref. + **/ +void +g_source_set_callback_indirect (GSource *source, + gpointer callback_data, + GSourceCallbackFuncs *callback_funcs) +{ + GMainContext *context; + gpointer old_cb_data; + GSourceCallbackFuncs *old_cb_funcs; + + g_return_if_fail (source != NULL); + g_return_if_fail (callback_funcs != NULL || callback_data == NULL); + + context = source->context; + + if (context) + LOCK_CONTEXT (context); + + old_cb_data = source->callback_data; + old_cb_funcs = source->callback_funcs; + + source->callback_data = callback_data; + source->callback_funcs = callback_funcs; + + if (context) + UNLOCK_CONTEXT (context); + + if (old_cb_funcs) + old_cb_funcs->unref (old_cb_data); +} + +static void +g_source_callback_ref (gpointer cb_data) +{ + GSourceCallback *callback = cb_data; + + callback->ref_count++; +} + + +static void +g_source_callback_unref (gpointer cb_data) +{ + GSourceCallback *callback = cb_data; + + callback->ref_count--; + if (callback->ref_count == 0) + { + if (callback->notify) + callback->notify (callback->data); + } +} + +static void +g_source_callback_get (gpointer cb_data, + GSourceFunc *func, + gpointer *data) +{ + GSourceCallback *callback = cb_data; + + *func = callback->func; + *data = callback->data; +} + +static GSourceCallbackFuncs g_source_callback_funcs = { + g_source_callback_ref, + g_source_callback_unref, + g_source_callback_get, +}; + +/** + * g_source_set_callback: + * @source: the source + * @func: a callback function + * @data: the data to pass to callback function + * @notify: a function to call when @data is no longer in use, or %NULL. + * + * Set the callback function for a source. + **/ +void +g_source_set_callback (GSource *source, + GSourceFunc func, + gpointer data, + GDestroyNotify notify) +{ + GSourceCallback *new_callback; + + g_return_if_fail (source != NULL); + + new_callback = g_new (GSourceCallback, 1); + + new_callback->func = func; + new_callback->data = data; + new_callback->notify = notify; + + g_source_set_callback_indirect (source, new_callback, &g_source_callback_funcs); +} + +/** + * g_source_set_priority: + * @source: a #GSource + * @priority: the new priority. + * + * Set the priority of a source. While the main loop is being + * run, a source will + **/ +void +g_source_set_priority (GSource *source, + gint priority) +{ + GSList *tmp_list; + GMainContext *context; + + g_return_if_fail (source != NULL); + + context = source->context; + + if (context) + LOCK_CONTEXT (context); + + source->priority = priority; + + if (context) + { + source->next = NULL; + source->prev = NULL; + + tmp_list = source->poll_fds; + while (tmp_list) + { + g_main_context_remove_poll_unlocked (context, tmp_list->data); + g_main_context_add_poll_unlocked (context, priority, tmp_list->data); + + tmp_list = tmp_list->next; + } + + UNLOCK_CONTEXT (source->context); + } +} + +/** + * g_source_get_priority: + * @source: a #GSource + * + * Gets the priority of a surce + * + * Return value: the priority of the source + **/ +gint +g_source_get_priority (GSource *source) +{ + g_return_val_if_fail (source != NULL, 0); + + return source->priority; +} + +/** + * g_source_set_can_recurse: + * @source: a #GSource + * @can_recurse: whether recursion is allowed for this source + * + * Sets whether a source can be called recursively. If @can_recurse is + * %TRUE, then while the source is being dispatched then this source + * will be processed normally. Otherwise, all processing of this + * source is blocked until the dispatch function returns. + **/ +void +g_source_set_can_recurse (GSource *source, + gboolean can_recurse) +{ + GMainContext *context; + + g_return_if_fail (source != NULL); + + context = source->context; + + if (context) + LOCK_CONTEXT (context); + + if (can_recurse) + source->flags |= G_SOURCE_CAN_RECURSE; + else + source->flags &= ~G_SOURCE_CAN_RECURSE; + + if (context) + UNLOCK_CONTEXT (context); +} + +/** + * g_source_get_can_recurse: + * @source: a #GSource + * + * Checks whether a source is allowed to be called recursively. + * see g_source_set_can_recurse. + * + * Return value: whether recursion is allowed. + **/ +gboolean +g_source_get_can_recurse (GSource *source) +{ + g_return_val_if_fail (source != NULL, FALSE); + + return (source->flags & G_SOURCE_CAN_RECURSE) != 0; +} + +/** + * g_source_ref: + * @source: a #GSource + * + * Increases the reference count on a source by one. + * + * Return value: @source + **/ +GSource * +g_source_ref (GSource *source) +{ + GMainContext *context; + + g_return_val_if_fail (source != NULL, NULL); + + context = source->context; + + if (context) + LOCK_CONTEXT (context); + + source->ref_count++; + + if (context) + UNLOCK_CONTEXT (context); + + return source; +} + +/* g_source_unref() but possible to call within context lock + */ +static void +g_source_unref_internal (GSource *source, + GMainContext *context, + gboolean have_lock) +{ + gpointer cb_data = NULL; + GSourceCallbackFuncs *cb_funcs = NULL; + GSList *tmp_list; + + g_return_if_fail (source != NULL); + + if (!have_lock && context) + LOCK_CONTEXT (context); + + source->ref_count--; + if (source->ref_count == 0) + { + if (context && !SOURCE_DESTROYED (source)) + { + g_warning (G_STRLOC ": ref_count == 0, but source is still attached to a context!"); + source->ref_count++; + } + else + { + g_source_list_remove (source, context); + + tmp_list = source->poll_fds; + while (tmp_list) + { + g_main_context_remove_poll_unlocked (context, tmp_list->data); + tmp_list = tmp_list->next; + } + } + } + + if (!have_lock && context) + UNLOCK_CONTEXT (context); + + if (cb_data) + { + if (have_lock) + UNLOCK_CONTEXT (context); + + cb_funcs->unref (cb_data); + + if (have_lock) + LOCK_CONTEXT (context); + } +} + +/** + * g_source_unref: + * @source: a #GSource + * + * Decreases the reference count of a source by one. If the + * resulting reference count is zero the source and associated + * memory will be destroyed. + **/ +void +g_source_unref (GSource *source) +{ + g_return_if_fail (source != NULL); + + g_source_unref_internal (source, source->context, FALSE); +} + +/** + * g_main_context_find_source_by_id: + * @context: a #GMainContext (if %NULL, the default context will be used) + * @id: the source ID, as returned by g_source_get_id() + * + * Finds a #GSource given a pair of context and ID + * + * Return value: the #GSource if found, otherwise, %NULL + **/ +GSource * +g_main_context_find_source_by_id (GMainContext *context, + guint id) +{ + GSource *source; + + g_return_val_if_fail (id > 0, FALSE); + + if (context == NULL) + context = g_main_context_default (); + + LOCK_CONTEXT (context); + + source = context->source_list; + while (source) + { + if (!SOURCE_DESTROYED (source) && + source->id == id) + break; + source = source->next; + } + + UNLOCK_CONTEXT (context); + + return source; +} + +/** + * g_main_context_find_source_by_funcs_user_data: + * @context: a #GMainContext (if %NULL, the default context will be used). + * @funcs: the @source_funcs passed to g_source_new(). + * @user_data: the user data from the callback. + * + * Finds a source with the given source functions and user data. If + * multiple sources exist with the same source function and user data, + * the first one found will be returned. + * + * Return value: the source, if one was found, otherwise %NULL + **/ +GSource * +g_main_context_find_source_by_funcs_user_data (GMainContext *context, + GSourceFuncs *funcs, + gpointer user_data) +{ + GSource *source; + + g_return_val_if_fail (funcs != NULL, FALSE); + + if (context == NULL) + context = g_main_context_default (); + + LOCK_CONTEXT (context); + + source = context->source_list; + while (source) + { + if (!SOURCE_DESTROYED (source) && + source->source_funcs == funcs && + source->callback_data == user_data) + break; + source = source->next; + } + + UNLOCK_CONTEXT (context); + + return source; +} + +/** + * g_main_context_find_source_by_user_data: + * @context: a #GMainContext + * @user_data: the user_data for the callback. + * + * Finds a source with the given user data for the callback. If + * multiple sources exist with the same user data, the first + * one found will be returned. + * + * Return value: the source, if one was found, otherwise %NULL + **/ +GSource * +g_main_context_find_source_by_user_data (GMainContext *context, + gpointer user_data) +{ + GSource *source; + + if (context == NULL) + context = g_main_context_default (); + + LOCK_CONTEXT (context); + + source = context->source_list; + while (source) + { + if (!SOURCE_DESTROYED (source) && + source->callback_data == user_data) + break; + source = source->next; + } + + UNLOCK_CONTEXT (context); + + return source; +} + +/** + * 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(). + * + * See also g_source_destroy(). + * + * Return value: %TRUE if the source was found and removed. + **/ gboolean g_source_remove (guint tag) { - GHook *hook; - + GSource *source; + g_return_val_if_fail (tag > 0, FALSE); - G_LOCK (main_loop); + source = g_main_context_find_source_by_id (NULL, tag); + if (source) + g_source_destroy (source); - hook = g_hook_get (&source_list, tag); - if (hook) - g_hook_destroy_link (&source_list, hook); - - G_UNLOCK (main_loop); - - return hook != NULL; + return source != NULL; } +/** + * g_source_remove_by_user_data: + * @user_data: the user_data for the callback. + * + * Removes a source from the default main loop context given the user + * data for the callback. If multiple sources exist with the same user + * data, only one will be destroyed. + * + * Return value: %TRUE if a source was found and removed. + **/ gboolean g_source_remove_by_user_data (gpointer user_data) { - GHook *hook; + GSource *source; - G_LOCK (main_loop); - - hook = g_hook_find_data (&source_list, TRUE, user_data); - if (hook) - g_hook_destroy_link (&source_list, hook); - - G_UNLOCK (main_loop); - - return hook != NULL; -} - -static gboolean -g_source_find_source_data (GHook *hook, - gpointer data) -{ - GSource *source = (GSource *)hook; - - return (source->source_data == data); -} - -gboolean -g_source_remove_by_source_data (gpointer source_data) -{ - GHook *hook; - - G_LOCK (main_loop); - - hook = g_hook_find (&source_list, TRUE, - g_source_find_source_data, source_data); - if (hook) - g_hook_destroy_link (&source_list, hook); - - G_UNLOCK (main_loop); - - return hook != NULL; -} - -static gboolean -g_source_find_funcs_user_data (GHook *hook, - gpointer data) -{ - gpointer *d = data; - - return hook->func == d[0] && hook->data == d[1]; + source = g_main_context_find_source_by_user_data (NULL, user_data); + if (source) + { + g_source_destroy (source); + return TRUE; + } + else + return FALSE; } +/** + * g_source_remove_by_funcs_user_data: + * @funcs: The @source_funcs passed to g_source_new() + * @user_data: the user data for the callback + * + * Removes a source from the default main loop context given the + * source functions and user data. If multiple sources exist with the + * same source functions and user data, only one will be destroyed. + * + * Return value: %TRUE if a source was found and removed. + **/ gboolean g_source_remove_by_funcs_user_data (GSourceFuncs *funcs, gpointer user_data) { - gpointer d[2]; - GHook *hook; + GSource *source; g_return_val_if_fail (funcs != NULL, FALSE); - G_LOCK (main_loop); - - d[0] = funcs; - d[1] = user_data; - - hook = g_hook_find (&source_list, TRUE, - g_source_find_funcs_user_data, d); - if (hook) - g_hook_destroy_link (&source_list, hook); - - G_UNLOCK (main_loop); - - return hook != NULL; + source = g_main_context_find_source_by_funcs_user_data (NULL, funcs, user_data); + if (source) + { + g_source_destroy (source); + return TRUE; + } + else + return FALSE; } +/** + * g_get_current_time: + * @result: #GTimeVal structure in which to store current time. + * + * Equivalent to Unix's gettimeofday(), but portable + **/ void g_get_current_time (GTimeVal *result) { @@ -629,463 +1437,769 @@ g_get_current_time (GTimeVal *result) result->tv_sec = r.tv_sec; result->tv_usec = r.tv_usec; #else - FILETIME filetime; - gint64 t; + /* Avoid calling time() except for the first time. + * GetTickCount() should be pretty fast and low-level? + * I could also use ftime() but it seems unnecessarily overheady. + */ + static DWORD start_tick = 0; + static time_t start_time; + DWORD tick; g_return_if_fail (result != NULL); + + if (start_tick == 0) + { + start_tick = GetTickCount (); + time (&start_time); + } - GetSystemTimeAsFileTime (&filetime); - t = ((gint64) filetime.dwLowDateTime) + - ((gint64) filetime.dwHighDateTime) * G_GINT64_CONSTANT (0x100000000); + tick = GetTickCount (); - result->tv_sec = (t - G_GINT64_CONSTANT (0x19db1ded53e8000)) / 10000000; - result->tv_usec = (t % 10000000) / 10; + result->tv_sec = (tick - start_tick) / 1000 + start_time; + result->tv_usec = ((tick - start_tick) % 1000) * 1000; #endif } /* Running the main loop */ -/* HOLDS: main_loop_lock */ +/* HOLDS: context's lock */ static void -g_main_dispatch (GTimeVal *dispatch_time) +g_main_dispatch (GMainContext *context) { - while (pending_dispatches != NULL) + gint i; + + for (i = 0; i < context->pending_dispatches->len; i++) { - gboolean need_destroy; - GSource *source = pending_dispatches->data; - GSList *tmp_list; + GSource *source = context->pending_dispatches->pdata[i]; - tmp_list = pending_dispatches; - pending_dispatches = g_slist_remove_link (pending_dispatches, pending_dispatches); - g_slist_free_1 (tmp_list); + context->pending_dispatches->pdata[i] = NULL; + g_assert (source); - if (G_HOOK_IS_VALID (source)) + source->flags &= ~G_SOURCE_READY; + + if (!SOURCE_DESTROYED (source)) { gboolean was_in_call; - gpointer hook_data = source->hook.data; - gpointer source_data = source->source_data; - gboolean (*dispatch) (gpointer, - GTimeVal *, + gpointer user_data = NULL; + GSourceFunc callback = NULL; + GSourceCallbackFuncs *cb_funcs; + gpointer cb_data; + gboolean need_destroy; + + gboolean (*dispatch) (GSource *, + GSourceFunc, gpointer); - dispatch = ((GSourceFuncs *) source->hook.func)->dispatch; - - was_in_call = G_HOOK_IN_CALL (source); - source->hook.flags |= G_HOOK_FLAG_IN_CALL; + dispatch = source->source_funcs->dispatch; + cb_funcs = source->callback_funcs; + cb_data = source->callback_data; - G_UNLOCK (main_loop); - need_destroy = ! dispatch (source_data, - dispatch_time, - hook_data); - G_LOCK (main_loop); - - if (!was_in_call) - source->hook.flags &= ~G_HOOK_FLAG_IN_CALL; + if (cb_funcs) + cb_funcs->ref (cb_data); - if (need_destroy && G_HOOK_IS_VALID (source)) - g_hook_destroy_link (&source_list, (GHook *) source); + was_in_call = source->flags & G_HOOK_FLAG_IN_CALL; + source->flags |= G_HOOK_FLAG_IN_CALL; + + UNLOCK_CONTEXT (context); + + if (cb_funcs) + cb_funcs->get (cb_data, &callback, &user_data); + + need_destroy = ! dispatch (source, + callback, + user_data); + LOCK_CONTEXT (context); + + if (cb_funcs) + cb_funcs->unref (cb_data); + + if (!was_in_call) + source->flags &= ~G_HOOK_FLAG_IN_CALL; + + /* Note: this depends on the fact that we can't switch + * sources from one main context to another + */ + if (need_destroy && !SOURCE_DESTROYED (source)) + { + g_assert (source->context == context); + g_source_destroy_internal (source, context, TRUE); + } } - - g_hook_unref (&source_list, (GHook*) source); + + SOURCE_UNREF (source, context); } + + g_ptr_array_set_size (context->pending_dispatches, 0); } -/* g_main_iterate () runs a single iteration of the mainloop, or, - * if !dispatch checks to see if any sources need dispatching. - * basic algorithm for dispatch=TRUE: - * - * 1) while the list of currently pending sources is non-empty, - * we call (*dispatch) on those that are !IN_CALL or can_recurse, - * removing sources from the list after each returns. - * the return value of (*dispatch) determines whether the source - * itself is kept alive. - * - * 2) call (*prepare) for sources that are not yet SOURCE_READY and - * are !IN_CALL or can_recurse. a return value of TRUE determines - * that the source would like to be dispatched immediatedly, it - * is then flagged as SOURCE_READY. - * - * 3) poll with the pollfds from all sources at the priority of the - * first source flagged as SOURCE_READY. if there are any sources - * flagged as SOURCE_READY, we use a timeout of 0 or the minimum - * of all timouts otherwise. - * - * 4) for each source !IN_CALL or can_recurse, if SOURCE_READY or - * (*check) returns true, add the source to the pending list. - * once one source returns true, stop after checking all sources - * at that priority. - * - * 5) while the list of currently pending sources is non-empty, - * call (*dispatch) on each source, removing the source - * after the call. - * - */ -static gboolean -g_main_iterate (gboolean block, - gboolean dispatch) +/* Holds context's lock */ +static inline GSource * +next_valid_source (GMainContext *context, + GSource *source) { - GHook *hook; - GTimeVal current_time = { 0, 0 }; - gint n_ready = 0; - gint current_priority = 0; - gint timeout; - gboolean retval = FALSE; + GSource *new_source = source ? source->next : context->source_list; - g_return_val_if_fail (!block || dispatch, FALSE); - - g_get_current_time (¤t_time); - - G_LOCK (main_loop); - -#ifdef G_THREADS_ENABLED - if (poll_waiting) + while (new_source) { - g_warning("g_main_iterate(): main loop already active in another thread"); - G_UNLOCK (main_loop); + if (!SOURCE_DESTROYED (new_source)) + { + new_source->ref_count++; + break; + } + + new_source = new_source->next; + } + + if (source) + SOURCE_UNREF (source, context); + + return new_source; +} + + +/** + * g_main_context_prepare: + * @context: a #GMainContext + * @priority: location to store priority of highest priority + * source already ready. + * + * Prepares to poll sources within a main loop. The resulting information + * for polling is determined by calling g_main_context_query (). + * + * Return value: %TRUE if some source is ready to be dispatched + * prior to polling. + **/ +gboolean +g_main_context_prepare (GMainContext *context, + gint *priority) +{ + gint n_ready = 0; + gint current_priority = G_MAXINT; + GSource *source; + + if (context == NULL) + context = g_main_context_default (); + + LOCK_CONTEXT (context); + + context->time_is_current = FALSE; + + if (context->in_check_or_prepare) + { + g_warning ("g_main_context_prepare() called recursively from within a source's check() or " + "prepare() member."); + return FALSE; + } + +#ifdef G_THREADS_ENABLED + if (context->poll_waiting) + { + g_warning("g_main_context_prepare(): main loop already active in another thread"); + UNLOCK_CONTEXT (context); return FALSE; } -#endif /* G_THREADS_ENABLED */ + context->poll_waiting = TRUE; +#endif G_THREADS_ENABLED + +#if 0 /* If recursing, finish up current dispatch, before starting over */ - if (pending_dispatches) + if (context->pending_dispatches) { if (dispatch) - g_main_dispatch (¤t_time); + g_main_dispatch (context, ¤t_time); - G_UNLOCK (main_loop); - + UNLOCK_CONTEXT (context); return TRUE; } +#endif + /* If recursing, clear list of pending dispatches */ + g_ptr_array_set_size (context->pending_dispatches, 0); + /* Prepare all sources */ - timeout = block ? -1 : 0; + context->timeout = -1; - hook = g_hook_first_valid (&source_list, TRUE); - while (hook) + source = next_valid_source (context, NULL); + while (source) { - GSource *source = (GSource*) hook; gint source_timeout = -1; if ((n_ready > 0) && (source->priority > current_priority)) { - g_hook_unref (&source_list, hook); + SOURCE_UNREF (source, context); break; } - if (G_HOOK_IN_CALL (hook) && !(hook->flags & G_SOURCE_CAN_RECURSE)) + if ((source->flags & G_HOOK_FLAG_IN_CALL) && !(source->flags & G_SOURCE_CAN_RECURSE)) + goto next; + + if (!(source->flags & G_SOURCE_READY)) { - hook = g_hook_next_valid (&source_list, hook, TRUE); - continue; + gboolean result; + gboolean (*prepare) (GSource *source, + gint *timeout); + + prepare = source->source_funcs->prepare; + context->in_check_or_prepare++; + UNLOCK_CONTEXT (context); + + result = (*prepare) (source, &source_timeout); + + LOCK_CONTEXT (context); + context->in_check_or_prepare--; + + if (result) + source->flags |= G_SOURCE_READY; } - if (!(hook->flags & G_SOURCE_READY)) + if (source->flags & G_SOURCE_READY) { - gboolean (*prepare) (gpointer source_data, - GTimeVal *current_time, - gint *timeout, - gpointer user_data); - - prepare = ((GSourceFuncs *) hook->func)->prepare; - in_check_or_prepare++; - G_UNLOCK (main_loop); - - if ((*prepare) (source->source_data, ¤t_time, &source_timeout, source->hook.data)) - hook->flags |= G_SOURCE_READY; - - G_LOCK (main_loop); - in_check_or_prepare--; - } - - if (hook->flags & G_SOURCE_READY) - { - if (!dispatch) - { - g_hook_unref (&source_list, hook); - G_UNLOCK (main_loop); - - return TRUE; - } - else - { - n_ready++; - current_priority = source->priority; - timeout = 0; - } + n_ready++; + current_priority = source->priority; + context->timeout = 0; } if (source_timeout >= 0) { - if (timeout < 0) - timeout = source_timeout; + if (context->timeout < 0) + context->timeout = source_timeout; else - timeout = MIN (timeout, source_timeout); + context->timeout = MIN (context->timeout, source_timeout); } - hook = g_hook_next_valid (&source_list, hook, TRUE); + next: + source = next_valid_source (context, source); } - /* poll(), if necessary */ - - g_main_poll (timeout, n_ready > 0, current_priority); - - if (timeout != 0) - g_get_current_time (¤t_time); + UNLOCK_CONTEXT (context); - /* Check to see what sources need to be dispatched */ - - n_ready = 0; + if (priority) + *priority = current_priority; - hook = g_hook_first_valid (&source_list, TRUE); - while (hook) - { - GSource *source = (GSource *)hook; - - if ((n_ready > 0) && (source->priority > current_priority)) - { - g_hook_unref (&source_list, hook); - break; - } - if (G_HOOK_IN_CALL (hook) && !(hook->flags & G_SOURCE_CAN_RECURSE)) - { - hook = g_hook_next_valid (&source_list, hook, TRUE); - continue; - } - - if (!(hook->flags & G_SOURCE_READY)) - { - gboolean (*check) (gpointer source_data, - GTimeVal *current_time, - gpointer user_data); - - check = ((GSourceFuncs *) hook->func)->check; - in_check_or_prepare++; - G_UNLOCK (main_loop); - - if ((*check) (source->source_data, ¤t_time, source->hook.data)) - hook->flags |= G_SOURCE_READY; - - G_LOCK (main_loop); - in_check_or_prepare--; - } - - if (hook->flags & G_SOURCE_READY) - { - if (dispatch) - { - hook->flags &= ~G_SOURCE_READY; - g_hook_ref (&source_list, hook); - pending_dispatches = g_slist_prepend (pending_dispatches, source); - current_priority = source->priority; - n_ready++; - } - else - { - g_hook_unref (&source_list, hook); - G_UNLOCK (main_loop); - - return TRUE; - } - } - - hook = g_hook_next_valid (&source_list, hook, TRUE); - } - - /* Now invoke the callbacks */ - - if (pending_dispatches) - { - pending_dispatches = g_slist_reverse (pending_dispatches); - g_main_dispatch (¤t_time); - retval = TRUE; - } - - G_UNLOCK (main_loop); - - return retval; + return (n_ready > 0); } -/* See if any events are pending - */ -gboolean -g_main_pending (void) +/** + * g_main_context_query: + * @context: a #GMainContext + * @max_priority: maximum priority source to check + * @timeout: location to store timeout to be used in polling + * @fds: location to store #GPollFD records that need to be polled. + * @n_fds: length of @fds. + * + * Determines information necessary to poll this main loop. + * + * Return value: + **/ +gint +g_main_context_query (GMainContext *context, + gint max_priority, + gint *timeout, + GPollFD *fds, + gint n_fds) { - return in_check_or_prepare ? FALSE : g_main_iterate (FALSE, FALSE); -} - -/* Run a single iteration of the mainloop. If block is FALSE, - * will never block - */ -gboolean -g_main_iteration (gboolean block) -{ - if (in_check_or_prepare) - { - g_warning ("g_main_iteration(): called recursively from within a source's check() or " - "prepare() member or from a second thread, iteration not possible"); - return FALSE; - } - else - return g_main_iterate (block, TRUE); -} - -GMainLoop* -g_main_new (gboolean is_running) -{ - GMainLoop *loop; - - loop = g_new0 (GMainLoop, 1); - loop->is_running = is_running != FALSE; - - return loop; -} - -void -g_main_run (GMainLoop *loop) -{ - g_return_if_fail (loop != NULL); - - if (in_check_or_prepare) - { - g_warning ("g_main_run(): called recursively from within a source's check() or " - "prepare() member or from a second thread, iteration not possible"); - return; - } - - loop->is_running = TRUE; - while (loop->is_running) - g_main_iterate (TRUE, TRUE); -} - -void -g_main_quit (GMainLoop *loop) -{ - g_return_if_fail (loop != NULL); - - loop->is_running = FALSE; -} - -void -g_main_destroy (GMainLoop *loop) -{ - g_return_if_fail (loop != NULL); - - g_free (loop); -} - -gboolean -g_main_is_running (GMainLoop *loop) -{ - g_return_val_if_fail (loop != NULL, FALSE); - - return loop->is_running; -} - -/* HOLDS: main_loop_lock */ -static void -g_main_poll (gint timeout, - gboolean use_priority, - gint priority) -{ -#ifdef G_MAIN_POLL_DEBUG - GTimer *poll_timer; -#endif - GPollFD *fd_array; + gint n_poll; GPollRec *pollrec; - gint i; - gint npoll; + + LOCK_CONTEXT (context); -#ifdef G_THREADS_ENABLED -#ifndef G_OS_WIN32 - if (wake_up_pipe[0] < 0) - { - if (pipe (wake_up_pipe) < 0) - g_error ("Cannot create pipe main loop wake-up: %s\n", - g_strerror (errno)); - - wake_up_rec.fd = wake_up_pipe[0]; - wake_up_rec.events = G_IO_IN; - g_main_add_poll_unlocked (0, &wake_up_rec); - } -#else - if (wake_up_semaphore == NULL) - { - if ((wake_up_semaphore = CreateSemaphore (NULL, 0, 100, NULL)) == NULL) - g_error ("Cannot create wake-up semaphore: %s", g_win32_error_message (GetLastError ())); - wake_up_rec.fd = (gint) wake_up_semaphore; - wake_up_rec.events = G_IO_IN; -#ifdef G_MAIN_POLL_DEBUG - g_print ("wake-up semaphore: %#x\n", (guint) wake_up_semaphore); -#endif - g_main_add_poll_unlocked (0, &wake_up_rec); - } -#endif -#endif - fd_array = g_new (GPollFD, n_poll_records); - - pollrec = poll_records; - i = 0; - while (pollrec && (!use_priority || priority >= pollrec->priority)) + pollrec = context->poll_records; + n_poll = 0; + while (pollrec && max_priority >= pollrec->priority) { if (pollrec->fd->events) { - fd_array[i].fd = pollrec->fd->fd; - /* In direct contradiction to the Unix98 spec, IRIX runs into - * difficulty if you pass in POLLERR, POLLHUP or POLLNVAL - * flags in the events field of the pollfd while it should - * just ignoring them. So we mask them out here. - */ - fd_array[i].events = pollrec->fd->events & ~(G_IO_ERR|G_IO_HUP|G_IO_NVAL); - fd_array[i].revents = 0; - i++; + if (n_poll < n_fds) + { + fds[n_poll].fd = pollrec->fd->fd; + /* In direct contradiction to the Unix98 spec, IRIX runs into + * difficulty if you pass in POLLERR, POLLHUP or POLLNVAL + * flags in the events field of the pollfd while it should + * just ignoring them. So we mask them out here. + */ + fds[n_poll].events = pollrec->fd->events & ~(G_IO_ERR|G_IO_HUP|G_IO_NVAL); + fds[n_poll].revents = 0; + } + n_poll++; } pollrec = pollrec->next; } -#ifdef G_THREADS_ENABLED - poll_waiting = TRUE; - poll_changed = FALSE; -#endif + + context->poll_changed = FALSE; + + if (timeout) + { + *timeout = context->timeout; + if (timeout != 0) + context->time_is_current = FALSE; + } - npoll = i; - if (npoll || timeout != 0) + UNLOCK_CONTEXT (context); + + return n_poll; +} + +/** + * g_main_context_check: + * @context: a #GMainContext + * @max_priority: the maximum numerical priority of sources to check + * @fds: array of #GPollFD's that was passed to the last call to + * g_main_context_query() + * @n_fds: return value of g_main_context_query() + * + * Pass the results of polling back to the main loop. + * + * Return value: %TRUE if some sources are ready to be dispatched. + **/ +gboolean +g_main_context_check (GMainContext *context, + gint max_priority, + GPollFD *fds, + gint n_fds) +{ + GSource *source; + GPollRec *pollrec; + gint n_ready = 0; + gint i; + + LOCK_CONTEXT (context); + + if (context->in_check_or_prepare) + { + g_warning ("g_main_context_check() called recursively from within a source's check() or " + "prepare() member."); + return FALSE; + } + +#ifdef G_THREADS_ENABLED + if (!context->poll_waiting) + { +#ifndef G_OS_WIN32 + gchar c; + read (context->wake_up_pipe[0], &c, 1); +#endif + } + else + context->poll_waiting = FALSE; +#endif /* G_THREADS_ENABLED */ + + /* If the set of poll file descriptors changed, bail out + * and let the main loop rerun + */ + if (context->poll_changed) + return 0; + + pollrec = context->poll_records; + i = 0; + while (i < n_fds) + { + if (pollrec->fd->events) + { + pollrec->fd->revents = fds[i].revents; + i++; + } + pollrec = pollrec->next; + } + + source = next_valid_source (context, NULL); + while (source) + { + if ((n_ready > 0) && (source->priority > max_priority)) + { + SOURCE_UNREF (source, context); + break; + } + if ((source->flags & G_HOOK_FLAG_IN_CALL) && !(source->flags & G_SOURCE_CAN_RECURSE)) + goto next; + + if (!(source->flags & G_SOURCE_READY)) + { + gboolean result; + gboolean (*check) (GSource *source); + + check = source->source_funcs->check; + + context->in_check_or_prepare++; + UNLOCK_CONTEXT (context); + + result = (*check) (source); + + LOCK_CONTEXT (context); + context->in_check_or_prepare--; + + if (result) + source->flags |= G_SOURCE_READY; + } + + if (source->flags & G_SOURCE_READY) + { + source->ref_count++; + g_ptr_array_add (context->pending_dispatches, source); + + n_ready++; + } + + next: + source = next_valid_source (context, source); + } + + UNLOCK_CONTEXT (context); + + return n_ready > 0; +} + +/** + * g_main_context_dispatch: + * @context: a #GMainContext + * + * Dispatch all pending sources() + **/ +void +g_main_context_dispatch (GMainContext *context) +{ + LOCK_CONTEXT (context); + + if (context->pending_dispatches->len > 0) + { + g_main_dispatch (context); + } + + UNLOCK_CONTEXT (context); +} + +static gboolean +g_main_context_iterate (GMainContext *context, + gboolean block, + gboolean dispatch) +{ + gint max_priority; + gint timeout; + gboolean some_ready; + gint nfds, new_nfds; + GPollFD *fds; + + some_ready = g_main_context_prepare (context, &max_priority); + + do + { + LOCK_CONTEXT (context); + + if (context->cached_poll_array) + { + nfds = context->cached_poll_array_size; + fds = context->cached_poll_array; + context->cached_poll_array = NULL; + } + else + { + nfds = context->cached_poll_array_size = context->n_poll_records; + fds = g_new (GPollFD, nfds); + } + + UNLOCK_CONTEXT (context); + + new_nfds = g_main_context_query (context, max_priority, + &timeout, fds, nfds); + } + while (new_nfds > nfds); + + if (!block) + timeout = 0; + + g_main_context_poll (context, timeout, max_priority, + fds, new_nfds); + + g_main_context_check (context, + max_priority, + fds, new_nfds); + + LOCK_CONTEXT (context); + + g_assert (!context->cached_poll_array); + + context->cached_poll_array = fds; + context->cached_poll_array_size = nfds; + + UNLOCK_CONTEXT (context); + + if (dispatch) + g_main_context_dispatch (context); + + return some_ready; +} + +/** + * g_main_context_pending: + * @context: a #GMainContext (if %NULL, the default context will be used) + * + * Check if any sources have pending events for the given context. + * + * Return value: %TRUE if events are pending. + **/ +gboolean +g_main_context_pending (GMainContext *context) +{ + if (!context) + context = g_main_context_default(); + + return g_main_context_iterate (context, FALSE, FALSE); +} + +/** + * g_main_context_iteration: + * @context: a #GMainContext (if %NULL, the default context will be used) + * @may_block: whether the call may block. + * + * Run a single iteration for the given main loop. This involves + * checking to see if any event sources are ready to be processed, + * then if no events sources are ready and @may_block is %TRUE, waiting + * for a source to become ready, then dispatching the highest priority + * events sources that are ready. Note that even when @may_block is %TRUE, + * it is still possible for g_main_context_iteration() to return + * %FALSE, since the the wait may be interrupted for other + * reasons than an event source becoming ready. + * + * Return value: %TRUE if events were dispatched. + **/ +gboolean +g_main_context_iteration (GMainContext *context, gboolean may_block) +{ + if (!context) + context = g_main_context_default(); + + return g_main_context_iterate (context, may_block, TRUE); +} + +/** + * g_main_loop_new: + * @context: a #GMainContext (if %NULL, the default context will be used). + * @is_running: set to TRUE to indicate that the loop is running. This + * is not very important since calling g_main_run() will set this to + * TRUE anyway. + * + * Create a new #GMainLoop structure + * + * Return value: + **/ +GMainLoop * +g_main_loop_new (GMainContext *context, + gboolean is_running) +{ + GMainLoop *loop; + + if (!context) + context = g_main_context_default(); + + loop = g_new0 (GMainLoop, 1); + loop->context = context; + loop->is_running = is_running != FALSE; + +#ifdef G_THREADS_ENABLED + if (g_thread_supported ()) + loop->mutex = g_mutex_new (); + else + loop->mutex = NULL; + loop->sem_cond = NULL; +#endif G_THREADS_ENABLED + + return loop; +} + +/** + * g_main_loop_run: + * @loop: a #GMainLoop + * + * Run a main loop until g_main_quit() is called on the loop. + * If this is called for the thread of the loop's #GMainContext, + * it will process events from the loop, otherwise it will + * simply wait. + **/ +void +g_main_loop_run (GMainLoop *loop) +{ + g_return_if_fail (loop != NULL); + +#ifdef G_THREADS_ENABLED + if (loop->context->thread != g_thread_self ()) + { + LOCK_LOOP (loop); + + if (!g_thread_supported ()) + { + g_warning ("g_main_loop_run() was called from second thread but" + "g_thread_init() was never called."); + } + else + { + if (!loop->sem_cond) + loop->sem_cond = g_cond_new (); + + if (!loop->is_running) + loop->is_running = TRUE; + + while (loop->is_running) + g_cond_wait (loop->sem_cond, loop->mutex); + } + + UNLOCK_LOOP (loop); + } + else +#endif /* G_THREADS_ENABLED */ + { + LOCK_CONTEXT (loop->context); + if (loop->context->in_check_or_prepare) + { + g_warning ("g_main_run(): called recursively from within a source's check() or " + "prepare() member, iteration not possible."); + return; + } + UNLOCK_CONTEXT (loop->context); + + LOCK_LOOP (loop); + + loop->is_running = TRUE; + while (loop->is_running) + { + UNLOCK_LOOP (loop); + g_main_context_iterate (loop->context, TRUE, TRUE); + LOCK_LOOP (loop); + } + UNLOCK_LOOP (loop); + } +} + +/** + * g_main_loop_quit: + * @loop: a #GMainLoop + * + * Stops a #GMainLoop from running. Any calls to g_main_loop_run() + * for the loop will return. + **/ +void +g_main_loop_quit (GMainLoop *loop) +{ + g_return_if_fail (loop != NULL); + + LOCK_LOOP (loop); + loop->is_running = FALSE; + + if (loop->sem_cond) + g_cond_broadcast (loop->sem_cond); + + UNLOCK_LOOP (loop); + + LOCK_CONTEXT (loop->context); + + g_main_context_wakeup (loop->context); + UNLOCK_CONTEXT (loop->context); +} + +/** + * g_main_loop_destroy: + * @loop: a #GMainLoop + * + * Destroy a #GMainLoop object and free all associated memory. + * The loop must not currently be running via g_main_run(). + **/ +void +g_main_loop_destroy (GMainLoop *loop) +{ + g_return_if_fail (loop != NULL); + g_return_if_fail (!loop->is_running); + +#ifdef G_THREADS_ENABLED + g_mutex_free (loop->mutex); + if (loop->sem_cond) + g_cond_free (loop->sem_cond); +#endif /* G_THREADS_ENABLED */ + + g_free (loop); +} + +/** + * g_main_loop_is_running: + * @loop: a #GMainLoop. + * + * Check to see if the main loop is currently being run via g_main_run() + * + * Return value: %TRUE if the mainloop is currently being run. + **/ +gboolean +g_main_loop_is_running (GMainLoop *loop) +{ + gboolean result; + + g_return_val_if_fail (loop != NULL, FALSE); + + LOCK_LOOP (loop); + result = loop->is_running; + UNLOCK_LOOP (loop); + + return result; +} + +/* HOLDS: context's lock */ +static void +g_main_context_poll (GMainContext *context, + gint timeout, + gint priority, + GPollFD *fds, + gint n_fds) +{ +#ifdef G_MAIN_POLL_DEBUG + GTimer *poll_timer; + GPollRec *pollrec; + gint i; +#endif + + GPollFunc poll_func; + + if (n_fds || timeout != 0) { #ifdef G_MAIN_POLL_DEBUG - g_print ("g_main_poll(%d) timeout: %d\n", npoll, timeout); + g_print ("g_main_poll(%d) timeout: %d\n", n_fds, timeout); poll_timer = g_timer_new (); #endif + + LOCK_CONTEXT (context); + + poll_func = context->poll_func; - G_UNLOCK (main_loop); - if ((*poll_func) (fd_array, npoll, timeout) < 0) + UNLOCK_CONTEXT (context); + if ((*poll_func) (fds, n_fds, timeout) < 0 && errno != EINTR) g_warning ("poll(2) failed due to: %s.", g_strerror (errno)); - G_LOCK (main_loop); #ifdef G_MAIN_POLL_DEBUG + LOCK_CONTEXT (context); + g_print ("g_main_poll(%d) timeout: %d - elapsed %12.10f seconds", - npoll, + n_fds, timeout, g_timer_elapsed (poll_timer, NULL)); g_timer_destroy (poll_timer); - pollrec = poll_records; + pollrec = context->poll_records; i = 0; - while (i < npoll) + while (i < n_fds) { if (pollrec->fd->events) { - if (fd_array[i].revents) + if (fds[i].revents) { - g_print (" [%d:", fd_array[i].fd); - if (fd_array[i].revents & G_IO_IN) + g_print (" [%d:", fds[i].fd); + if (fds[i].revents & G_IO_IN) g_print ("i"); - if (fd_array[i].revents & G_IO_OUT) + if (fds[i].revents & G_IO_OUT) g_print ("o"); - if (fd_array[i].revents & G_IO_PRI) + if (fds[i].revents & G_IO_PRI) g_print ("p"); - if (fd_array[i].revents & G_IO_ERR) + if (fds[i].revents & G_IO_ERR) g_print ("e"); - if (fd_array[i].revents & G_IO_HUP) + if (fds[i].revents & G_IO_HUP) g_print ("h"); - if (fd_array[i].revents & G_IO_NVAL) + if (fds[i].revents & G_IO_NVAL) g_print ("n"); g_print ("]"); } @@ -1094,77 +2208,64 @@ g_main_poll (gint timeout, pollrec = pollrec->next; } g_print ("\n"); + + UNLOCK_CONTEXT (context); #endif - } /* if (npoll || timeout != 0) */ - -#ifdef G_THREADS_ENABLED - if (!poll_waiting) - { -#ifndef G_OS_WIN32 - gchar c; - read (wake_up_pipe[0], &c, 1); -#endif - } - else - poll_waiting = FALSE; - - /* If the set of poll file descriptors changed, bail out - * and let the main loop rerun - */ - if (poll_changed) - { - g_free (fd_array); - return; - } -#endif - - pollrec = poll_records; - i = 0; - while (i < npoll) - { - if (pollrec->fd->events) - { - pollrec->fd->revents = fd_array[i].revents; - i++; - } - pollrec = pollrec->next; - } - - g_free (fd_array); + } /* if (n_fds || timeout != 0) */ } -void -g_main_add_poll (GPollFD *fd, - gint priority) +/** + * g_main_context_add_poll: + * @context: a #GMainContext (or %NULL for the default context) + * @fd: a #GPollFD structure holding information about a file + * descriptor to watch. + * @priority: the priority for this file descriptor which should be + * the same as the priority used for g_source_attach() to ensure that the + * file descriptor is polled whenever the results may be needed. + * + * Add a file descriptor to the set of file descriptors polled * for + * this context. This will very seldom be used directly. Instead + * a typical event source will use g_source_add_poll() instead. + **/ +void +g_main_context_add_poll (GMainContext *context, + GPollFD *fd, + gint priority) { - G_LOCK (main_loop); - g_main_add_poll_unlocked (priority, fd); - G_UNLOCK (main_loop); + if (!context) + context = g_main_context_default (); + + LOCK_CONTEXT (context); + g_main_context_add_poll_unlocked (context, priority, fd); + UNLOCK_CONTEXT (context); } /* HOLDS: main_loop_lock */ static void -g_main_add_poll_unlocked (gint priority, - GPollFD *fd) +g_main_context_add_poll_unlocked (GMainContext *context, + gint priority, + GPollFD *fd) { GPollRec *lastrec, *pollrec, *newrec; - if (!poll_chunk) - poll_chunk = g_mem_chunk_create (GPollRec, 32, G_ALLOC_ONLY); + if (!context->poll_chunk) + context->poll_chunk = g_mem_chunk_create (GPollRec, 32, G_ALLOC_ONLY); - if (poll_free_list) + if (context->poll_free_list) { - newrec = poll_free_list; - poll_free_list = newrec->next; + newrec = context->poll_free_list; + context->poll_free_list = newrec->next; } else - newrec = g_chunk_new (GPollRec, poll_chunk); + newrec = g_chunk_new (GPollRec, context->poll_chunk); + /* This file descriptor may be checked before we ever poll */ + fd->revents = 0; newrec->fd = fd; newrec->priority = priority; lastrec = NULL; - pollrec = poll_records; + pollrec = context->poll_records; while (pollrec && priority >= pollrec->priority) { lastrec = pollrec; @@ -1174,29 +2275,56 @@ g_main_add_poll_unlocked (gint priority, if (lastrec) lastrec->next = newrec; else - poll_records = newrec; + context->poll_records = newrec; newrec->next = pollrec; - n_poll_records++; + context->n_poll_records++; + if (context->cached_poll_array && + context->cached_poll_array_size < context->n_poll_records) + { + g_free (context->cached_poll_array); + context->cached_poll_array = NULL; + } #ifdef G_THREADS_ENABLED - poll_changed = TRUE; + context->poll_changed = TRUE; /* Now wake up the main loop if it is waiting in the poll() */ - g_main_wakeup (); + g_main_context_wakeup (context); #endif } -void -g_main_remove_poll (GPollFD *fd) +/** + * g_main_context_remove_poll: + * @context:a #GMainContext + * @fd: a #GPollFD descriptor previously added with g_main_context_add_poll() + * + * Remove file descriptor from the set of file descriptors to be + * polled for a particular context. + **/ +void +g_main_context_remove_poll (GMainContext *context, + GPollFD *fd) +{ + if (!context) + context = g_main_context_default (); + + LOCK_CONTEXT (context); + + g_main_context_remove_poll_unlocked (context, fd); + + UNLOCK_CONTEXT (context); +} + +static void +g_main_context_remove_poll_unlocked (GMainContext *context, + GPollFD *fd) { GPollRec *pollrec, *lastrec; - G_LOCK (main_loop); - lastrec = NULL; - pollrec = poll_records; + pollrec = context->poll_records; while (pollrec) { @@ -1205,16 +2333,16 @@ g_main_remove_poll (GPollFD *fd) if (lastrec != NULL) lastrec->next = pollrec->next; else - poll_records = pollrec->next; + context->poll_records = pollrec->next; #ifdef ENABLE_GC_FRIENDLY pollrec->fd = NULL; #endif /* ENABLE_GC_FRIENDLY */ - pollrec->next = poll_free_list; - poll_free_list = pollrec; + pollrec->next = context->poll_free_list; + context->poll_free_list = pollrec; - n_poll_records--; + context->n_poll_records--; break; } lastrec = pollrec; @@ -1222,52 +2350,120 @@ g_main_remove_poll (GPollFD *fd) } #ifdef G_THREADS_ENABLED - poll_changed = TRUE; + context->poll_changed = TRUE; /* Now wake up the main loop if it is waiting in the poll() */ - g_main_wakeup (); + g_main_context_wakeup (context); #endif - - G_UNLOCK (main_loop); } -void -g_main_set_poll_func (GPollFunc func) +/** + * g_source_get_current_time: + * @source: a #GSource + * @timeval: #GTimeVal structure in which to store current time. + * + * Gets the "current time" to be used when checking + * this source. The advantage of calling this function over + * calling g_get_current_time() directly is that when + * checking multiple sources, GLib can cache a single value + * instead of having to repeatedly get the system time. + **/ +void +g_source_get_current_time (GSource *source, + GTimeVal *timeval) { + GMainContext *context; + + g_return_if_fail (source->context != NULL); + + context = source->context; + + LOCK_CONTEXT (context); + + if (!context->time_is_current) + { + g_get_current_time (&context->current_time); + context->time_is_current = TRUE; + } + + *timeval = context->current_time; + + UNLOCK_CONTEXT (context); +} + +/** + * g_main_context_set_poll_func: + * @context: a #GMainContext + * @func: the function to call to poll all file descriptors + * + * Sets the function to use to handle polling of file descriptors. It + * will be used instead of the poll() system call (or GLib's + * replacement function, which is used where poll() isn't available). + * + * This function could possibly be used to integrate the GLib event + * loop with an external event loop. + **/ +void +g_main_context_set_poll_func (GMainContext *context, + GPollFunc func) +{ + if (!context) + context = g_main_context_default (); + + LOCK_CONTEXT (context); + if (func) - poll_func = func; + context->poll_func = func; else + { #ifdef HAVE_POLL - poll_func = (GPollFunc) poll; + context->poll_func = (GPollFunc) poll; #else - poll_func = (GPollFunc) g_poll; + context->poll_func = (GPollFunc) g_poll; #endif + } + + UNLOCK_CONTEXT (context); } #ifdef G_OS_WIN32 -/* Useful on other platforms, too? */ - +/** + * g_main_context_get_poll_func: + * @context: a #GMainContext + * + * Gets the poll function set by g_main_context_set_poll_func() + * + * Return value: the poll function + **/ GPollFunc -g_main_win32_get_poll_func (void) +g_main_context_get_poll_func (GMainContext *context) { - return poll_func; + GPollFunc *result; + + if (!context) + context = g_main_context_default (); + + LOCK_CONTEXT (context); + result = context->poll_func; + UNLOCK_CONTEXT (context); } #endif +/* HOLDS: context's lock */ /* Wake the main loop up from a poll() */ static void -g_main_wakeup (void) +g_main_context_wakeup (GMainContext *context) { #ifdef G_THREADS_ENABLED - if (poll_waiting) + if (g_thread_supported() && context->poll_waiting) { - poll_waiting = FALSE; + context->poll_waiting = FALSE; #ifndef G_OS_WIN32 - write (wake_up_pipe[1], "A", 1); + write (context->wake_up_pipe[1], "A", 1); #else - ReleaseSemaphore (wake_up_semaphore, 1, NULL); + ReleaseSemaphore (context->context->wake_up_semaphore, 1, NULL); #endif } #endif @@ -1276,43 +2472,45 @@ g_main_wakeup (void) /* Timeouts */ static void -g_timeout_set_expiration (GTimeoutData *data, - GTimeVal *current_time) +g_timeout_set_expiration (GTimeoutSource *timeout_source, + GTimeVal *current_time) { - guint seconds = data->interval / 1000; - guint msecs = data->interval - seconds * 1000; + guint seconds = timeout_source->interval / 1000; + guint msecs = timeout_source->interval - seconds * 1000; - data->expiration.tv_sec = current_time->tv_sec + seconds; - data->expiration.tv_usec = current_time->tv_usec + msecs * 1000; - if (data->expiration.tv_usec >= 1000000) + timeout_source->expiration.tv_sec = current_time->tv_sec + seconds; + timeout_source->expiration.tv_usec = current_time->tv_usec + msecs * 1000; + if (timeout_source->expiration.tv_usec >= 1000000) { - data->expiration.tv_usec -= 1000000; - data->expiration.tv_sec++; + timeout_source->expiration.tv_usec -= 1000000; + timeout_source->expiration.tv_sec++; } } static gboolean -g_timeout_prepare (gpointer source_data, - GTimeVal *current_time, - gint *timeout, - gpointer user_data) +g_timeout_prepare (GSource *source, + gint *timeout) { glong msec; - GTimeoutData *data = source_data; + GTimeVal current_time; - msec = ((data->expiration.tv_sec - current_time->tv_sec) * 1000 + - (data->expiration.tv_usec - current_time->tv_usec) / 1000); + GTimeoutSource *timeout_source = (GTimeoutSource *)source; + + g_source_get_current_time (source, ¤t_time); + msec = ((timeout_source->expiration.tv_sec - current_time.tv_sec) * 1000 + + (timeout_source->expiration.tv_usec - current_time.tv_usec) / 1000); + if (msec < 0) msec = 0; - else if (msec > data->interval) + else if (msec > timeout_source->interval) { /* The system time has been set backwards, so we - * reset the expiration time to now + data->interval; + * reset the expiration time to now + timeout_source->interval; * this at least avoids hanging for long periods of time. */ - g_timeout_set_expiration (data, current_time); - msec = data->interval; + g_timeout_set_expiration (timeout_source, ¤t_time); + msec = timeout_source->interval; } *timeout = msec; @@ -1321,27 +2519,38 @@ g_timeout_prepare (gpointer source_data, } static gboolean -g_timeout_check (gpointer source_data, - GTimeVal *current_time, - gpointer user_data) +g_timeout_check (GSource *source) { - GTimeoutData *data = source_data; + GTimeVal current_time; + GTimeoutSource *timeout_source = (GTimeoutSource *)source; + + g_source_get_current_time (source, ¤t_time); - return ((data->expiration.tv_sec < current_time->tv_sec) || - ((data->expiration.tv_sec == current_time->tv_sec) && - (data->expiration.tv_usec <= current_time->tv_usec))); + return ((timeout_source->expiration.tv_sec < current_time.tv_sec) || + ((timeout_source->expiration.tv_sec == current_time.tv_sec) && + (timeout_source->expiration.tv_usec <= current_time.tv_usec))); } static gboolean -g_timeout_dispatch (gpointer source_data, - GTimeVal *dispatch_time, - gpointer user_data) +g_timeout_dispatch (GSource *source, + GSourceFunc callback, + gpointer user_data) { - GTimeoutData *data = source_data; + GTimeoutSource *timeout_source = (GTimeoutSource *)source; - if (data->callback (user_data)) + if (!callback) { - g_timeout_set_expiration (data, dispatch_time); + g_warning ("Timeout source dispatched without callback\n" + "You must call g_source_set_callback()."); + return FALSE; + } + + if (callback (user_data)) + { + GTimeVal current_time; + + g_source_get_current_time (source, ¤t_time); + g_timeout_set_expiration (timeout_source, ¤t_time); return TRUE; } @@ -1349,6 +2558,58 @@ g_timeout_dispatch (gpointer source_data, return FALSE; } +/** + * g_timeout_source_new: + * @interval: the timeout interval in milliseconds. + * + * Create a new timeout source. + * + * 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. + * + * Return value: the newly create timeout source + **/ +GSource * +g_timeout_source_new (guint interval) +{ + GSource *source = g_source_new (&timeout_funcs, sizeof (GTimeoutSource)); + GTimeoutSource *timeout_source = (GTimeoutSource *)source; + GTimeVal current_time; + + timeout_source->interval = interval; + + g_get_current_time (¤t_time); + g_timeout_set_expiration (timeout_source, ¤t_time); + + return source; +} + +/** + * g_timeout_add_full: + * @priority: the priority of the idle source. Typically this will be in the + * range btweeen #G_PRIORITY_DEFAULT_IDLE and #G_PRIORITY_HIGH_IDLE. + * @interval: the time between calls to the function, in milliseconds + * (1/1000ths of a second.) + * @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 at regular intervals, with the given + * priority. The function is called repeatedly until it returns + * FALSE, at which point the timeout is automatically destroyed and + * the function will not be called again. The @notify function is + * called when the timeout is destroyed. The first call to the + * function will be at the end of the first @interval. + * + * Note that timeout functions may be delayed, due to the processing of other + * event sources. Thus they should not be relied on for precise timing. + * After each call to the timeout function, the time of the next + * timeout is recalculated based on the current time and the given interval + * (it does not try to 'catch up' time lost in delays). + * + * Return value: the id of event source. + **/ guint g_timeout_add_full (gint priority, guint interval, @@ -1356,18 +2617,41 @@ g_timeout_add_full (gint priority, gpointer data, GDestroyNotify notify) { - GTimeoutData *timeout_data = g_new (GTimeoutData, 1); - GTimeVal current_time; + GSource *source; + + g_return_val_if_fail (function != NULL, 0); - timeout_data->interval = interval; - timeout_data->callback = function; - g_get_current_time (¤t_time); + source = g_timeout_source_new (interval); - g_timeout_set_expiration (timeout_data, ¤t_time); + if (priority != G_PRIORITY_DEFAULT) + g_source_set_priority (source, priority); - return g_source_add (priority, FALSE, &timeout_funcs, timeout_data, data, notify); + g_source_set_callback (source, function, data, notify); + return g_source_attach (source, NULL); } +/** + * g_timeout_add: + * @interval: the time between calls to the function, in milliseconds + * (1/1000ths of a second.) + * @function: function to call + * @data: data to pass to @function + * + * Sets a function to be called at regular intervals, with the default + * priority, #G_PRIORITY_DEFAULT. The function is called repeatedly + * until it returns FALSE, at which point the timeout is automatically + * destroyed and the function will not be called again. The @notify + * function is called when the timeout is destroyed. The first call + * to the function will be at the end of the first @interval. + * + * Note that timeout functions may be delayed, due to the processing of other + * event sources. Thus they should not be relied on for precise timing. + * After each call to the timeout function, the time of the next + * timeout is recalculated based on the current time and the given interval + * (it does not try to 'catch up' time lost in delays). + * + * Return value: the id of event source. + **/ guint g_timeout_add (guint32 interval, GSourceFunc function, @@ -1380,10 +2664,8 @@ g_timeout_add (guint32 interval, /* Idle functions */ static gboolean -g_idle_prepare (gpointer source_data, - GTimeVal *current_time, - gint *timeout, - gpointer user_data) +g_idle_prepare (GSource *source, + gint *timeout) { *timeout = 0; @@ -1391,34 +2673,89 @@ g_idle_prepare (gpointer source_data, } static gboolean -g_idle_check (gpointer source_data, - GTimeVal *current_time, - gpointer user_data) +g_idle_check (GSource *source) { return TRUE; } static gboolean -g_idle_dispatch (gpointer source_data, - GTimeVal *dispatch_time, - gpointer user_data) +g_idle_dispatch (GSource *source, + GSourceFunc callback, + gpointer user_data) { - GSourceFunc func = (GSourceFunc) source_data; - - return func (user_data); + if (!callback) + { + g_warning ("Idle source dispatched without callback\n" + "You must call g_source_set_callback()."); + return FALSE; + } + + return callback (user_data); } +/** + * g_idle_source_new: + * + * Create a new idle source. + * + * 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. + * + * Return value: the newly created idle source + **/ +GSource * +g_idle_source_new (void) +{ + return g_source_new (&idle_funcs, sizeof (GSource)); +} + +/** + * g_idle_add_full: + * @priority: the priority of the idle source. Typically this will be in the + * range btweeen #G_PRIORITY_DEFAULT_IDLE and #G_PRIORITY_HIGH_IDLE. + * @function: function to call + * @data: data to pass to @function + * @notify: function to call when the idle is removed, or %NULL + * + * Adds a function to be called whenever there are no higher priority + * events pending. If the function returns FALSE it is automatically + * removed from the list of event sources and will not be called again. + * + * Return value: the id of the event source. + **/ guint g_idle_add_full (gint priority, GSourceFunc function, gpointer data, GDestroyNotify notify) { + GSource *source; + g_return_val_if_fail (function != NULL, 0); - return g_source_add (priority, FALSE, &idle_funcs, (gpointer) function, data, notify); + source = g_idle_source_new (); + + if (priority != G_PRIORITY_DEFAULT) + g_source_set_priority (source, priority); + + g_source_set_callback (source, function, data, notify); + return g_source_attach (source, NULL); } +/** + * g_idle_add: + * @function: function to call + * @data: data to pass to @function. + * + * Adds a function to be called whenever there are no higher priority + * events pending to the default main loop. The function is given the + * default idle priority, #G_PRIORITY_DEFAULT_IDLE. If the function + * returns FALSE it is automatically removed from the list of event + * sources and will not be called again. + * + * Return value: the id of the event source. + **/ guint g_idle_add (GSourceFunc function, gpointer data) @@ -1426,8 +2763,17 @@ g_idle_add (GSourceFunc function, return g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, function, data, NULL); } +/** + * g_idle_remove_by_data: + * @data: the data for the idle source's callback. + * + * Removes the idle function with the given data. + * + * Return value: %TRUE if an idle source was found and removed. + **/ gboolean g_idle_remove_by_data (gpointer data) { return g_source_remove_by_funcs_user_data (&idle_funcs, data); } + diff --git a/glib/gmain.h b/glib/gmain.h index 7f08c3dfb..8e4d7fce7 100644 --- a/glib/gmain.h +++ b/glib/gmain.h @@ -1,122 +1,87 @@ -/* GLIB - Library of useful routines for C programming - * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald +/* gmain.h - the GLib Main loop + * Copyright (C) 1998-2000 Red Hat, Inc. * * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public + * modify it under the terms of the GNU Library 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. + * Library General Public License for more details. * - * You should have received a copy of the GNU Lesser General Public + * You should have received a copy of the GNU Library 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. */ -/* - * Modified by the GLib Team and others 1997-2000. See the AUTHORS - * file for a list of people on the GLib Team. See the ChangeLog - * files for a list of changes. These files are distributed with - * GLib at ftp://ftp.gtk.org/pub/gtk/. - */ - #ifndef __G_MAIN_H__ #define __G_MAIN_H__ -#include +#include +#include G_BEGIN_DECLS -/* Main loop - */ -typedef struct _GTimeVal GTimeVal; -typedef struct _GSourceFuncs GSourceFuncs; -typedef struct _GMainLoop GMainLoop; /* Opaque */ +typedef struct _GMainContext GMainContext; /* Opaque */ +typedef struct _GMainLoop GMainLoop; /* Opaque */ +typedef struct _GSource GSource; +typedef struct _GSourceCallbackFuncs GSourceCallbackFuncs; +typedef struct _GSourceFuncs GSourceFuncs; -struct _GTimeVal +typedef gboolean (*GSourceFunc) (gpointer data); + +struct _GSource { - glong tv_sec; - glong tv_usec; + /*< private >*/ + gpointer callback_data; + GSourceCallbackFuncs *callback_funcs; + + GSourceFuncs *source_funcs; + guint ref_count; + + GMainContext *context; + + gint priority; + guint flags; + guint id; + + GSList *poll_fds; + + GSource *prev; + GSource *next; }; + +struct _GSourceCallbackFuncs +{ + void (*ref) (gpointer cb_data); + void (*unref) (gpointer cb_data); + void (*get) (gpointer cb_data, + GSourceFunc *func, + gpointer *data); +}; + struct _GSourceFuncs { - gboolean (*prepare) (gpointer source_data, - GTimeVal *current_time, - gint *timeout, - gpointer user_data); - gboolean (*check) (gpointer source_data, - GTimeVal *current_time, - gpointer user_data); - gboolean (*dispatch) (gpointer source_data, - GTimeVal *dispatch_time, - gpointer user_data); - GDestroyNotify destroy; + gboolean (*prepare) (GSource *source, + gint *timeout); + gboolean (*check) (GSource *source); + gboolean (*dispatch) (GSource *source, + GSourceFunc callback, + gpointer user_data); + void (*destroy) (GSource *source); }; -/* Standard priorities */ - -#define G_PRIORITY_HIGH -100 -#define G_PRIORITY_DEFAULT 0 -#define G_PRIORITY_HIGH_IDLE 100 -#define G_PRIORITY_DEFAULT_IDLE 200 -#define G_PRIORITY_LOW 300 - -typedef gboolean (*GSourceFunc) (gpointer data); - -/* Hooks for adding to the main loop */ -guint g_source_add (gint priority, - gboolean can_recurse, - GSourceFuncs *funcs, - gpointer source_data, - gpointer user_data, - GDestroyNotify notify); -gboolean g_source_remove (guint tag); -gboolean g_source_remove_by_user_data (gpointer user_data); -gboolean g_source_remove_by_source_data (gpointer source_data); -gboolean g_source_remove_by_funcs_user_data (GSourceFuncs *funcs, - gpointer user_data); - -void g_get_current_time (GTimeVal *result); - -/* Running the main loop */ -GMainLoop* g_main_new (gboolean is_running); -void g_main_run (GMainLoop *loop); -void g_main_quit (GMainLoop *loop); -void g_main_destroy (GMainLoop *loop); -gboolean g_main_is_running (GMainLoop *loop); - -/* Run a single iteration of the mainloop. If block is FALSE, - * will never block - */ -gboolean g_main_iteration (gboolean may_block); - -/* See if any events are pending */ -gboolean g_main_pending (void); - -/* 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 destroy); -gboolean g_idle_remove_by_data (gpointer data); - -/* GPollFD +/* Any definitions using GPollFD or GPollFunc are primarily + * for Unix and not guaranteed to be the compatible on all + * operating systems on which GLib runs. Right now, the + * GLib does use these functions on Win32 as well, but interprets + * them in a fairly different way than on Unix. If you use + * these definitions, you are should be prepared to recode + * for different operating systems. * - * System-specific IO and main loop calls * * On Win32, the fd in a GPollFD should be Win32 HANDLE (*not* a file * descriptor as provided by the C runtime) that can be used by @@ -137,32 +102,185 @@ gboolean g_idle_remove_by_data (gpointer data); * Win32. It's really only written for the GIMP's needs so * far. */ - typedef struct _GPollFD GPollFD; -typedef gint (*GPollFunc) (GPollFD *ufds, - guint nfsd, - gint timeout); +typedef gint (*GPollFunc) (GPollFD *ufds, + guint nfsd, + gint timeout); + struct _GPollFD { - gint fd; - gushort events; - gushort revents; + gint fd; + gushort events; + gushort revents; }; -void g_main_add_poll (GPollFD *fd, - gint priority); -void g_main_remove_poll (GPollFD *fd); -void g_main_set_poll_func (GPollFunc func); +/* Standard priorities */ + +#define G_PRIORITY_HIGH -100 +#define G_PRIORITY_DEFAULT 0 +#define G_PRIORITY_HIGH_IDLE 100 +#define G_PRIORITY_DEFAULT_IDLE 200 +#define G_PRIORITY_LOW 300 + +/* GMainContext: */ + +GMainContext *g_main_context_get (GThread *thread); +GMainContext *g_main_context_default (void); + +gboolean g_main_context_iteration (GMainContext *context, + gboolean may_block); +gboolean g_main_context_pending (GMainContext *context); + +/* For implementation of legacy interfaces + */ +GSource *g_main_context_find_source_by_id (GMainContext *context, + guint id); +GSource *g_main_context_find_source_by_user_data (GMainContext *context, + gpointer user_data); +GSource *g_main_context_find_source_by_funcs_user_data (GMainContext *context, + GSourceFuncs *funcs, + gpointer user_data); + +/* Low level functions for implementing custom main loops. + */ +gboolean g_main_context_prepare (GMainContext *context, + gint *priority); +gint g_main_context_query (GMainContext *context, + gint max_priority, + gint *timeout, + GPollFD *fds, + gint n_fds); +gint g_main_context_check (GMainContext *context, + gint max_priority, + GPollFD *fds, + gint n_fds); +void g_main_context_dispatch (GMainContext *context); + +void g_main_context_set_poll_func (GMainContext *context, + GPollFunc func); +GPollFunc g_main_context_get_poll_func (GMainContext *context); + +/* Low level functions for use by source implementations + */ +void g_main_context_add_poll (GMainContext *context, + GPollFD *fd, + gint priority); +void g_main_context_remove_poll (GMainContext *context, + GPollFD *fd); + +/* GMainLoop: */ + +GMainLoop *g_main_loop_new (GMainContext *context, + gboolean is_running); +void g_main_loop_run (GMainLoop *loop); +void g_main_loop_quit (GMainLoop *loop); +void g_main_loop_destroy (GMainLoop *loop); +gboolean g_main_loop_is_running (GMainLoop *loop); + +/* GSource: */ + +GSource *g_source_new (GSourceFuncs *source_funcs, + guint struct_size); +GSource *g_source_ref (GSource *source); +void g_source_unref (GSource *source); + +guint g_source_attach (GSource *source, + GMainContext *context); +void g_source_destroy (GSource *source); + +void g_source_set_priority (GSource *source, + gint priority); +gint g_source_get_priority (GSource *source); +void g_source_set_can_recurse (GSource *source, + gboolean can_recurse); +gboolean g_source_get_can_recurse (GSource *source); +guint g_source_get_id (GSource *source); + +GMainContext *g_source_get_context (GSource *source); + +void g_source_set_callback (GSource *source, + GSourceFunc func, + gpointer data, + GDestroyNotify notify); + + +/* Used to implement g_source_connect_closure and internally*/ +void g_source_set_callback_indirect (GSource *source, + gpointer callback_data, + GSourceCallbackFuncs *callback_funcs); + +void g_source_add_poll (GSource *source, + GPollFD *fd); +void g_source_remove_poll (GSource *source, + GPollFD *fd); + +void g_source_get_current_time (GSource *source, + GTimeVal *timeval); + + /* void g_source_connect_closure (GSource *source, + GClosure *closure); + */ + +/* Specific source types + */ +GSource *g_idle_source_new (void); +GSource *g_timeout_source_new (guint interval); + +/* Miscellaneous functions + */ +void g_get_current_time (GTimeVal *result); + +/* ============== Compat main loop stuff ================== */ + +/* Legacy names for GMainLoop functions + */ +#define g_main_new(is_running) g_main_loop_new (NULL, is_running); +#define g_main_run(loop) g_main_loop_run(loop) +#define g_main_quit(loop) g_main_loop_quit(loop) +#define g_main_destroy(loop) g_main_loop_destroy(loop) +#define g_main_is_running(loop) g_main_loop_is_running(loop) + +/* Source manipulation by ID */ +gboolean g_source_remove (guint tag); +gboolean g_source_remove_by_user_data (gpointer user_data); +gboolean g_source_remove_by_funcs_user_data (GSourceFuncs *funcs, + gpointer user_data); + +/* Functions to manipulate the default main loop + */ + +#define g_main_iteration(may_block) g_main_context_iteration (NULL, may_block) +#define g_main_pending() g_main_context_pending (NULL) + +#define g_main_set_poll_func(func) g_main_context_set_poll_func (NULL, func) + +/* 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); #ifdef G_OS_WIN32 -/* Useful on other platforms, too? */ - -GPollFunc g_main_win32_get_poll_func (void); - -#endif +/* This is used to add polling for Windows messages. GDK (GTK+) programs + * should *not* use this. + */ +void g_main_poll_win32_msg_add (gint priority, + GPollFD *fd, + guint hwnd); +#endif G_OS_WIN32 G_END_DECLS #endif /* __G_MAIN_H__ */ - diff --git a/glib/gthread.h b/glib/gthread.h index e99c78d5e..12681784b 100644 --- a/glib/gthread.h +++ b/glib/gthread.h @@ -28,7 +28,6 @@ #define __G_THREAD_H__ #include -#include G_BEGIN_DECLS diff --git a/glib/gtypes.h b/glib/gtypes.h index c43d322a7..9614c73db 100644 --- a/glib/gtypes.h +++ b/glib/gtypes.h @@ -314,6 +314,14 @@ union _GDoubleIEEE754 #error unknown ENDIAN type #endif /* !G_LITTLE_ENDIAN && !G_BIG_ENDIAN */ +typedef struct _GTimeVal GTimeVal; + +struct _GTimeVal +{ + glong tv_sec; + glong tv_usec; +}; + G_END_DECLS #endif /* __G_TYPES_H__ */ diff --git a/gmain.c b/gmain.c index ad03a35c0..d87022c30 100644 --- a/gmain.c +++ b/gmain.c @@ -63,9 +63,9 @@ /* Types */ -typedef struct _GTimeoutData GTimeoutData; -typedef struct _GSource GSource; +typedef struct _GTimeoutSource GTimeoutSource; typedef struct _GPollRec GPollRec; +typedef struct _GSourceCallback GSourceCallback; typedef enum { @@ -73,23 +73,76 @@ typedef enum G_SOURCE_CAN_RECURSE = 1 << (G_HOOK_FLAG_USER_SHIFT + 1) } GSourceFlags; -struct _GSource +struct _GMainContext { - GHook hook; - gint priority; - gpointer source_data; +#ifdef G_THREADS_ENABLED + /* The following lock is used for both the list of sources + * and the list of poll records + */ + GMutex *mutex; + GThread *thread; +#endif + + GPtrArray *pending_dispatches; + gint timeout; /* Timeout for current iteration */ + + guint next_id; + GSource *source_list; + gint in_check_or_prepare; + + GPollRec *poll_records; + GPollRec *poll_free_list; + GMemChunk *poll_chunk; + guint n_poll_records; + GPollFD *cached_poll_array; + gint cached_poll_array_size; + +#ifdef G_THREADS_ENABLED +#ifndef G_OS_WIN32 +/* this pipe is used to wake up the main loop when a source is added. + */ + gint wake_up_pipe[2]; +#else /* G_OS_WIN32 */ + HANDLE wake_up_semaphore; +#endif /* G_OS_WIN32 */ + + GPollFD wake_up_rec; + gboolean poll_waiting; + +/* Flag indicating whether the set of fd's changed during a poll */ + gboolean poll_changed; +#endif /* G_THREADS_ENABLED */ + + GPollFunc poll_func; + + GTimeVal current_time; + gboolean time_is_current; +}; + +struct _GSourceCallback +{ + guint ref_count; + GSourceFunc func; + gpointer data; + GDestroyNotify notify; }; struct _GMainLoop { + GMainContext *context; gboolean is_running; + +#ifdef G_THREADS_ENABLED + GMutex *mutex; + GCond *sem_cond; +#endif G_THREADS_ENABLED }; -struct _GTimeoutData +struct _GTimeoutSource { + GSource source; GTimeVal expiration; gint interval; - GSourceFunc callback; }; struct _GPollRec @@ -99,57 +152,71 @@ struct _GPollRec GPollRec *next; }; +#ifdef G_THREADS_ENABLED +#define LOCK_CONTEXT(context) g_mutex_lock(context->mutex) +#define UNLOCK_CONTEXT(context) g_mutex_unlock(context->mutex) +#define LOCK_LOOP(loop) g_mutex_lock(loop->mutex) +#define UNLOCK_LOOP(loop) g_mutex_unlock(loop->mutex) +#else +#define LOCK_CONTEXT(context) (void)0 +#define UNLOCK_CONTEXT(context) (void)0 +#define LOCK_LOOP(context) (void)0 +#define UNLOCK_LOOP(context) (void)0 +#endif + +#define SOURCE_DESTROYED(source) (((source)->flags & G_HOOK_FLAG_ACTIVE) == 0) + +#define SOURCE_UNREF(source, context) \ + G_STMT_START { \ + if ((source)->ref_count > 1) \ + (source)->ref_count--; \ + else \ + g_source_unref_internal ((source), (context), TRUE); \ + } G_STMT_END + + /* Forward declarations */ -static gint g_source_compare (GHook *a, - GHook *b); -static void g_source_destroy_func (GHookList *hook_list, - GHook *hook); -static void g_main_poll (gint timeout, - gboolean use_priority, - gint priority); -static void g_main_add_poll_unlocked (gint priority, - GPollFD *fd); -static void g_main_wakeup (void); +static void g_source_unref_internal (GSource *source, + GMainContext *context, + gboolean have_lock); +static void g_source_destroy_internal (GSource *source, + GMainContext *context, + gboolean have_lock); +static void g_main_context_poll (GMainContext *context, + gint timeout, + gint priority, + GPollFD *fds, + gint n_fds); +static void g_main_context_add_poll_unlocked (GMainContext *context, + gint priority, + GPollFD *fd); +static void g_main_context_remove_poll_unlocked (GMainContext *context, + GPollFD *fd); +static void g_main_context_wakeup (GMainContext *context); -static gboolean g_timeout_prepare (gpointer source_data, - GTimeVal *current_time, - gint *timeout, - gpointer user_data); -static gboolean g_timeout_check (gpointer source_data, - GTimeVal *current_time, - gpointer user_data); -static gboolean g_timeout_dispatch (gpointer source_data, - GTimeVal *dispatch_time, - gpointer user_data); -static gboolean g_idle_prepare (gpointer source_data, - GTimeVal *current_time, - gint *timeout, - gpointer user_data); -static gboolean g_idle_check (gpointer source_data, - GTimeVal *current_time, - gpointer user_data); -static gboolean g_idle_dispatch (gpointer source_data, - GTimeVal *dispatch_time, - gpointer user_data); +static gboolean g_timeout_prepare (GSource *source, + gint *timeout); +static gboolean g_timeout_check (GSource *source); +static gboolean g_timeout_dispatch (GSource *source, + GSourceFunc callback, + gpointer user_data); +static gboolean g_idle_prepare (GSource *source, + gint *timeout); +static gboolean g_idle_check (GSource *source); +static gboolean g_idle_dispatch (GSource *source, + GSourceFunc callback, + gpointer user_data); -/* Data */ - -static GSList *pending_dispatches = NULL; -static GHookList source_list = { 0 }; -static gint in_check_or_prepare = 0; - -/* The following lock is used for both the list of sources - * and the list of poll records - */ G_LOCK_DEFINE_STATIC (main_loop); +static GMainContext *default_main_context; static GSourceFuncs timeout_funcs = { g_timeout_prepare, g_timeout_check, g_timeout_dispatch, - g_free, + NULL }; static GSourceFuncs idle_funcs = @@ -157,36 +224,16 @@ static GSourceFuncs idle_funcs = g_idle_prepare, g_idle_check, g_idle_dispatch, - NULL, + NULL }; -static GPollRec *poll_records = NULL; -static GPollRec *poll_free_list = NULL; -static GMemChunk *poll_chunk; -static guint n_poll_records = 0; - -#ifdef G_THREADS_ENABLED -#ifndef G_OS_WIN32 -/* this pipe is used to wake up the main loop when a source is added. - */ -static gint wake_up_pipe[2] = { -1, -1 }; -#else /* G_OS_WIN32 */ -static HANDLE wake_up_semaphore = NULL; -#endif /* G_OS_WIN32 */ -static GPollFD wake_up_rec; -static gboolean poll_waiting = FALSE; - -/* Flag indicating whether the set of fd's changed during a poll */ -static gboolean poll_changed = FALSE; -#endif /* G_THREADS_ENABLED */ - #ifdef HAVE_POLL /* SunOS has poll, but doesn't provide a prototype. */ # if defined (sun) && !defined (__SVR4) extern gint poll (GPollFD *ufds, guint nfsd, gint timeout); # endif /* !sun */ -static GPollFunc poll_func = (GPollFunc) poll; #else /* !HAVE_POLL */ + #ifdef G_OS_WIN32 static gint @@ -436,185 +483,946 @@ g_poll (GPollFD *fds, #endif /* !G_OS_WIN32 */ -static GPollFunc poll_func = g_poll; #endif /* !HAVE_POLL */ +/* Called to clean up when a thread terminates + */ +static void +g_main_context_destroy (GMainContext *context) +{ + GSource *source; + + /* We need the lock here only because g_source_destroy expects + * to be able to unlock when destroying the source's data + */ + LOCK_CONTEXT (context); + source = context->source_list; + while (source) + { + GSource *next = source->next; + g_source_destroy_internal (source, context, TRUE); + source = next; + } + UNLOCK_CONTEXT (context); + +#ifdef G_THREADS_ENABLED + g_mutex_free (context->mutex); +#endif + + g_ptr_array_free (context->pending_dispatches, TRUE); + g_free (context->cached_poll_array); + + g_mem_chunk_destroy (context->poll_chunk); + + if (g_thread_supported()) + { +#ifndef G_OS_WIN32 + close (context->wake_up_pipe[0]); + close (context->wake_up_pipe[1]); +#else + CloseHandle (context->wake_up_semaphore); +#endif + } + + g_free (context); +} + +/** + * g_main_context_get: + * @thread: a #GThread + * + * Retrieves the main loop context for a particular thread. This + * will create the main context for the thread if none previously + * existed. The context will exist until the thread terminates. + * + * Return value: the main loop context for @thread. + **/ +GMainContext * +g_main_context_get (GThread *thread) +{ + static GStaticPrivate private_key = G_STATIC_PRIVATE_INIT; + GMainContext *context; + + g_return_val_if_fail (thread != NULL, NULL); + + if (g_thread_supported ()) + context = g_static_private_get_for_thread (&private_key, thread); + else + context = default_main_context; + + if (!context) + { + context = g_new0 (GMainContext, 1); + +#ifdef G_THREADS_ENABLED + if (g_thread_supported ()) + context->mutex = g_mutex_new(); + + context->thread = thread; +#endif + + context->next_id = 1; + + context->source_list = NULL; + +#if HAVE_POLL + context->poll_func = (GPollFunc)poll; +#else + context->poll_func = g_poll; +#endif + + context->cached_poll_array = NULL; + context->cached_poll_array_size = 0; + + context->pending_dispatches = g_ptr_array_new (); + + context->time_is_current = FALSE; + +#ifdef G_THREADS_ENABLED + if (g_thread_supported ()) + { +#ifndef G_OS_WIN32 + if (pipe (context->wake_up_pipe) < 0) + g_error ("Cannot create pipe main loop wake-up: %s\n", + g_strerror (errno)); + + context->wake_up_rec.fd = context->wake_up_pipe[0]; + context->wake_up_rec.events = G_IO_IN; + g_main_context_add_poll_unlocked (context, 0, &context->wake_up_rec); +#else + if ((context->wake_up_semaphore = CreateSemaphore (NULL, 0, 100, NULL)) == NULL) + g_error ("Cannot create wake-up semaphore: %s", g_win32_error_message (GetLastError ())); + context->wake_up_rec.fd = (gint) context->wake_up_semaphore; + context->wake_up_rec.events = G_IO_IN; +#ifdef G_MAIN_POLL_DEBUG + g_print ("wake-up semaphore: %#x\n", (guint) context->wake_up_semaphore); +#endif + g_main_context_add_poll_unlocked (context, 0, &context->wake_up_rec); +#endif + } +#endif + + if (g_thread_supported ()) + g_static_private_set_for_thread (&private_key, thread, + context, + (GDestroyNotify)g_main_context_destroy); + else + default_main_context = context; + } + + return context; +} + +/** + * g_main_context_default: + * + * Return the default main context. This is the main context used + * for main loop functions when a main loop is not explicitly + * specified. + * + * Return value: the default main context. + **/ +GMainContext * +g_main_context_default (void) +{ + /* Slow, but safe */ + + G_LOCK (main_loop); + + if (!default_main_context) + default_main_context = g_main_context_get (g_thread_self ()); + + G_UNLOCK (main_loop); + + return default_main_context; +} + /* Hooks for adding to the main loop */ -/* Use knowledge of insert_sorted algorithm here to make - * sure we insert at the end of equal priority items - */ -static gint -g_source_compare (GHook *a, - GHook *b) +/** + * g_source_new: + * @source_funcs: structure containing functions that implement + * the sources behavior. + * @struct_size: size of the #GSource structure to create + * + * Create a new GSource structure. The size is specified to + * allow creating structures derived from GSource that contain + * additional data. The size passed in must be at least + * sizeof(GSource). + * + * The source will not initially be associated with any #GMainContext + * and must be added to one with g_source_add() before it will be + * executed. + * + * Return value: the newly create #GSource + **/ +GSource * +g_source_new (GSourceFuncs *source_funcs, + guint struct_size) { - GSource *source_a = (GSource *)a; - GSource *source_b = (GSource *)b; - - return (source_a->priority < source_b->priority) ? -1 : 1; -} - -/* HOLDS: main_loop lock */ -static void -g_source_destroy_func (GHookList *hook_list, - GHook *hook) -{ - GSource *source = (GSource*) hook; - GDestroyNotify destroy; - - G_UNLOCK (main_loop); - - destroy = hook->destroy; - if (destroy) - destroy (hook->data); - - destroy = ((GSourceFuncs*) hook->func)->destroy; - if (destroy) - destroy (source->source_data); - - G_LOCK (main_loop); -} - -guint -g_source_add (gint priority, - gboolean can_recurse, - GSourceFuncs *funcs, - gpointer source_data, - gpointer user_data, - GDestroyNotify notify) -{ - guint return_val; GSource *source; - G_LOCK (main_loop); + g_return_val_if_fail (source_funcs != NULL, NULL); + g_return_val_if_fail (struct_size >= sizeof (GSource), NULL); + + source = (GSource*) g_malloc0 (struct_size); - if (!source_list.is_setup) + source->source_funcs = source_funcs; + source->ref_count = 1; + + source->priority = G_PRIORITY_DEFAULT; + + source->flags = G_HOOK_FLAG_ACTIVE; + + /* NULL/0 initialization for all other fields */ + + return source; +} + +/* Holds context's lock + */ +static void +g_source_list_add (GSource *source, + GMainContext *context) +{ + GSource *tmp_source, *last_source; + + last_source = NULL; + tmp_source = context->source_list; + while (tmp_source && tmp_source->priority <= source->priority) { - g_hook_list_init (&source_list, sizeof (GSource)); - - source_list.hook_destroy = G_HOOK_DEFERRED_DESTROY; - source_list.hook_free = g_source_destroy_func; + last_source = tmp_source; + tmp_source = tmp_source->next; } - source = (GSource*) g_hook_alloc (&source_list); - source->priority = priority; - source->source_data = source_data; - source->hook.func = funcs; - source->hook.data = user_data; - source->hook.destroy = notify; + source->next = tmp_source; + if (tmp_source) + tmp_source->prev = source; - g_hook_insert_sorted (&source_list, - (GHook *)source, - g_source_compare); + source->prev = last_source; + if (last_source) + last_source->next = source; + else + context->source_list = source; +} - if (can_recurse) - source->hook.flags |= G_SOURCE_CAN_RECURSE; +/* Holds context's lock + */ +static void +g_source_list_remove (GSource *source, + GMainContext *context) +{ + if (source->prev) + source->prev->next = source->next; + else + context->source_list = source->next; - return_val = source->hook.hook_id; + if (source->next) + source->next->prev = source->prev; + + source->prev = NULL; + source->next = NULL; +} + +/** + * g_source_attach: + * @source: a #GSource + * @context: a #GMainContext (if %NULL, the default context will be used) + * + * Adds a #GSource to a @context so that it will be executed within + * that context. + * + * Return value: the ID for the source within the #GMainContext + **/ +guint +g_source_attach (GSource *source, + GMainContext *context) +{ + guint result = 0; + GSList *tmp_list; + + g_return_val_if_fail (source->context == NULL, 0); + g_return_val_if_fail (!SOURCE_DESTROYED (source), 0); + + if (!context) + context = g_main_context_default (); + + LOCK_CONTEXT (context); + + source->context = context; + result = source->id = context->next_id++; + + g_source_list_add (source, context); + + tmp_list = source->poll_fds; + while (tmp_list) + { + g_main_context_add_poll_unlocked (context, source->priority, tmp_list->data); + tmp_list = tmp_list->next; + } #ifdef G_THREADS_ENABLED /* Now wake up the main loop if it is waiting in the poll() */ - g_main_wakeup (); + g_main_context_wakeup (context); #endif - - G_UNLOCK (main_loop); - return return_val; + UNLOCK_CONTEXT (context); + + return result; } +static void +g_source_destroy_internal (GSource *source, + GMainContext *context, + gboolean have_lock) +{ + if (!have_lock) + LOCK_CONTEXT (context); + + if (!SOURCE_DESTROYED (source)) + { + gpointer old_cb_data; + GSourceCallbackFuncs *old_cb_funcs; + + source->flags &= ~G_HOOK_FLAG_ACTIVE; + + old_cb_data = source->callback_data; + old_cb_funcs = source->callback_funcs; + + source->callback_data = NULL; + source->callback_funcs = NULL; + + if (old_cb_funcs) + { + UNLOCK_CONTEXT (context); + old_cb_funcs->unref (old_cb_data); + LOCK_CONTEXT (context); + } + + g_source_unref_internal (source, context, TRUE); + } + + if (!have_lock) + UNLOCK_CONTEXT (context); +} + +/** + * g_source_destroy: + * @source: a #GSource + * + * Remove a source from its #GMainContext, if any, and mark it as + * destroyed. The source cannot be subsequently added to another + * context. + **/ +void +g_source_destroy (GSource *source) +{ + GMainContext *context; + + g_return_if_fail (source != NULL); + + context = source->context; + + if (context) + g_source_destroy_internal (source, context, FALSE); + else + source->flags &= ~G_HOOK_FLAG_ACTIVE; +} + +/** + * g_source_get_id: + * @source: a #GSource + * + * Return the numeric ID for a particular source. The ID of a source + * is unique within a particular main loop context. The reverse + * mapping from ID to source is done by g_main_context_find_source_by_id(). + * + * Return value: the ID for the source + **/ +guint +g_source_get_id (GSource *source) +{ + guint result; + + g_return_val_if_fail (source != NULL, 0); + g_return_val_if_fail (source->context != NULL, 0); + + LOCK_CONTEXT (source->context); + result = source->id; + UNLOCK_CONTEXT (source->context); + + return result; +} + +/** + * g_source_get_context: + * @source: a #GSource + * + * Get the #GMainContext with which the source is associated. + * Calling this function on a destroyed source is an error. + * + * Return value: the #GMainContext with which the source is associated, + * or %NULL if the context has not yet been added + * to a source. + **/ +GMainContext * +g_source_get_context (GSource *source) +{ + g_return_val_if_fail (!SOURCE_DESTROYED (source), NULL); + + return source->context; +} + +/** + * g_main_source_add_poll: + * @source:a #GSource + * @fd: a #GPollFD structure holding information about a file + * descriptor to watch. + * + * Add a file descriptor to the set of file descriptors polled * for + * this source. This is usually combined with g_source_new() to add an + * event source. The event source's check function will typically test + * the revents field in the #GPollFD struct and return %TRUE if events need + * to be processed. + **/ +void +g_source_add_poll (GSource *source, + GPollFD *fd) +{ + GMainContext *context; + + g_return_if_fail (source != NULL); + g_return_if_fail (fd != NULL); + + context = source->context; + + if (context) + LOCK_CONTEXT (context); + + source->poll_fds = g_slist_prepend (source->poll_fds, fd); + + if (context) + { + g_main_context_add_poll_unlocked (context, source->priority, fd); + UNLOCK_CONTEXT (context); + } +} + +/** + * g_source_set_callback_indirect: + * @source: the source + * @callback_data: pointer to callback data "object" + * @callback_funcs: functions for reference counting callback_data + * and getting the callback and data + * + * Set the callback function storing the data as a refcounted callback + * "object". This is used to implement g_source_set_callback_closure() + * and internally. Note that calling g_source_set_callback_indirect() assumes + * an initial reference count on @callback_data, and thus + * @callback_funcs->unref will eventually be called once more + * than @callback_funcs->ref. + **/ +void +g_source_set_callback_indirect (GSource *source, + gpointer callback_data, + GSourceCallbackFuncs *callback_funcs) +{ + GMainContext *context; + gpointer old_cb_data; + GSourceCallbackFuncs *old_cb_funcs; + + g_return_if_fail (source != NULL); + g_return_if_fail (callback_funcs != NULL || callback_data == NULL); + + context = source->context; + + if (context) + LOCK_CONTEXT (context); + + old_cb_data = source->callback_data; + old_cb_funcs = source->callback_funcs; + + source->callback_data = callback_data; + source->callback_funcs = callback_funcs; + + if (context) + UNLOCK_CONTEXT (context); + + if (old_cb_funcs) + old_cb_funcs->unref (old_cb_data); +} + +static void +g_source_callback_ref (gpointer cb_data) +{ + GSourceCallback *callback = cb_data; + + callback->ref_count++; +} + + +static void +g_source_callback_unref (gpointer cb_data) +{ + GSourceCallback *callback = cb_data; + + callback->ref_count--; + if (callback->ref_count == 0) + { + if (callback->notify) + callback->notify (callback->data); + } +} + +static void +g_source_callback_get (gpointer cb_data, + GSourceFunc *func, + gpointer *data) +{ + GSourceCallback *callback = cb_data; + + *func = callback->func; + *data = callback->data; +} + +static GSourceCallbackFuncs g_source_callback_funcs = { + g_source_callback_ref, + g_source_callback_unref, + g_source_callback_get, +}; + +/** + * g_source_set_callback: + * @source: the source + * @func: a callback function + * @data: the data to pass to callback function + * @notify: a function to call when @data is no longer in use, or %NULL. + * + * Set the callback function for a source. + **/ +void +g_source_set_callback (GSource *source, + GSourceFunc func, + gpointer data, + GDestroyNotify notify) +{ + GSourceCallback *new_callback; + + g_return_if_fail (source != NULL); + + new_callback = g_new (GSourceCallback, 1); + + new_callback->func = func; + new_callback->data = data; + new_callback->notify = notify; + + g_source_set_callback_indirect (source, new_callback, &g_source_callback_funcs); +} + +/** + * g_source_set_priority: + * @source: a #GSource + * @priority: the new priority. + * + * Set the priority of a source. While the main loop is being + * run, a source will + **/ +void +g_source_set_priority (GSource *source, + gint priority) +{ + GSList *tmp_list; + GMainContext *context; + + g_return_if_fail (source != NULL); + + context = source->context; + + if (context) + LOCK_CONTEXT (context); + + source->priority = priority; + + if (context) + { + source->next = NULL; + source->prev = NULL; + + tmp_list = source->poll_fds; + while (tmp_list) + { + g_main_context_remove_poll_unlocked (context, tmp_list->data); + g_main_context_add_poll_unlocked (context, priority, tmp_list->data); + + tmp_list = tmp_list->next; + } + + UNLOCK_CONTEXT (source->context); + } +} + +/** + * g_source_get_priority: + * @source: a #GSource + * + * Gets the priority of a surce + * + * Return value: the priority of the source + **/ +gint +g_source_get_priority (GSource *source) +{ + g_return_val_if_fail (source != NULL, 0); + + return source->priority; +} + +/** + * g_source_set_can_recurse: + * @source: a #GSource + * @can_recurse: whether recursion is allowed for this source + * + * Sets whether a source can be called recursively. If @can_recurse is + * %TRUE, then while the source is being dispatched then this source + * will be processed normally. Otherwise, all processing of this + * source is blocked until the dispatch function returns. + **/ +void +g_source_set_can_recurse (GSource *source, + gboolean can_recurse) +{ + GMainContext *context; + + g_return_if_fail (source != NULL); + + context = source->context; + + if (context) + LOCK_CONTEXT (context); + + if (can_recurse) + source->flags |= G_SOURCE_CAN_RECURSE; + else + source->flags &= ~G_SOURCE_CAN_RECURSE; + + if (context) + UNLOCK_CONTEXT (context); +} + +/** + * g_source_get_can_recurse: + * @source: a #GSource + * + * Checks whether a source is allowed to be called recursively. + * see g_source_set_can_recurse. + * + * Return value: whether recursion is allowed. + **/ +gboolean +g_source_get_can_recurse (GSource *source) +{ + g_return_val_if_fail (source != NULL, FALSE); + + return (source->flags & G_SOURCE_CAN_RECURSE) != 0; +} + +/** + * g_source_ref: + * @source: a #GSource + * + * Increases the reference count on a source by one. + * + * Return value: @source + **/ +GSource * +g_source_ref (GSource *source) +{ + GMainContext *context; + + g_return_val_if_fail (source != NULL, NULL); + + context = source->context; + + if (context) + LOCK_CONTEXT (context); + + source->ref_count++; + + if (context) + UNLOCK_CONTEXT (context); + + return source; +} + +/* g_source_unref() but possible to call within context lock + */ +static void +g_source_unref_internal (GSource *source, + GMainContext *context, + gboolean have_lock) +{ + gpointer cb_data = NULL; + GSourceCallbackFuncs *cb_funcs = NULL; + GSList *tmp_list; + + g_return_if_fail (source != NULL); + + if (!have_lock && context) + LOCK_CONTEXT (context); + + source->ref_count--; + if (source->ref_count == 0) + { + if (context && !SOURCE_DESTROYED (source)) + { + g_warning (G_STRLOC ": ref_count == 0, but source is still attached to a context!"); + source->ref_count++; + } + else + { + g_source_list_remove (source, context); + + tmp_list = source->poll_fds; + while (tmp_list) + { + g_main_context_remove_poll_unlocked (context, tmp_list->data); + tmp_list = tmp_list->next; + } + } + } + + if (!have_lock && context) + UNLOCK_CONTEXT (context); + + if (cb_data) + { + if (have_lock) + UNLOCK_CONTEXT (context); + + cb_funcs->unref (cb_data); + + if (have_lock) + LOCK_CONTEXT (context); + } +} + +/** + * g_source_unref: + * @source: a #GSource + * + * Decreases the reference count of a source by one. If the + * resulting reference count is zero the source and associated + * memory will be destroyed. + **/ +void +g_source_unref (GSource *source) +{ + g_return_if_fail (source != NULL); + + g_source_unref_internal (source, source->context, FALSE); +} + +/** + * g_main_context_find_source_by_id: + * @context: a #GMainContext (if %NULL, the default context will be used) + * @id: the source ID, as returned by g_source_get_id() + * + * Finds a #GSource given a pair of context and ID + * + * Return value: the #GSource if found, otherwise, %NULL + **/ +GSource * +g_main_context_find_source_by_id (GMainContext *context, + guint id) +{ + GSource *source; + + g_return_val_if_fail (id > 0, FALSE); + + if (context == NULL) + context = g_main_context_default (); + + LOCK_CONTEXT (context); + + source = context->source_list; + while (source) + { + if (!SOURCE_DESTROYED (source) && + source->id == id) + break; + source = source->next; + } + + UNLOCK_CONTEXT (context); + + return source; +} + +/** + * g_main_context_find_source_by_funcs_user_data: + * @context: a #GMainContext (if %NULL, the default context will be used). + * @funcs: the @source_funcs passed to g_source_new(). + * @user_data: the user data from the callback. + * + * Finds a source with the given source functions and user data. If + * multiple sources exist with the same source function and user data, + * the first one found will be returned. + * + * Return value: the source, if one was found, otherwise %NULL + **/ +GSource * +g_main_context_find_source_by_funcs_user_data (GMainContext *context, + GSourceFuncs *funcs, + gpointer user_data) +{ + GSource *source; + + g_return_val_if_fail (funcs != NULL, FALSE); + + if (context == NULL) + context = g_main_context_default (); + + LOCK_CONTEXT (context); + + source = context->source_list; + while (source) + { + if (!SOURCE_DESTROYED (source) && + source->source_funcs == funcs && + source->callback_data == user_data) + break; + source = source->next; + } + + UNLOCK_CONTEXT (context); + + return source; +} + +/** + * g_main_context_find_source_by_user_data: + * @context: a #GMainContext + * @user_data: the user_data for the callback. + * + * Finds a source with the given user data for the callback. If + * multiple sources exist with the same user data, the first + * one found will be returned. + * + * Return value: the source, if one was found, otherwise %NULL + **/ +GSource * +g_main_context_find_source_by_user_data (GMainContext *context, + gpointer user_data) +{ + GSource *source; + + if (context == NULL) + context = g_main_context_default (); + + LOCK_CONTEXT (context); + + source = context->source_list; + while (source) + { + if (!SOURCE_DESTROYED (source) && + source->callback_data == user_data) + break; + source = source->next; + } + + UNLOCK_CONTEXT (context); + + return source; +} + +/** + * 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(). + * + * See also g_source_destroy(). + * + * Return value: %TRUE if the source was found and removed. + **/ gboolean g_source_remove (guint tag) { - GHook *hook; - + GSource *source; + g_return_val_if_fail (tag > 0, FALSE); - G_LOCK (main_loop); + source = g_main_context_find_source_by_id (NULL, tag); + if (source) + g_source_destroy (source); - hook = g_hook_get (&source_list, tag); - if (hook) - g_hook_destroy_link (&source_list, hook); - - G_UNLOCK (main_loop); - - return hook != NULL; + return source != NULL; } +/** + * g_source_remove_by_user_data: + * @user_data: the user_data for the callback. + * + * Removes a source from the default main loop context given the user + * data for the callback. If multiple sources exist with the same user + * data, only one will be destroyed. + * + * Return value: %TRUE if a source was found and removed. + **/ gboolean g_source_remove_by_user_data (gpointer user_data) { - GHook *hook; + GSource *source; - G_LOCK (main_loop); - - hook = g_hook_find_data (&source_list, TRUE, user_data); - if (hook) - g_hook_destroy_link (&source_list, hook); - - G_UNLOCK (main_loop); - - return hook != NULL; -} - -static gboolean -g_source_find_source_data (GHook *hook, - gpointer data) -{ - GSource *source = (GSource *)hook; - - return (source->source_data == data); -} - -gboolean -g_source_remove_by_source_data (gpointer source_data) -{ - GHook *hook; - - G_LOCK (main_loop); - - hook = g_hook_find (&source_list, TRUE, - g_source_find_source_data, source_data); - if (hook) - g_hook_destroy_link (&source_list, hook); - - G_UNLOCK (main_loop); - - return hook != NULL; -} - -static gboolean -g_source_find_funcs_user_data (GHook *hook, - gpointer data) -{ - gpointer *d = data; - - return hook->func == d[0] && hook->data == d[1]; + source = g_main_context_find_source_by_user_data (NULL, user_data); + if (source) + { + g_source_destroy (source); + return TRUE; + } + else + return FALSE; } +/** + * g_source_remove_by_funcs_user_data: + * @funcs: The @source_funcs passed to g_source_new() + * @user_data: the user data for the callback + * + * Removes a source from the default main loop context given the + * source functions and user data. If multiple sources exist with the + * same source functions and user data, only one will be destroyed. + * + * Return value: %TRUE if a source was found and removed. + **/ gboolean g_source_remove_by_funcs_user_data (GSourceFuncs *funcs, gpointer user_data) { - gpointer d[2]; - GHook *hook; + GSource *source; g_return_val_if_fail (funcs != NULL, FALSE); - G_LOCK (main_loop); - - d[0] = funcs; - d[1] = user_data; - - hook = g_hook_find (&source_list, TRUE, - g_source_find_funcs_user_data, d); - if (hook) - g_hook_destroy_link (&source_list, hook); - - G_UNLOCK (main_loop); - - return hook != NULL; + source = g_main_context_find_source_by_funcs_user_data (NULL, funcs, user_data); + if (source) + { + g_source_destroy (source); + return TRUE; + } + else + return FALSE; } +/** + * g_get_current_time: + * @result: #GTimeVal structure in which to store current time. + * + * Equivalent to Unix's gettimeofday(), but portable + **/ void g_get_current_time (GTimeVal *result) { @@ -629,463 +1437,769 @@ g_get_current_time (GTimeVal *result) result->tv_sec = r.tv_sec; result->tv_usec = r.tv_usec; #else - FILETIME filetime; - gint64 t; + /* Avoid calling time() except for the first time. + * GetTickCount() should be pretty fast and low-level? + * I could also use ftime() but it seems unnecessarily overheady. + */ + static DWORD start_tick = 0; + static time_t start_time; + DWORD tick; g_return_if_fail (result != NULL); + + if (start_tick == 0) + { + start_tick = GetTickCount (); + time (&start_time); + } - GetSystemTimeAsFileTime (&filetime); - t = ((gint64) filetime.dwLowDateTime) + - ((gint64) filetime.dwHighDateTime) * G_GINT64_CONSTANT (0x100000000); + tick = GetTickCount (); - result->tv_sec = (t - G_GINT64_CONSTANT (0x19db1ded53e8000)) / 10000000; - result->tv_usec = (t % 10000000) / 10; + result->tv_sec = (tick - start_tick) / 1000 + start_time; + result->tv_usec = ((tick - start_tick) % 1000) * 1000; #endif } /* Running the main loop */ -/* HOLDS: main_loop_lock */ +/* HOLDS: context's lock */ static void -g_main_dispatch (GTimeVal *dispatch_time) +g_main_dispatch (GMainContext *context) { - while (pending_dispatches != NULL) + gint i; + + for (i = 0; i < context->pending_dispatches->len; i++) { - gboolean need_destroy; - GSource *source = pending_dispatches->data; - GSList *tmp_list; + GSource *source = context->pending_dispatches->pdata[i]; - tmp_list = pending_dispatches; - pending_dispatches = g_slist_remove_link (pending_dispatches, pending_dispatches); - g_slist_free_1 (tmp_list); + context->pending_dispatches->pdata[i] = NULL; + g_assert (source); - if (G_HOOK_IS_VALID (source)) + source->flags &= ~G_SOURCE_READY; + + if (!SOURCE_DESTROYED (source)) { gboolean was_in_call; - gpointer hook_data = source->hook.data; - gpointer source_data = source->source_data; - gboolean (*dispatch) (gpointer, - GTimeVal *, + gpointer user_data = NULL; + GSourceFunc callback = NULL; + GSourceCallbackFuncs *cb_funcs; + gpointer cb_data; + gboolean need_destroy; + + gboolean (*dispatch) (GSource *, + GSourceFunc, gpointer); - dispatch = ((GSourceFuncs *) source->hook.func)->dispatch; - - was_in_call = G_HOOK_IN_CALL (source); - source->hook.flags |= G_HOOK_FLAG_IN_CALL; + dispatch = source->source_funcs->dispatch; + cb_funcs = source->callback_funcs; + cb_data = source->callback_data; - G_UNLOCK (main_loop); - need_destroy = ! dispatch (source_data, - dispatch_time, - hook_data); - G_LOCK (main_loop); - - if (!was_in_call) - source->hook.flags &= ~G_HOOK_FLAG_IN_CALL; + if (cb_funcs) + cb_funcs->ref (cb_data); - if (need_destroy && G_HOOK_IS_VALID (source)) - g_hook_destroy_link (&source_list, (GHook *) source); + was_in_call = source->flags & G_HOOK_FLAG_IN_CALL; + source->flags |= G_HOOK_FLAG_IN_CALL; + + UNLOCK_CONTEXT (context); + + if (cb_funcs) + cb_funcs->get (cb_data, &callback, &user_data); + + need_destroy = ! dispatch (source, + callback, + user_data); + LOCK_CONTEXT (context); + + if (cb_funcs) + cb_funcs->unref (cb_data); + + if (!was_in_call) + source->flags &= ~G_HOOK_FLAG_IN_CALL; + + /* Note: this depends on the fact that we can't switch + * sources from one main context to another + */ + if (need_destroy && !SOURCE_DESTROYED (source)) + { + g_assert (source->context == context); + g_source_destroy_internal (source, context, TRUE); + } } - - g_hook_unref (&source_list, (GHook*) source); + + SOURCE_UNREF (source, context); } + + g_ptr_array_set_size (context->pending_dispatches, 0); } -/* g_main_iterate () runs a single iteration of the mainloop, or, - * if !dispatch checks to see if any sources need dispatching. - * basic algorithm for dispatch=TRUE: - * - * 1) while the list of currently pending sources is non-empty, - * we call (*dispatch) on those that are !IN_CALL or can_recurse, - * removing sources from the list after each returns. - * the return value of (*dispatch) determines whether the source - * itself is kept alive. - * - * 2) call (*prepare) for sources that are not yet SOURCE_READY and - * are !IN_CALL or can_recurse. a return value of TRUE determines - * that the source would like to be dispatched immediatedly, it - * is then flagged as SOURCE_READY. - * - * 3) poll with the pollfds from all sources at the priority of the - * first source flagged as SOURCE_READY. if there are any sources - * flagged as SOURCE_READY, we use a timeout of 0 or the minimum - * of all timouts otherwise. - * - * 4) for each source !IN_CALL or can_recurse, if SOURCE_READY or - * (*check) returns true, add the source to the pending list. - * once one source returns true, stop after checking all sources - * at that priority. - * - * 5) while the list of currently pending sources is non-empty, - * call (*dispatch) on each source, removing the source - * after the call. - * - */ -static gboolean -g_main_iterate (gboolean block, - gboolean dispatch) +/* Holds context's lock */ +static inline GSource * +next_valid_source (GMainContext *context, + GSource *source) { - GHook *hook; - GTimeVal current_time = { 0, 0 }; - gint n_ready = 0; - gint current_priority = 0; - gint timeout; - gboolean retval = FALSE; + GSource *new_source = source ? source->next : context->source_list; - g_return_val_if_fail (!block || dispatch, FALSE); - - g_get_current_time (¤t_time); - - G_LOCK (main_loop); - -#ifdef G_THREADS_ENABLED - if (poll_waiting) + while (new_source) { - g_warning("g_main_iterate(): main loop already active in another thread"); - G_UNLOCK (main_loop); + if (!SOURCE_DESTROYED (new_source)) + { + new_source->ref_count++; + break; + } + + new_source = new_source->next; + } + + if (source) + SOURCE_UNREF (source, context); + + return new_source; +} + + +/** + * g_main_context_prepare: + * @context: a #GMainContext + * @priority: location to store priority of highest priority + * source already ready. + * + * Prepares to poll sources within a main loop. The resulting information + * for polling is determined by calling g_main_context_query (). + * + * Return value: %TRUE if some source is ready to be dispatched + * prior to polling. + **/ +gboolean +g_main_context_prepare (GMainContext *context, + gint *priority) +{ + gint n_ready = 0; + gint current_priority = G_MAXINT; + GSource *source; + + if (context == NULL) + context = g_main_context_default (); + + LOCK_CONTEXT (context); + + context->time_is_current = FALSE; + + if (context->in_check_or_prepare) + { + g_warning ("g_main_context_prepare() called recursively from within a source's check() or " + "prepare() member."); + return FALSE; + } + +#ifdef G_THREADS_ENABLED + if (context->poll_waiting) + { + g_warning("g_main_context_prepare(): main loop already active in another thread"); + UNLOCK_CONTEXT (context); return FALSE; } -#endif /* G_THREADS_ENABLED */ + context->poll_waiting = TRUE; +#endif G_THREADS_ENABLED + +#if 0 /* If recursing, finish up current dispatch, before starting over */ - if (pending_dispatches) + if (context->pending_dispatches) { if (dispatch) - g_main_dispatch (¤t_time); + g_main_dispatch (context, ¤t_time); - G_UNLOCK (main_loop); - + UNLOCK_CONTEXT (context); return TRUE; } +#endif + /* If recursing, clear list of pending dispatches */ + g_ptr_array_set_size (context->pending_dispatches, 0); + /* Prepare all sources */ - timeout = block ? -1 : 0; + context->timeout = -1; - hook = g_hook_first_valid (&source_list, TRUE); - while (hook) + source = next_valid_source (context, NULL); + while (source) { - GSource *source = (GSource*) hook; gint source_timeout = -1; if ((n_ready > 0) && (source->priority > current_priority)) { - g_hook_unref (&source_list, hook); + SOURCE_UNREF (source, context); break; } - if (G_HOOK_IN_CALL (hook) && !(hook->flags & G_SOURCE_CAN_RECURSE)) + if ((source->flags & G_HOOK_FLAG_IN_CALL) && !(source->flags & G_SOURCE_CAN_RECURSE)) + goto next; + + if (!(source->flags & G_SOURCE_READY)) { - hook = g_hook_next_valid (&source_list, hook, TRUE); - continue; + gboolean result; + gboolean (*prepare) (GSource *source, + gint *timeout); + + prepare = source->source_funcs->prepare; + context->in_check_or_prepare++; + UNLOCK_CONTEXT (context); + + result = (*prepare) (source, &source_timeout); + + LOCK_CONTEXT (context); + context->in_check_or_prepare--; + + if (result) + source->flags |= G_SOURCE_READY; } - if (!(hook->flags & G_SOURCE_READY)) + if (source->flags & G_SOURCE_READY) { - gboolean (*prepare) (gpointer source_data, - GTimeVal *current_time, - gint *timeout, - gpointer user_data); - - prepare = ((GSourceFuncs *) hook->func)->prepare; - in_check_or_prepare++; - G_UNLOCK (main_loop); - - if ((*prepare) (source->source_data, ¤t_time, &source_timeout, source->hook.data)) - hook->flags |= G_SOURCE_READY; - - G_LOCK (main_loop); - in_check_or_prepare--; - } - - if (hook->flags & G_SOURCE_READY) - { - if (!dispatch) - { - g_hook_unref (&source_list, hook); - G_UNLOCK (main_loop); - - return TRUE; - } - else - { - n_ready++; - current_priority = source->priority; - timeout = 0; - } + n_ready++; + current_priority = source->priority; + context->timeout = 0; } if (source_timeout >= 0) { - if (timeout < 0) - timeout = source_timeout; + if (context->timeout < 0) + context->timeout = source_timeout; else - timeout = MIN (timeout, source_timeout); + context->timeout = MIN (context->timeout, source_timeout); } - hook = g_hook_next_valid (&source_list, hook, TRUE); + next: + source = next_valid_source (context, source); } - /* poll(), if necessary */ - - g_main_poll (timeout, n_ready > 0, current_priority); - - if (timeout != 0) - g_get_current_time (¤t_time); + UNLOCK_CONTEXT (context); - /* Check to see what sources need to be dispatched */ - - n_ready = 0; + if (priority) + *priority = current_priority; - hook = g_hook_first_valid (&source_list, TRUE); - while (hook) - { - GSource *source = (GSource *)hook; - - if ((n_ready > 0) && (source->priority > current_priority)) - { - g_hook_unref (&source_list, hook); - break; - } - if (G_HOOK_IN_CALL (hook) && !(hook->flags & G_SOURCE_CAN_RECURSE)) - { - hook = g_hook_next_valid (&source_list, hook, TRUE); - continue; - } - - if (!(hook->flags & G_SOURCE_READY)) - { - gboolean (*check) (gpointer source_data, - GTimeVal *current_time, - gpointer user_data); - - check = ((GSourceFuncs *) hook->func)->check; - in_check_or_prepare++; - G_UNLOCK (main_loop); - - if ((*check) (source->source_data, ¤t_time, source->hook.data)) - hook->flags |= G_SOURCE_READY; - - G_LOCK (main_loop); - in_check_or_prepare--; - } - - if (hook->flags & G_SOURCE_READY) - { - if (dispatch) - { - hook->flags &= ~G_SOURCE_READY; - g_hook_ref (&source_list, hook); - pending_dispatches = g_slist_prepend (pending_dispatches, source); - current_priority = source->priority; - n_ready++; - } - else - { - g_hook_unref (&source_list, hook); - G_UNLOCK (main_loop); - - return TRUE; - } - } - - hook = g_hook_next_valid (&source_list, hook, TRUE); - } - - /* Now invoke the callbacks */ - - if (pending_dispatches) - { - pending_dispatches = g_slist_reverse (pending_dispatches); - g_main_dispatch (¤t_time); - retval = TRUE; - } - - G_UNLOCK (main_loop); - - return retval; + return (n_ready > 0); } -/* See if any events are pending - */ -gboolean -g_main_pending (void) +/** + * g_main_context_query: + * @context: a #GMainContext + * @max_priority: maximum priority source to check + * @timeout: location to store timeout to be used in polling + * @fds: location to store #GPollFD records that need to be polled. + * @n_fds: length of @fds. + * + * Determines information necessary to poll this main loop. + * + * Return value: + **/ +gint +g_main_context_query (GMainContext *context, + gint max_priority, + gint *timeout, + GPollFD *fds, + gint n_fds) { - return in_check_or_prepare ? FALSE : g_main_iterate (FALSE, FALSE); -} - -/* Run a single iteration of the mainloop. If block is FALSE, - * will never block - */ -gboolean -g_main_iteration (gboolean block) -{ - if (in_check_or_prepare) - { - g_warning ("g_main_iteration(): called recursively from within a source's check() or " - "prepare() member or from a second thread, iteration not possible"); - return FALSE; - } - else - return g_main_iterate (block, TRUE); -} - -GMainLoop* -g_main_new (gboolean is_running) -{ - GMainLoop *loop; - - loop = g_new0 (GMainLoop, 1); - loop->is_running = is_running != FALSE; - - return loop; -} - -void -g_main_run (GMainLoop *loop) -{ - g_return_if_fail (loop != NULL); - - if (in_check_or_prepare) - { - g_warning ("g_main_run(): called recursively from within a source's check() or " - "prepare() member or from a second thread, iteration not possible"); - return; - } - - loop->is_running = TRUE; - while (loop->is_running) - g_main_iterate (TRUE, TRUE); -} - -void -g_main_quit (GMainLoop *loop) -{ - g_return_if_fail (loop != NULL); - - loop->is_running = FALSE; -} - -void -g_main_destroy (GMainLoop *loop) -{ - g_return_if_fail (loop != NULL); - - g_free (loop); -} - -gboolean -g_main_is_running (GMainLoop *loop) -{ - g_return_val_if_fail (loop != NULL, FALSE); - - return loop->is_running; -} - -/* HOLDS: main_loop_lock */ -static void -g_main_poll (gint timeout, - gboolean use_priority, - gint priority) -{ -#ifdef G_MAIN_POLL_DEBUG - GTimer *poll_timer; -#endif - GPollFD *fd_array; + gint n_poll; GPollRec *pollrec; - gint i; - gint npoll; + + LOCK_CONTEXT (context); -#ifdef G_THREADS_ENABLED -#ifndef G_OS_WIN32 - if (wake_up_pipe[0] < 0) - { - if (pipe (wake_up_pipe) < 0) - g_error ("Cannot create pipe main loop wake-up: %s\n", - g_strerror (errno)); - - wake_up_rec.fd = wake_up_pipe[0]; - wake_up_rec.events = G_IO_IN; - g_main_add_poll_unlocked (0, &wake_up_rec); - } -#else - if (wake_up_semaphore == NULL) - { - if ((wake_up_semaphore = CreateSemaphore (NULL, 0, 100, NULL)) == NULL) - g_error ("Cannot create wake-up semaphore: %s", g_win32_error_message (GetLastError ())); - wake_up_rec.fd = (gint) wake_up_semaphore; - wake_up_rec.events = G_IO_IN; -#ifdef G_MAIN_POLL_DEBUG - g_print ("wake-up semaphore: %#x\n", (guint) wake_up_semaphore); -#endif - g_main_add_poll_unlocked (0, &wake_up_rec); - } -#endif -#endif - fd_array = g_new (GPollFD, n_poll_records); - - pollrec = poll_records; - i = 0; - while (pollrec && (!use_priority || priority >= pollrec->priority)) + pollrec = context->poll_records; + n_poll = 0; + while (pollrec && max_priority >= pollrec->priority) { if (pollrec->fd->events) { - fd_array[i].fd = pollrec->fd->fd; - /* In direct contradiction to the Unix98 spec, IRIX runs into - * difficulty if you pass in POLLERR, POLLHUP or POLLNVAL - * flags in the events field of the pollfd while it should - * just ignoring them. So we mask them out here. - */ - fd_array[i].events = pollrec->fd->events & ~(G_IO_ERR|G_IO_HUP|G_IO_NVAL); - fd_array[i].revents = 0; - i++; + if (n_poll < n_fds) + { + fds[n_poll].fd = pollrec->fd->fd; + /* In direct contradiction to the Unix98 spec, IRIX runs into + * difficulty if you pass in POLLERR, POLLHUP or POLLNVAL + * flags in the events field of the pollfd while it should + * just ignoring them. So we mask them out here. + */ + fds[n_poll].events = pollrec->fd->events & ~(G_IO_ERR|G_IO_HUP|G_IO_NVAL); + fds[n_poll].revents = 0; + } + n_poll++; } pollrec = pollrec->next; } -#ifdef G_THREADS_ENABLED - poll_waiting = TRUE; - poll_changed = FALSE; -#endif + + context->poll_changed = FALSE; + + if (timeout) + { + *timeout = context->timeout; + if (timeout != 0) + context->time_is_current = FALSE; + } - npoll = i; - if (npoll || timeout != 0) + UNLOCK_CONTEXT (context); + + return n_poll; +} + +/** + * g_main_context_check: + * @context: a #GMainContext + * @max_priority: the maximum numerical priority of sources to check + * @fds: array of #GPollFD's that was passed to the last call to + * g_main_context_query() + * @n_fds: return value of g_main_context_query() + * + * Pass the results of polling back to the main loop. + * + * Return value: %TRUE if some sources are ready to be dispatched. + **/ +gboolean +g_main_context_check (GMainContext *context, + gint max_priority, + GPollFD *fds, + gint n_fds) +{ + GSource *source; + GPollRec *pollrec; + gint n_ready = 0; + gint i; + + LOCK_CONTEXT (context); + + if (context->in_check_or_prepare) + { + g_warning ("g_main_context_check() called recursively from within a source's check() or " + "prepare() member."); + return FALSE; + } + +#ifdef G_THREADS_ENABLED + if (!context->poll_waiting) + { +#ifndef G_OS_WIN32 + gchar c; + read (context->wake_up_pipe[0], &c, 1); +#endif + } + else + context->poll_waiting = FALSE; +#endif /* G_THREADS_ENABLED */ + + /* If the set of poll file descriptors changed, bail out + * and let the main loop rerun + */ + if (context->poll_changed) + return 0; + + pollrec = context->poll_records; + i = 0; + while (i < n_fds) + { + if (pollrec->fd->events) + { + pollrec->fd->revents = fds[i].revents; + i++; + } + pollrec = pollrec->next; + } + + source = next_valid_source (context, NULL); + while (source) + { + if ((n_ready > 0) && (source->priority > max_priority)) + { + SOURCE_UNREF (source, context); + break; + } + if ((source->flags & G_HOOK_FLAG_IN_CALL) && !(source->flags & G_SOURCE_CAN_RECURSE)) + goto next; + + if (!(source->flags & G_SOURCE_READY)) + { + gboolean result; + gboolean (*check) (GSource *source); + + check = source->source_funcs->check; + + context->in_check_or_prepare++; + UNLOCK_CONTEXT (context); + + result = (*check) (source); + + LOCK_CONTEXT (context); + context->in_check_or_prepare--; + + if (result) + source->flags |= G_SOURCE_READY; + } + + if (source->flags & G_SOURCE_READY) + { + source->ref_count++; + g_ptr_array_add (context->pending_dispatches, source); + + n_ready++; + } + + next: + source = next_valid_source (context, source); + } + + UNLOCK_CONTEXT (context); + + return n_ready > 0; +} + +/** + * g_main_context_dispatch: + * @context: a #GMainContext + * + * Dispatch all pending sources() + **/ +void +g_main_context_dispatch (GMainContext *context) +{ + LOCK_CONTEXT (context); + + if (context->pending_dispatches->len > 0) + { + g_main_dispatch (context); + } + + UNLOCK_CONTEXT (context); +} + +static gboolean +g_main_context_iterate (GMainContext *context, + gboolean block, + gboolean dispatch) +{ + gint max_priority; + gint timeout; + gboolean some_ready; + gint nfds, new_nfds; + GPollFD *fds; + + some_ready = g_main_context_prepare (context, &max_priority); + + do + { + LOCK_CONTEXT (context); + + if (context->cached_poll_array) + { + nfds = context->cached_poll_array_size; + fds = context->cached_poll_array; + context->cached_poll_array = NULL; + } + else + { + nfds = context->cached_poll_array_size = context->n_poll_records; + fds = g_new (GPollFD, nfds); + } + + UNLOCK_CONTEXT (context); + + new_nfds = g_main_context_query (context, max_priority, + &timeout, fds, nfds); + } + while (new_nfds > nfds); + + if (!block) + timeout = 0; + + g_main_context_poll (context, timeout, max_priority, + fds, new_nfds); + + g_main_context_check (context, + max_priority, + fds, new_nfds); + + LOCK_CONTEXT (context); + + g_assert (!context->cached_poll_array); + + context->cached_poll_array = fds; + context->cached_poll_array_size = nfds; + + UNLOCK_CONTEXT (context); + + if (dispatch) + g_main_context_dispatch (context); + + return some_ready; +} + +/** + * g_main_context_pending: + * @context: a #GMainContext (if %NULL, the default context will be used) + * + * Check if any sources have pending events for the given context. + * + * Return value: %TRUE if events are pending. + **/ +gboolean +g_main_context_pending (GMainContext *context) +{ + if (!context) + context = g_main_context_default(); + + return g_main_context_iterate (context, FALSE, FALSE); +} + +/** + * g_main_context_iteration: + * @context: a #GMainContext (if %NULL, the default context will be used) + * @may_block: whether the call may block. + * + * Run a single iteration for the given main loop. This involves + * checking to see if any event sources are ready to be processed, + * then if no events sources are ready and @may_block is %TRUE, waiting + * for a source to become ready, then dispatching the highest priority + * events sources that are ready. Note that even when @may_block is %TRUE, + * it is still possible for g_main_context_iteration() to return + * %FALSE, since the the wait may be interrupted for other + * reasons than an event source becoming ready. + * + * Return value: %TRUE if events were dispatched. + **/ +gboolean +g_main_context_iteration (GMainContext *context, gboolean may_block) +{ + if (!context) + context = g_main_context_default(); + + return g_main_context_iterate (context, may_block, TRUE); +} + +/** + * g_main_loop_new: + * @context: a #GMainContext (if %NULL, the default context will be used). + * @is_running: set to TRUE to indicate that the loop is running. This + * is not very important since calling g_main_run() will set this to + * TRUE anyway. + * + * Create a new #GMainLoop structure + * + * Return value: + **/ +GMainLoop * +g_main_loop_new (GMainContext *context, + gboolean is_running) +{ + GMainLoop *loop; + + if (!context) + context = g_main_context_default(); + + loop = g_new0 (GMainLoop, 1); + loop->context = context; + loop->is_running = is_running != FALSE; + +#ifdef G_THREADS_ENABLED + if (g_thread_supported ()) + loop->mutex = g_mutex_new (); + else + loop->mutex = NULL; + loop->sem_cond = NULL; +#endif G_THREADS_ENABLED + + return loop; +} + +/** + * g_main_loop_run: + * @loop: a #GMainLoop + * + * Run a main loop until g_main_quit() is called on the loop. + * If this is called for the thread of the loop's #GMainContext, + * it will process events from the loop, otherwise it will + * simply wait. + **/ +void +g_main_loop_run (GMainLoop *loop) +{ + g_return_if_fail (loop != NULL); + +#ifdef G_THREADS_ENABLED + if (loop->context->thread != g_thread_self ()) + { + LOCK_LOOP (loop); + + if (!g_thread_supported ()) + { + g_warning ("g_main_loop_run() was called from second thread but" + "g_thread_init() was never called."); + } + else + { + if (!loop->sem_cond) + loop->sem_cond = g_cond_new (); + + if (!loop->is_running) + loop->is_running = TRUE; + + while (loop->is_running) + g_cond_wait (loop->sem_cond, loop->mutex); + } + + UNLOCK_LOOP (loop); + } + else +#endif /* G_THREADS_ENABLED */ + { + LOCK_CONTEXT (loop->context); + if (loop->context->in_check_or_prepare) + { + g_warning ("g_main_run(): called recursively from within a source's check() or " + "prepare() member, iteration not possible."); + return; + } + UNLOCK_CONTEXT (loop->context); + + LOCK_LOOP (loop); + + loop->is_running = TRUE; + while (loop->is_running) + { + UNLOCK_LOOP (loop); + g_main_context_iterate (loop->context, TRUE, TRUE); + LOCK_LOOP (loop); + } + UNLOCK_LOOP (loop); + } +} + +/** + * g_main_loop_quit: + * @loop: a #GMainLoop + * + * Stops a #GMainLoop from running. Any calls to g_main_loop_run() + * for the loop will return. + **/ +void +g_main_loop_quit (GMainLoop *loop) +{ + g_return_if_fail (loop != NULL); + + LOCK_LOOP (loop); + loop->is_running = FALSE; + + if (loop->sem_cond) + g_cond_broadcast (loop->sem_cond); + + UNLOCK_LOOP (loop); + + LOCK_CONTEXT (loop->context); + + g_main_context_wakeup (loop->context); + UNLOCK_CONTEXT (loop->context); +} + +/** + * g_main_loop_destroy: + * @loop: a #GMainLoop + * + * Destroy a #GMainLoop object and free all associated memory. + * The loop must not currently be running via g_main_run(). + **/ +void +g_main_loop_destroy (GMainLoop *loop) +{ + g_return_if_fail (loop != NULL); + g_return_if_fail (!loop->is_running); + +#ifdef G_THREADS_ENABLED + g_mutex_free (loop->mutex); + if (loop->sem_cond) + g_cond_free (loop->sem_cond); +#endif /* G_THREADS_ENABLED */ + + g_free (loop); +} + +/** + * g_main_loop_is_running: + * @loop: a #GMainLoop. + * + * Check to see if the main loop is currently being run via g_main_run() + * + * Return value: %TRUE if the mainloop is currently being run. + **/ +gboolean +g_main_loop_is_running (GMainLoop *loop) +{ + gboolean result; + + g_return_val_if_fail (loop != NULL, FALSE); + + LOCK_LOOP (loop); + result = loop->is_running; + UNLOCK_LOOP (loop); + + return result; +} + +/* HOLDS: context's lock */ +static void +g_main_context_poll (GMainContext *context, + gint timeout, + gint priority, + GPollFD *fds, + gint n_fds) +{ +#ifdef G_MAIN_POLL_DEBUG + GTimer *poll_timer; + GPollRec *pollrec; + gint i; +#endif + + GPollFunc poll_func; + + if (n_fds || timeout != 0) { #ifdef G_MAIN_POLL_DEBUG - g_print ("g_main_poll(%d) timeout: %d\n", npoll, timeout); + g_print ("g_main_poll(%d) timeout: %d\n", n_fds, timeout); poll_timer = g_timer_new (); #endif + + LOCK_CONTEXT (context); + + poll_func = context->poll_func; - G_UNLOCK (main_loop); - if ((*poll_func) (fd_array, npoll, timeout) < 0) + UNLOCK_CONTEXT (context); + if ((*poll_func) (fds, n_fds, timeout) < 0 && errno != EINTR) g_warning ("poll(2) failed due to: %s.", g_strerror (errno)); - G_LOCK (main_loop); #ifdef G_MAIN_POLL_DEBUG + LOCK_CONTEXT (context); + g_print ("g_main_poll(%d) timeout: %d - elapsed %12.10f seconds", - npoll, + n_fds, timeout, g_timer_elapsed (poll_timer, NULL)); g_timer_destroy (poll_timer); - pollrec = poll_records; + pollrec = context->poll_records; i = 0; - while (i < npoll) + while (i < n_fds) { if (pollrec->fd->events) { - if (fd_array[i].revents) + if (fds[i].revents) { - g_print (" [%d:", fd_array[i].fd); - if (fd_array[i].revents & G_IO_IN) + g_print (" [%d:", fds[i].fd); + if (fds[i].revents & G_IO_IN) g_print ("i"); - if (fd_array[i].revents & G_IO_OUT) + if (fds[i].revents & G_IO_OUT) g_print ("o"); - if (fd_array[i].revents & G_IO_PRI) + if (fds[i].revents & G_IO_PRI) g_print ("p"); - if (fd_array[i].revents & G_IO_ERR) + if (fds[i].revents & G_IO_ERR) g_print ("e"); - if (fd_array[i].revents & G_IO_HUP) + if (fds[i].revents & G_IO_HUP) g_print ("h"); - if (fd_array[i].revents & G_IO_NVAL) + if (fds[i].revents & G_IO_NVAL) g_print ("n"); g_print ("]"); } @@ -1094,77 +2208,64 @@ g_main_poll (gint timeout, pollrec = pollrec->next; } g_print ("\n"); + + UNLOCK_CONTEXT (context); #endif - } /* if (npoll || timeout != 0) */ - -#ifdef G_THREADS_ENABLED - if (!poll_waiting) - { -#ifndef G_OS_WIN32 - gchar c; - read (wake_up_pipe[0], &c, 1); -#endif - } - else - poll_waiting = FALSE; - - /* If the set of poll file descriptors changed, bail out - * and let the main loop rerun - */ - if (poll_changed) - { - g_free (fd_array); - return; - } -#endif - - pollrec = poll_records; - i = 0; - while (i < npoll) - { - if (pollrec->fd->events) - { - pollrec->fd->revents = fd_array[i].revents; - i++; - } - pollrec = pollrec->next; - } - - g_free (fd_array); + } /* if (n_fds || timeout != 0) */ } -void -g_main_add_poll (GPollFD *fd, - gint priority) +/** + * g_main_context_add_poll: + * @context: a #GMainContext (or %NULL for the default context) + * @fd: a #GPollFD structure holding information about a file + * descriptor to watch. + * @priority: the priority for this file descriptor which should be + * the same as the priority used for g_source_attach() to ensure that the + * file descriptor is polled whenever the results may be needed. + * + * Add a file descriptor to the set of file descriptors polled * for + * this context. This will very seldom be used directly. Instead + * a typical event source will use g_source_add_poll() instead. + **/ +void +g_main_context_add_poll (GMainContext *context, + GPollFD *fd, + gint priority) { - G_LOCK (main_loop); - g_main_add_poll_unlocked (priority, fd); - G_UNLOCK (main_loop); + if (!context) + context = g_main_context_default (); + + LOCK_CONTEXT (context); + g_main_context_add_poll_unlocked (context, priority, fd); + UNLOCK_CONTEXT (context); } /* HOLDS: main_loop_lock */ static void -g_main_add_poll_unlocked (gint priority, - GPollFD *fd) +g_main_context_add_poll_unlocked (GMainContext *context, + gint priority, + GPollFD *fd) { GPollRec *lastrec, *pollrec, *newrec; - if (!poll_chunk) - poll_chunk = g_mem_chunk_create (GPollRec, 32, G_ALLOC_ONLY); + if (!context->poll_chunk) + context->poll_chunk = g_mem_chunk_create (GPollRec, 32, G_ALLOC_ONLY); - if (poll_free_list) + if (context->poll_free_list) { - newrec = poll_free_list; - poll_free_list = newrec->next; + newrec = context->poll_free_list; + context->poll_free_list = newrec->next; } else - newrec = g_chunk_new (GPollRec, poll_chunk); + newrec = g_chunk_new (GPollRec, context->poll_chunk); + /* This file descriptor may be checked before we ever poll */ + fd->revents = 0; newrec->fd = fd; newrec->priority = priority; lastrec = NULL; - pollrec = poll_records; + pollrec = context->poll_records; while (pollrec && priority >= pollrec->priority) { lastrec = pollrec; @@ -1174,29 +2275,56 @@ g_main_add_poll_unlocked (gint priority, if (lastrec) lastrec->next = newrec; else - poll_records = newrec; + context->poll_records = newrec; newrec->next = pollrec; - n_poll_records++; + context->n_poll_records++; + if (context->cached_poll_array && + context->cached_poll_array_size < context->n_poll_records) + { + g_free (context->cached_poll_array); + context->cached_poll_array = NULL; + } #ifdef G_THREADS_ENABLED - poll_changed = TRUE; + context->poll_changed = TRUE; /* Now wake up the main loop if it is waiting in the poll() */ - g_main_wakeup (); + g_main_context_wakeup (context); #endif } -void -g_main_remove_poll (GPollFD *fd) +/** + * g_main_context_remove_poll: + * @context:a #GMainContext + * @fd: a #GPollFD descriptor previously added with g_main_context_add_poll() + * + * Remove file descriptor from the set of file descriptors to be + * polled for a particular context. + **/ +void +g_main_context_remove_poll (GMainContext *context, + GPollFD *fd) +{ + if (!context) + context = g_main_context_default (); + + LOCK_CONTEXT (context); + + g_main_context_remove_poll_unlocked (context, fd); + + UNLOCK_CONTEXT (context); +} + +static void +g_main_context_remove_poll_unlocked (GMainContext *context, + GPollFD *fd) { GPollRec *pollrec, *lastrec; - G_LOCK (main_loop); - lastrec = NULL; - pollrec = poll_records; + pollrec = context->poll_records; while (pollrec) { @@ -1205,16 +2333,16 @@ g_main_remove_poll (GPollFD *fd) if (lastrec != NULL) lastrec->next = pollrec->next; else - poll_records = pollrec->next; + context->poll_records = pollrec->next; #ifdef ENABLE_GC_FRIENDLY pollrec->fd = NULL; #endif /* ENABLE_GC_FRIENDLY */ - pollrec->next = poll_free_list; - poll_free_list = pollrec; + pollrec->next = context->poll_free_list; + context->poll_free_list = pollrec; - n_poll_records--; + context->n_poll_records--; break; } lastrec = pollrec; @@ -1222,52 +2350,120 @@ g_main_remove_poll (GPollFD *fd) } #ifdef G_THREADS_ENABLED - poll_changed = TRUE; + context->poll_changed = TRUE; /* Now wake up the main loop if it is waiting in the poll() */ - g_main_wakeup (); + g_main_context_wakeup (context); #endif - - G_UNLOCK (main_loop); } -void -g_main_set_poll_func (GPollFunc func) +/** + * g_source_get_current_time: + * @source: a #GSource + * @timeval: #GTimeVal structure in which to store current time. + * + * Gets the "current time" to be used when checking + * this source. The advantage of calling this function over + * calling g_get_current_time() directly is that when + * checking multiple sources, GLib can cache a single value + * instead of having to repeatedly get the system time. + **/ +void +g_source_get_current_time (GSource *source, + GTimeVal *timeval) { + GMainContext *context; + + g_return_if_fail (source->context != NULL); + + context = source->context; + + LOCK_CONTEXT (context); + + if (!context->time_is_current) + { + g_get_current_time (&context->current_time); + context->time_is_current = TRUE; + } + + *timeval = context->current_time; + + UNLOCK_CONTEXT (context); +} + +/** + * g_main_context_set_poll_func: + * @context: a #GMainContext + * @func: the function to call to poll all file descriptors + * + * Sets the function to use to handle polling of file descriptors. It + * will be used instead of the poll() system call (or GLib's + * replacement function, which is used where poll() isn't available). + * + * This function could possibly be used to integrate the GLib event + * loop with an external event loop. + **/ +void +g_main_context_set_poll_func (GMainContext *context, + GPollFunc func) +{ + if (!context) + context = g_main_context_default (); + + LOCK_CONTEXT (context); + if (func) - poll_func = func; + context->poll_func = func; else + { #ifdef HAVE_POLL - poll_func = (GPollFunc) poll; + context->poll_func = (GPollFunc) poll; #else - poll_func = (GPollFunc) g_poll; + context->poll_func = (GPollFunc) g_poll; #endif + } + + UNLOCK_CONTEXT (context); } #ifdef G_OS_WIN32 -/* Useful on other platforms, too? */ - +/** + * g_main_context_get_poll_func: + * @context: a #GMainContext + * + * Gets the poll function set by g_main_context_set_poll_func() + * + * Return value: the poll function + **/ GPollFunc -g_main_win32_get_poll_func (void) +g_main_context_get_poll_func (GMainContext *context) { - return poll_func; + GPollFunc *result; + + if (!context) + context = g_main_context_default (); + + LOCK_CONTEXT (context); + result = context->poll_func; + UNLOCK_CONTEXT (context); } #endif +/* HOLDS: context's lock */ /* Wake the main loop up from a poll() */ static void -g_main_wakeup (void) +g_main_context_wakeup (GMainContext *context) { #ifdef G_THREADS_ENABLED - if (poll_waiting) + if (g_thread_supported() && context->poll_waiting) { - poll_waiting = FALSE; + context->poll_waiting = FALSE; #ifndef G_OS_WIN32 - write (wake_up_pipe[1], "A", 1); + write (context->wake_up_pipe[1], "A", 1); #else - ReleaseSemaphore (wake_up_semaphore, 1, NULL); + ReleaseSemaphore (context->context->wake_up_semaphore, 1, NULL); #endif } #endif @@ -1276,43 +2472,45 @@ g_main_wakeup (void) /* Timeouts */ static void -g_timeout_set_expiration (GTimeoutData *data, - GTimeVal *current_time) +g_timeout_set_expiration (GTimeoutSource *timeout_source, + GTimeVal *current_time) { - guint seconds = data->interval / 1000; - guint msecs = data->interval - seconds * 1000; + guint seconds = timeout_source->interval / 1000; + guint msecs = timeout_source->interval - seconds * 1000; - data->expiration.tv_sec = current_time->tv_sec + seconds; - data->expiration.tv_usec = current_time->tv_usec + msecs * 1000; - if (data->expiration.tv_usec >= 1000000) + timeout_source->expiration.tv_sec = current_time->tv_sec + seconds; + timeout_source->expiration.tv_usec = current_time->tv_usec + msecs * 1000; + if (timeout_source->expiration.tv_usec >= 1000000) { - data->expiration.tv_usec -= 1000000; - data->expiration.tv_sec++; + timeout_source->expiration.tv_usec -= 1000000; + timeout_source->expiration.tv_sec++; } } static gboolean -g_timeout_prepare (gpointer source_data, - GTimeVal *current_time, - gint *timeout, - gpointer user_data) +g_timeout_prepare (GSource *source, + gint *timeout) { glong msec; - GTimeoutData *data = source_data; + GTimeVal current_time; - msec = ((data->expiration.tv_sec - current_time->tv_sec) * 1000 + - (data->expiration.tv_usec - current_time->tv_usec) / 1000); + GTimeoutSource *timeout_source = (GTimeoutSource *)source; + + g_source_get_current_time (source, ¤t_time); + msec = ((timeout_source->expiration.tv_sec - current_time.tv_sec) * 1000 + + (timeout_source->expiration.tv_usec - current_time.tv_usec) / 1000); + if (msec < 0) msec = 0; - else if (msec > data->interval) + else if (msec > timeout_source->interval) { /* The system time has been set backwards, so we - * reset the expiration time to now + data->interval; + * reset the expiration time to now + timeout_source->interval; * this at least avoids hanging for long periods of time. */ - g_timeout_set_expiration (data, current_time); - msec = data->interval; + g_timeout_set_expiration (timeout_source, ¤t_time); + msec = timeout_source->interval; } *timeout = msec; @@ -1321,27 +2519,38 @@ g_timeout_prepare (gpointer source_data, } static gboolean -g_timeout_check (gpointer source_data, - GTimeVal *current_time, - gpointer user_data) +g_timeout_check (GSource *source) { - GTimeoutData *data = source_data; + GTimeVal current_time; + GTimeoutSource *timeout_source = (GTimeoutSource *)source; + + g_source_get_current_time (source, ¤t_time); - return ((data->expiration.tv_sec < current_time->tv_sec) || - ((data->expiration.tv_sec == current_time->tv_sec) && - (data->expiration.tv_usec <= current_time->tv_usec))); + return ((timeout_source->expiration.tv_sec < current_time.tv_sec) || + ((timeout_source->expiration.tv_sec == current_time.tv_sec) && + (timeout_source->expiration.tv_usec <= current_time.tv_usec))); } static gboolean -g_timeout_dispatch (gpointer source_data, - GTimeVal *dispatch_time, - gpointer user_data) +g_timeout_dispatch (GSource *source, + GSourceFunc callback, + gpointer user_data) { - GTimeoutData *data = source_data; + GTimeoutSource *timeout_source = (GTimeoutSource *)source; - if (data->callback (user_data)) + if (!callback) { - g_timeout_set_expiration (data, dispatch_time); + g_warning ("Timeout source dispatched without callback\n" + "You must call g_source_set_callback()."); + return FALSE; + } + + if (callback (user_data)) + { + GTimeVal current_time; + + g_source_get_current_time (source, ¤t_time); + g_timeout_set_expiration (timeout_source, ¤t_time); return TRUE; } @@ -1349,6 +2558,58 @@ g_timeout_dispatch (gpointer source_data, return FALSE; } +/** + * g_timeout_source_new: + * @interval: the timeout interval in milliseconds. + * + * Create a new timeout source. + * + * 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. + * + * Return value: the newly create timeout source + **/ +GSource * +g_timeout_source_new (guint interval) +{ + GSource *source = g_source_new (&timeout_funcs, sizeof (GTimeoutSource)); + GTimeoutSource *timeout_source = (GTimeoutSource *)source; + GTimeVal current_time; + + timeout_source->interval = interval; + + g_get_current_time (¤t_time); + g_timeout_set_expiration (timeout_source, ¤t_time); + + return source; +} + +/** + * g_timeout_add_full: + * @priority: the priority of the idle source. Typically this will be in the + * range btweeen #G_PRIORITY_DEFAULT_IDLE and #G_PRIORITY_HIGH_IDLE. + * @interval: the time between calls to the function, in milliseconds + * (1/1000ths of a second.) + * @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 at regular intervals, with the given + * priority. The function is called repeatedly until it returns + * FALSE, at which point the timeout is automatically destroyed and + * the function will not be called again. The @notify function is + * called when the timeout is destroyed. The first call to the + * function will be at the end of the first @interval. + * + * Note that timeout functions may be delayed, due to the processing of other + * event sources. Thus they should not be relied on for precise timing. + * After each call to the timeout function, the time of the next + * timeout is recalculated based on the current time and the given interval + * (it does not try to 'catch up' time lost in delays). + * + * Return value: the id of event source. + **/ guint g_timeout_add_full (gint priority, guint interval, @@ -1356,18 +2617,41 @@ g_timeout_add_full (gint priority, gpointer data, GDestroyNotify notify) { - GTimeoutData *timeout_data = g_new (GTimeoutData, 1); - GTimeVal current_time; + GSource *source; + + g_return_val_if_fail (function != NULL, 0); - timeout_data->interval = interval; - timeout_data->callback = function; - g_get_current_time (¤t_time); + source = g_timeout_source_new (interval); - g_timeout_set_expiration (timeout_data, ¤t_time); + if (priority != G_PRIORITY_DEFAULT) + g_source_set_priority (source, priority); - return g_source_add (priority, FALSE, &timeout_funcs, timeout_data, data, notify); + g_source_set_callback (source, function, data, notify); + return g_source_attach (source, NULL); } +/** + * g_timeout_add: + * @interval: the time between calls to the function, in milliseconds + * (1/1000ths of a second.) + * @function: function to call + * @data: data to pass to @function + * + * Sets a function to be called at regular intervals, with the default + * priority, #G_PRIORITY_DEFAULT. The function is called repeatedly + * until it returns FALSE, at which point the timeout is automatically + * destroyed and the function will not be called again. The @notify + * function is called when the timeout is destroyed. The first call + * to the function will be at the end of the first @interval. + * + * Note that timeout functions may be delayed, due to the processing of other + * event sources. Thus they should not be relied on for precise timing. + * After each call to the timeout function, the time of the next + * timeout is recalculated based on the current time and the given interval + * (it does not try to 'catch up' time lost in delays). + * + * Return value: the id of event source. + **/ guint g_timeout_add (guint32 interval, GSourceFunc function, @@ -1380,10 +2664,8 @@ g_timeout_add (guint32 interval, /* Idle functions */ static gboolean -g_idle_prepare (gpointer source_data, - GTimeVal *current_time, - gint *timeout, - gpointer user_data) +g_idle_prepare (GSource *source, + gint *timeout) { *timeout = 0; @@ -1391,34 +2673,89 @@ g_idle_prepare (gpointer source_data, } static gboolean -g_idle_check (gpointer source_data, - GTimeVal *current_time, - gpointer user_data) +g_idle_check (GSource *source) { return TRUE; } static gboolean -g_idle_dispatch (gpointer source_data, - GTimeVal *dispatch_time, - gpointer user_data) +g_idle_dispatch (GSource *source, + GSourceFunc callback, + gpointer user_data) { - GSourceFunc func = (GSourceFunc) source_data; - - return func (user_data); + if (!callback) + { + g_warning ("Idle source dispatched without callback\n" + "You must call g_source_set_callback()."); + return FALSE; + } + + return callback (user_data); } +/** + * g_idle_source_new: + * + * Create a new idle source. + * + * 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. + * + * Return value: the newly created idle source + **/ +GSource * +g_idle_source_new (void) +{ + return g_source_new (&idle_funcs, sizeof (GSource)); +} + +/** + * g_idle_add_full: + * @priority: the priority of the idle source. Typically this will be in the + * range btweeen #G_PRIORITY_DEFAULT_IDLE and #G_PRIORITY_HIGH_IDLE. + * @function: function to call + * @data: data to pass to @function + * @notify: function to call when the idle is removed, or %NULL + * + * Adds a function to be called whenever there are no higher priority + * events pending. If the function returns FALSE it is automatically + * removed from the list of event sources and will not be called again. + * + * Return value: the id of the event source. + **/ guint g_idle_add_full (gint priority, GSourceFunc function, gpointer data, GDestroyNotify notify) { + GSource *source; + g_return_val_if_fail (function != NULL, 0); - return g_source_add (priority, FALSE, &idle_funcs, (gpointer) function, data, notify); + source = g_idle_source_new (); + + if (priority != G_PRIORITY_DEFAULT) + g_source_set_priority (source, priority); + + g_source_set_callback (source, function, data, notify); + return g_source_attach (source, NULL); } +/** + * g_idle_add: + * @function: function to call + * @data: data to pass to @function. + * + * Adds a function to be called whenever there are no higher priority + * events pending to the default main loop. The function is given the + * default idle priority, #G_PRIORITY_DEFAULT_IDLE. If the function + * returns FALSE it is automatically removed from the list of event + * sources and will not be called again. + * + * Return value: the id of the event source. + **/ guint g_idle_add (GSourceFunc function, gpointer data) @@ -1426,8 +2763,17 @@ g_idle_add (GSourceFunc function, return g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, function, data, NULL); } +/** + * g_idle_remove_by_data: + * @data: the data for the idle source's callback. + * + * Removes the idle function with the given data. + * + * Return value: %TRUE if an idle source was found and removed. + **/ gboolean g_idle_remove_by_data (gpointer data) { return g_source_remove_by_funcs_user_data (&idle_funcs, data); } + diff --git a/gmain.h b/gmain.h index 7f08c3dfb..8e4d7fce7 100644 --- a/gmain.h +++ b/gmain.h @@ -1,122 +1,87 @@ -/* GLIB - Library of useful routines for C programming - * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald +/* gmain.h - the GLib Main loop + * Copyright (C) 1998-2000 Red Hat, Inc. * * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public + * modify it under the terms of the GNU Library 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. + * Library General Public License for more details. * - * You should have received a copy of the GNU Lesser General Public + * You should have received a copy of the GNU Library 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. */ -/* - * Modified by the GLib Team and others 1997-2000. See the AUTHORS - * file for a list of people on the GLib Team. See the ChangeLog - * files for a list of changes. These files are distributed with - * GLib at ftp://ftp.gtk.org/pub/gtk/. - */ - #ifndef __G_MAIN_H__ #define __G_MAIN_H__ -#include +#include +#include G_BEGIN_DECLS -/* Main loop - */ -typedef struct _GTimeVal GTimeVal; -typedef struct _GSourceFuncs GSourceFuncs; -typedef struct _GMainLoop GMainLoop; /* Opaque */ +typedef struct _GMainContext GMainContext; /* Opaque */ +typedef struct _GMainLoop GMainLoop; /* Opaque */ +typedef struct _GSource GSource; +typedef struct _GSourceCallbackFuncs GSourceCallbackFuncs; +typedef struct _GSourceFuncs GSourceFuncs; -struct _GTimeVal +typedef gboolean (*GSourceFunc) (gpointer data); + +struct _GSource { - glong tv_sec; - glong tv_usec; + /*< private >*/ + gpointer callback_data; + GSourceCallbackFuncs *callback_funcs; + + GSourceFuncs *source_funcs; + guint ref_count; + + GMainContext *context; + + gint priority; + guint flags; + guint id; + + GSList *poll_fds; + + GSource *prev; + GSource *next; }; + +struct _GSourceCallbackFuncs +{ + void (*ref) (gpointer cb_data); + void (*unref) (gpointer cb_data); + void (*get) (gpointer cb_data, + GSourceFunc *func, + gpointer *data); +}; + struct _GSourceFuncs { - gboolean (*prepare) (gpointer source_data, - GTimeVal *current_time, - gint *timeout, - gpointer user_data); - gboolean (*check) (gpointer source_data, - GTimeVal *current_time, - gpointer user_data); - gboolean (*dispatch) (gpointer source_data, - GTimeVal *dispatch_time, - gpointer user_data); - GDestroyNotify destroy; + gboolean (*prepare) (GSource *source, + gint *timeout); + gboolean (*check) (GSource *source); + gboolean (*dispatch) (GSource *source, + GSourceFunc callback, + gpointer user_data); + void (*destroy) (GSource *source); }; -/* Standard priorities */ - -#define G_PRIORITY_HIGH -100 -#define G_PRIORITY_DEFAULT 0 -#define G_PRIORITY_HIGH_IDLE 100 -#define G_PRIORITY_DEFAULT_IDLE 200 -#define G_PRIORITY_LOW 300 - -typedef gboolean (*GSourceFunc) (gpointer data); - -/* Hooks for adding to the main loop */ -guint g_source_add (gint priority, - gboolean can_recurse, - GSourceFuncs *funcs, - gpointer source_data, - gpointer user_data, - GDestroyNotify notify); -gboolean g_source_remove (guint tag); -gboolean g_source_remove_by_user_data (gpointer user_data); -gboolean g_source_remove_by_source_data (gpointer source_data); -gboolean g_source_remove_by_funcs_user_data (GSourceFuncs *funcs, - gpointer user_data); - -void g_get_current_time (GTimeVal *result); - -/* Running the main loop */ -GMainLoop* g_main_new (gboolean is_running); -void g_main_run (GMainLoop *loop); -void g_main_quit (GMainLoop *loop); -void g_main_destroy (GMainLoop *loop); -gboolean g_main_is_running (GMainLoop *loop); - -/* Run a single iteration of the mainloop. If block is FALSE, - * will never block - */ -gboolean g_main_iteration (gboolean may_block); - -/* See if any events are pending */ -gboolean g_main_pending (void); - -/* 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 destroy); -gboolean g_idle_remove_by_data (gpointer data); - -/* GPollFD +/* Any definitions using GPollFD or GPollFunc are primarily + * for Unix and not guaranteed to be the compatible on all + * operating systems on which GLib runs. Right now, the + * GLib does use these functions on Win32 as well, but interprets + * them in a fairly different way than on Unix. If you use + * these definitions, you are should be prepared to recode + * for different operating systems. * - * System-specific IO and main loop calls * * On Win32, the fd in a GPollFD should be Win32 HANDLE (*not* a file * descriptor as provided by the C runtime) that can be used by @@ -137,32 +102,185 @@ gboolean g_idle_remove_by_data (gpointer data); * Win32. It's really only written for the GIMP's needs so * far. */ - typedef struct _GPollFD GPollFD; -typedef gint (*GPollFunc) (GPollFD *ufds, - guint nfsd, - gint timeout); +typedef gint (*GPollFunc) (GPollFD *ufds, + guint nfsd, + gint timeout); + struct _GPollFD { - gint fd; - gushort events; - gushort revents; + gint fd; + gushort events; + gushort revents; }; -void g_main_add_poll (GPollFD *fd, - gint priority); -void g_main_remove_poll (GPollFD *fd); -void g_main_set_poll_func (GPollFunc func); +/* Standard priorities */ + +#define G_PRIORITY_HIGH -100 +#define G_PRIORITY_DEFAULT 0 +#define G_PRIORITY_HIGH_IDLE 100 +#define G_PRIORITY_DEFAULT_IDLE 200 +#define G_PRIORITY_LOW 300 + +/* GMainContext: */ + +GMainContext *g_main_context_get (GThread *thread); +GMainContext *g_main_context_default (void); + +gboolean g_main_context_iteration (GMainContext *context, + gboolean may_block); +gboolean g_main_context_pending (GMainContext *context); + +/* For implementation of legacy interfaces + */ +GSource *g_main_context_find_source_by_id (GMainContext *context, + guint id); +GSource *g_main_context_find_source_by_user_data (GMainContext *context, + gpointer user_data); +GSource *g_main_context_find_source_by_funcs_user_data (GMainContext *context, + GSourceFuncs *funcs, + gpointer user_data); + +/* Low level functions for implementing custom main loops. + */ +gboolean g_main_context_prepare (GMainContext *context, + gint *priority); +gint g_main_context_query (GMainContext *context, + gint max_priority, + gint *timeout, + GPollFD *fds, + gint n_fds); +gint g_main_context_check (GMainContext *context, + gint max_priority, + GPollFD *fds, + gint n_fds); +void g_main_context_dispatch (GMainContext *context); + +void g_main_context_set_poll_func (GMainContext *context, + GPollFunc func); +GPollFunc g_main_context_get_poll_func (GMainContext *context); + +/* Low level functions for use by source implementations + */ +void g_main_context_add_poll (GMainContext *context, + GPollFD *fd, + gint priority); +void g_main_context_remove_poll (GMainContext *context, + GPollFD *fd); + +/* GMainLoop: */ + +GMainLoop *g_main_loop_new (GMainContext *context, + gboolean is_running); +void g_main_loop_run (GMainLoop *loop); +void g_main_loop_quit (GMainLoop *loop); +void g_main_loop_destroy (GMainLoop *loop); +gboolean g_main_loop_is_running (GMainLoop *loop); + +/* GSource: */ + +GSource *g_source_new (GSourceFuncs *source_funcs, + guint struct_size); +GSource *g_source_ref (GSource *source); +void g_source_unref (GSource *source); + +guint g_source_attach (GSource *source, + GMainContext *context); +void g_source_destroy (GSource *source); + +void g_source_set_priority (GSource *source, + gint priority); +gint g_source_get_priority (GSource *source); +void g_source_set_can_recurse (GSource *source, + gboolean can_recurse); +gboolean g_source_get_can_recurse (GSource *source); +guint g_source_get_id (GSource *source); + +GMainContext *g_source_get_context (GSource *source); + +void g_source_set_callback (GSource *source, + GSourceFunc func, + gpointer data, + GDestroyNotify notify); + + +/* Used to implement g_source_connect_closure and internally*/ +void g_source_set_callback_indirect (GSource *source, + gpointer callback_data, + GSourceCallbackFuncs *callback_funcs); + +void g_source_add_poll (GSource *source, + GPollFD *fd); +void g_source_remove_poll (GSource *source, + GPollFD *fd); + +void g_source_get_current_time (GSource *source, + GTimeVal *timeval); + + /* void g_source_connect_closure (GSource *source, + GClosure *closure); + */ + +/* Specific source types + */ +GSource *g_idle_source_new (void); +GSource *g_timeout_source_new (guint interval); + +/* Miscellaneous functions + */ +void g_get_current_time (GTimeVal *result); + +/* ============== Compat main loop stuff ================== */ + +/* Legacy names for GMainLoop functions + */ +#define g_main_new(is_running) g_main_loop_new (NULL, is_running); +#define g_main_run(loop) g_main_loop_run(loop) +#define g_main_quit(loop) g_main_loop_quit(loop) +#define g_main_destroy(loop) g_main_loop_destroy(loop) +#define g_main_is_running(loop) g_main_loop_is_running(loop) + +/* Source manipulation by ID */ +gboolean g_source_remove (guint tag); +gboolean g_source_remove_by_user_data (gpointer user_data); +gboolean g_source_remove_by_funcs_user_data (GSourceFuncs *funcs, + gpointer user_data); + +/* Functions to manipulate the default main loop + */ + +#define g_main_iteration(may_block) g_main_context_iteration (NULL, may_block) +#define g_main_pending() g_main_context_pending (NULL) + +#define g_main_set_poll_func(func) g_main_context_set_poll_func (NULL, func) + +/* 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); #ifdef G_OS_WIN32 -/* Useful on other platforms, too? */ - -GPollFunc g_main_win32_get_poll_func (void); - -#endif +/* This is used to add polling for Windows messages. GDK (GTK+) programs + * should *not* use this. + */ +void g_main_poll_win32_msg_add (gint priority, + GPollFD *fd, + guint hwnd); +#endif G_OS_WIN32 G_END_DECLS #endif /* __G_MAIN_H__ */ - diff --git a/gthread.h b/gthread.h index e99c78d5e..12681784b 100644 --- a/gthread.h +++ b/gthread.h @@ -28,7 +28,6 @@ #define __G_THREAD_H__ #include -#include G_BEGIN_DECLS diff --git a/gtypes.h b/gtypes.h index c43d322a7..9614c73db 100644 --- a/gtypes.h +++ b/gtypes.h @@ -314,6 +314,14 @@ union _GDoubleIEEE754 #error unknown ENDIAN type #endif /* !G_LITTLE_ENDIAN && !G_BIG_ENDIAN */ +typedef struct _GTimeVal GTimeVal; + +struct _GTimeVal +{ + glong tv_sec; + glong tv_usec; +}; + G_END_DECLS #endif /* __G_TYPES_H__ */ diff --git a/tests/.cvsignore b/tests/.cvsignore index a714e362e..4f67021e9 100644 --- a/tests/.cvsignore +++ b/tests/.cvsignore @@ -11,6 +11,7 @@ date-test dirname-test hash-test list-test +mainloop-test markup-test node-test queue-test diff --git a/tests/Makefile.am b/tests/Makefile.am index 0838262b9..3195e8fa3 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -19,6 +19,7 @@ TESTS = \ gio-test \ hash-test \ list-test \ + mainloop-test \ markup-test \ node-test \ queue-test \ @@ -45,6 +46,7 @@ dirname_test_LDADD = $(progs_LDADD) gio_test_LDADD = $(progs_LDADD) hash_test_LDADD = $(progs_LDADD) list_test_LDADD = $(progs_LDADD) +mainloop_test_LDADD = $(thread_LDADD) markup_test_LDADD = $(progs_LDADD) node_test_LDADD = $(progs_LDADD) queue_test_LDADD = $(progs_LDADD) diff --git a/tests/mainloop-test.c b/tests/mainloop-test.c new file mode 100644 index 000000000..aebefd166 --- /dev/null +++ b/tests/mainloop-test.c @@ -0,0 +1,396 @@ +#include +#include +#include +#include +#include + +#define ITERS 10000 +#define INCREMENT 10 +#define NTHREADS 4 +#define NCRAWLERS 4 +#define CRAWLER_TIMEOUT_RANGE 40 +#define RECURSER_TIMEOUT 50 + +G_LOCK_DEFINE_STATIC (context_array_lock); +GPtrArray *context_array; +GMainLoop *main_loop; + +G_LOCK_DEFINE_STATIC (crawler_array_lock); +GPtrArray *crawler_array; + +typedef struct _AddrData AddrData; +typedef struct _TestData TestData; + +struct _AddrData +{ + GMainLoop *loop; + GIOChannel *dest; + gint count; +}; + +struct _TestData +{ + gint current_val; + gint iters; + GIOChannel *in; +}; + +static void cleanup_crawlers (GMainContext *context); + +gboolean +read_all (GIOChannel *channel, char *buf, int len) +{ + int bytes_read = 0; + int count; + GIOError err; + + while (bytes_read < len) + { + err = g_io_channel_read (channel, buf + bytes_read, len - bytes_read, &count); + if (err) + { + if (err != G_IO_ERROR_AGAIN) + return FALSE; + } + else if (count == 0) + return FALSE; + + bytes_read += count; + } + + return TRUE; +} + +gboolean +write_all (GIOChannel *channel, char *buf, int len) +{ + int bytes_written = 0; + int count; + GIOError err; + + while (bytes_written < len) + { + err = g_io_channel_write (channel, buf + bytes_written, len - bytes_written, &count); + if (err && err != G_IO_ERROR_AGAIN) + return FALSE; + + bytes_written += count; + } + + return TRUE; +} + +gboolean +adder_callback (GIOChannel *source, + GIOCondition condition, + gpointer data) +{ + char buf1[32]; + char buf2[32]; + + char result[32]; + + AddrData *addr_data = data; + + if (!read_all (source, buf1, 32) || + !read_all (source, buf2, 32)) + { + g_main_loop_quit (addr_data->loop); + return FALSE; + } + + sprintf (result, "%d", atoi(buf1) + atoi(buf2)); + write_all (addr_data->dest, result, 32); + + return TRUE; +} + +gboolean +timeout_callback (gpointer data) +{ + AddrData *addr_data = data; + + addr_data->count++; + + return TRUE; +} + +void +adder_thread (gpointer data) +{ + GMainContext *context; + GSource *adder_source; + GSource *timeout_source; + + GIOChannel **channels = data; + AddrData addr_data; + + context = g_main_context_get (g_thread_self()); + + G_LOCK (context_array_lock); + g_ptr_array_add (context_array, context); + G_UNLOCK (context_array_lock); + + addr_data.dest = channels[1]; + addr_data.loop = g_main_loop_new (context, FALSE); + addr_data.count = 0; + + adder_source = g_io_create_watch (channels[0], G_IO_IN | G_IO_HUP); + g_source_set_callback (adder_source, (GSourceFunc)adder_callback, &addr_data, NULL); + g_source_attach (adder_source, context); + + timeout_source = g_timeout_source_new (10); + g_source_set_callback (timeout_source, (GSourceFunc)timeout_callback, &addr_data, NULL); + g_source_set_priority (timeout_source, G_PRIORITY_HIGH); + g_source_attach (timeout_source, context); + + g_main_run (addr_data.loop); + + g_io_channel_close (channels[0]); + g_io_channel_close (channels[1]); + g_io_channel_unref (channels[0]); + g_io_channel_unref (channels[1]); + + g_free (channels); + + g_main_loop_destroy (addr_data.loop); + + g_print ("Timeout run %d times\n", addr_data.count); + + G_LOCK (context_array_lock); + g_ptr_array_remove (context_array, context); + if (context_array->len == 0) + g_main_loop_quit (main_loop); + G_UNLOCK (context_array_lock); + + cleanup_crawlers (context); +} + +void +io_pipe (GIOChannel **channels) +{ + gint fds[2]; + + if (pipe(fds) < 0) + { + g_warning ("Cannot create pipe %s\n", g_strerror (errno)); + exit (1); + } + + channels[0] = g_io_channel_unix_new (fds[0]); + channels[1] = g_io_channel_unix_new (fds[1]); +} + +void +do_add (GIOChannel *in, gint a, gint b) +{ + char buf1[32]; + char buf2[32]; + + sprintf (buf1, "%d", a); + sprintf (buf2, "%d", b); + + write_all (in, buf1, 32); + write_all (in, buf2, 32); +} + +gboolean +adder_response (GIOChannel *source, + GIOCondition condition, + gpointer data) +{ + char result[32]; + TestData *test_data = data; + + if (!read_all (source, result, 32)) + return FALSE; + + test_data->current_val = atoi (result); + test_data->iters--; + + if (test_data->iters == 0) + { + if (test_data->current_val != ITERS * INCREMENT) + { + g_print ("Addition failed: %d != %d\n", + test_data->current_val, ITERS * INCREMENT); + exit (1); + } + + g_io_channel_close (source); + g_io_channel_close (test_data->in); + + g_io_channel_unref (source); + g_io_channel_unref (test_data->in); + + return FALSE; + } + + do_add (test_data->in, test_data->current_val, INCREMENT); + + return TRUE; +} + +void +create_adder_thread (void) +{ + GError *err = NULL; + TestData *test_data; + + GIOChannel *in_channels[2]; + GIOChannel *out_channels[2]; + + GIOChannel **sub_channels; + + sub_channels = g_new (GIOChannel *, 2); + + io_pipe (in_channels); + io_pipe (out_channels); + + sub_channels[0] = in_channels[0]; + sub_channels[1] = out_channels[1]; + + g_thread_create (adder_thread, sub_channels, 0, + FALSE, TRUE, G_THREAD_PRIORITY_NORMAL, &err); + + if (err) + { + g_warning ("Cannot create thread: %s", err->message); + exit (1); + } + + test_data = g_new (TestData, 1); + test_data->in = in_channels[1]; + test_data->current_val = 0; + test_data->iters = ITERS; + + g_io_add_watch (out_channels[0], G_IO_IN | G_IO_HUP, + adder_response, test_data); + + do_add (test_data->in, test_data->current_val, INCREMENT); +} + +static void create_crawler (void); + +static void +remove_crawler (void) +{ + GSource *other_source; + + if (crawler_array->len > 0) + { + other_source = crawler_array->pdata[g_random_int_range (0, crawler_array->len)]; + g_source_destroy (other_source); + g_assert (g_ptr_array_remove_fast (crawler_array, other_source)); + } +} + +static gint +crawler_callback (gpointer data) +{ + GSource *source = data; + + G_LOCK (crawler_array_lock); + + if (!g_ptr_array_remove_fast (crawler_array, source)) + remove_crawler(); + + remove_crawler(); + G_UNLOCK (crawler_array_lock); + + create_crawler(); + create_crawler(); + + return FALSE; +} + +static void +create_crawler (void) +{ + GSource *source = g_timeout_source_new (g_random_int_range (0, CRAWLER_TIMEOUT_RANGE)); + g_source_set_callback (source, (GSourceFunc)crawler_callback, source, NULL); + + G_LOCK (context_array_lock); + g_source_attach (source, context_array->pdata[g_random_int_range (0, context_array->len)]); + G_UNLOCK (context_array_lock); + + G_LOCK (crawler_array_lock); + g_ptr_array_add (crawler_array, source); + G_UNLOCK (crawler_array_lock); +} + +static void +cleanup_crawlers (GMainContext *context) +{ + gint i; + + G_LOCK (crawler_array_lock); + for (i=0; i < crawler_array->len; i++) + { + if (g_source_get_context (crawler_array->pdata[i]) == context) + { + g_source_destroy (g_ptr_array_remove_index (crawler_array, i)); + i--; + } + } + G_UNLOCK (crawler_array_lock); +} + +static gboolean +recurser_idle (gpointer data) +{ + GMainContext *context = data; + gint i; + + for (i = 0; i < 10; i++) + g_main_context_iteration (context, TRUE); + + return FALSE; +} + +static gboolean +recurser_start (gpointer data) +{ + GMainContext *context; + GSource *source; + + G_LOCK (context_array_lock); + context = context_array->pdata[g_random_int_range (0, context_array->len)]; + source = g_idle_source_new (); + g_source_set_callback (source, recurser_idle, context, NULL); + g_source_attach (source, context); + G_UNLOCK (context_array_lock); + + return TRUE; +} + +int +main (int argc, + char *argv[]) +{ + /* Only run the test, if threads are enabled and a default thread + implementation is available */ +#if defined(G_THREADS_ENABLED) && ! defined(G_THREADS_IMPL_NONE) + gint i; + + g_thread_init (NULL); + + context_array = g_ptr_array_new (); + crawler_array = g_ptr_array_new (); + + main_loop = g_main_loop_new (NULL, FALSE); + + for (i = 0; i < NTHREADS; i++) + create_adder_thread (); + + for (i = 0; i < NCRAWLERS; i++) + create_crawler (); + + g_timeout_add (RECURSER_TIMEOUT, recurser_start, NULL); + + g_main_loop_run (main_loop); + g_main_loop_destroy (main_loop); + +#endif + return 0; +} diff --git a/tests/timeloop-basic.c b/tests/timeloop-basic.c new file mode 100644 index 000000000..64d41330e --- /dev/null +++ b/tests/timeloop-basic.c @@ -0,0 +1,232 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define TRUE 1 +#define FALSE 0 + +static int n_children = 3; +static int n_active_children; +static int n_iters = 10000; + +static int write_fds[1024]; +static struct pollfd poll_fds[1024]; + +void +my_pipe (int *fds) +{ + if (pipe(fds) < 0) + { + fprintf (stderr, "Cannot create pipe %s\n", strerror (errno)); + exit (1); + } +} + +int +read_all (int fd, char *buf, int len) +{ + int bytes_read = 0; + int count; + + while (bytes_read < len) + { + count = read (fd, buf + bytes_read, len - bytes_read); + if (count < 0) + { + if (errno != EAGAIN) + return FALSE; + } + else if (count == 0) + return FALSE; + + bytes_read += count; + } + + return TRUE; +} + +int +write_all (int fd, char *buf, int len) +{ + int bytes_written = 0; + int count; + + while (bytes_written < len) + { + count = write (fd, buf + bytes_written, len - bytes_written); + if (count < 0) + { + if (errno != EAGAIN) + return FALSE; + } + + bytes_written += count; + } + + return TRUE; +} + +void +run_child (int in_fd, int out_fd) +{ + int i; + int val = 1; + + for (i = 0; i < n_iters; i++) + { + write_all (out_fd, (char *)&val, sizeof (val)); + read_all (in_fd, (char *)&val, sizeof (val)); + } + + val = 0; + write_all (out_fd, (char *)&val, sizeof (val)); + + exit (0); +} + +int +input_callback (int source, int dest) +{ + int val; + + if (!read_all (source, (char *)&val, sizeof(val))) + { + fprintf (stderr,"Unexpected EOF\n"); + exit (1); + } + + if (val) + { + write_all (dest, (char *)&val, sizeof(val)); + return TRUE; + } + else + { + close (source); + close (dest); + + n_active_children--; + return FALSE; + } +} + +void +create_child (int pos) +{ + int pid; + int in_fds[2]; + int out_fds[2]; + + my_pipe (in_fds); + my_pipe (out_fds); + + pid = fork (); + + if (pid > 0) /* Parent */ + { + close (in_fds[0]); + close (out_fds[1]); + + write_fds[pos] = in_fds[1]; + poll_fds[pos].fd = out_fds[0]; + poll_fds[pos].events = POLLIN; + } + else if (pid == 0) /* Child */ + { + close (in_fds[1]); + close (out_fds[0]); + + setsid (); + + run_child (in_fds[0], out_fds[1]); + } + else /* Error */ + { + fprintf (stderr,"Cannot fork: %s\n", strerror (errno)); + exit (1); + } +} + +static double +difftimeval (struct timeval *old, struct timeval *new) +{ + return + (new->tv_sec - old->tv_sec) * 1000. + (new->tv_usec - old->tv_usec) / 1000; +} + +int +main (int argc, char **argv) +{ + int i, j; + struct rusage old_usage; + struct rusage new_usage; + + if (argc > 1) + n_children = atoi(argv[1]); + + if (argc > 2) + n_iters = atoi(argv[2]); + + printf ("Children: %d Iters: %d\n", n_children, n_iters); + + n_active_children = n_children; + for (i = 0; i < n_children; i++) + create_child (i); + + getrusage (RUSAGE_SELF, &old_usage); + + while (n_active_children > 0) + { + int old_n_active_children = n_active_children; + + poll (poll_fds, n_active_children, -1); + + for (i=0; i n_active_children) + { + j = 0; + for (i=0; i +#include +#include +#include +#include +#include + +#include + +static int n_children = 3; +static int n_active_children; +static int n_iters = 10000; +static GMainLoop *loop; + +void +io_pipe (GIOChannel **channels) +{ + int fds[2]; + + if (pipe(fds) < 0) + { + fprintf (stderr, "Cannot create pipe %s\n", g_strerror (errno)); + exit (1); + } + + channels[0] = g_io_channel_unix_new (fds[0]); + channels[1] = g_io_channel_unix_new (fds[1]); +} + +gboolean +read_all (GIOChannel *channel, char *buf, int len) +{ + int bytes_read = 0; + int count; + GIOError err; + + while (bytes_read < len) + { + err = g_io_channel_read (channel, buf + bytes_read, len - bytes_read, &count); + if (err) + { + if (err != G_IO_ERROR_AGAIN) + return FALSE; + } + else if (count == 0) + return FALSE; + + bytes_read += count; + } + + return TRUE; +} + +gboolean +write_all (GIOChannel *channel, char *buf, int len) +{ + int bytes_written = 0; + int count; + GIOError err; + + while (bytes_written < len) + { + err = g_io_channel_write (channel, buf + bytes_written, len - bytes_written, &count); + if (err && err != G_IO_ERROR_AGAIN) + return FALSE; + + bytes_written += count; + } + + return TRUE; +} + +void +run_child (GIOChannel *in_channel, GIOChannel *out_channel) +{ + int i; + int val = 1; + GTimer *timer = g_timer_new(); + + for (i = 0; i < n_iters; i++) + { + write_all (out_channel, (char *)&val, sizeof (val)); + read_all (in_channel, (char *)&val, sizeof (val)); + } + + val = 0; + write_all (out_channel, (char *)&val, sizeof (val)); + + val = g_timer_elapsed (timer, NULL) * 1000; + + write_all (out_channel, (char *)&val, sizeof (val)); + g_timer_destroy (timer); + + exit (0); +} + +gboolean +input_callback (GIOChannel *source, + GIOCondition condition, + gpointer data) +{ + int val; + GIOChannel *dest = (GIOChannel *)data; + + if (!read_all (source, (char *)&val, sizeof(val))) + { + fprintf (stderr, "Unexpected EOF\n"); + exit (1); + } + + if (val) + { + write_all (dest, (char *)&val, sizeof(val)); + + return TRUE; + } + else + { + g_io_channel_close (source); + g_io_channel_close (dest); + + g_io_channel_unref (source); + g_io_channel_unref (dest); + + n_active_children--; + if (n_active_children == 0) + g_main_quit (loop); + + return FALSE; + } +} + +void +create_child () +{ + int pid; + GIOChannel *in_channels[2]; + GIOChannel *out_channels[2]; + + io_pipe (in_channels); + io_pipe (out_channels); + + pid = fork (); + + if (pid > 0) /* Parent */ + { + g_io_channel_close (in_channels[0]); + g_io_channel_close (out_channels[1]); + + g_io_add_watch (out_channels[0], G_IO_IN | G_IO_HUP, + input_callback, in_channels[1]); + } + else if (pid == 0) /* Child */ + { + g_io_channel_close (in_channels[1]); + g_io_channel_close (out_channels[0]); + + setsid (); + + run_child (in_channels[0], out_channels[1]); + } + else /* Error */ + { + fprintf (stderr, "Cannot fork: %s\n", g_strerror (errno)); + exit (1); + } +} + +static double +difftimeval (struct timeval *old, struct timeval *new) +{ + return + (new->tv_sec - old->tv_sec) * 1000. + (new->tv_usec - old->tv_usec) / 1000; +} + +int +main (int argc, char **argv) +{ + int i; + struct rusage old_usage; + struct rusage new_usage; + + if (argc > 1) + n_children = atoi(argv[1]); + + if (argc > 2) + n_iters = atoi(argv[2]); + + printf ("Children: %d Iters: %d\n", n_children, n_iters); + + n_active_children = n_children; + for (i = 0; i < n_children; i++) + create_child (); + + getrusage (RUSAGE_SELF, &old_usage); + loop = g_main_new (FALSE); + g_main_run (loop); + getrusage (RUSAGE_SELF, &new_usage); + + printf ("Elapsed user: %g\n", + difftimeval (&old_usage.ru_utime, &new_usage.ru_utime)); + printf ("Elapsed system: %g\n", + difftimeval (&old_usage.ru_stime, &new_usage.ru_stime)); + printf ("Elapsed total: %g\n", + difftimeval (&old_usage.ru_utime, &new_usage.ru_utime) + + difftimeval (&old_usage.ru_stime, &new_usage.ru_stime)); + printf ("total / iteration: %g\n", + (difftimeval (&old_usage.ru_utime, &new_usage.ru_utime) + + difftimeval (&old_usage.ru_stime, &new_usage.ru_stime)) / + (n_iters * n_children)); + + return 0; +} diff --git a/timeloop-basic.c b/timeloop-basic.c new file mode 100644 index 000000000..64d41330e --- /dev/null +++ b/timeloop-basic.c @@ -0,0 +1,232 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define TRUE 1 +#define FALSE 0 + +static int n_children = 3; +static int n_active_children; +static int n_iters = 10000; + +static int write_fds[1024]; +static struct pollfd poll_fds[1024]; + +void +my_pipe (int *fds) +{ + if (pipe(fds) < 0) + { + fprintf (stderr, "Cannot create pipe %s\n", strerror (errno)); + exit (1); + } +} + +int +read_all (int fd, char *buf, int len) +{ + int bytes_read = 0; + int count; + + while (bytes_read < len) + { + count = read (fd, buf + bytes_read, len - bytes_read); + if (count < 0) + { + if (errno != EAGAIN) + return FALSE; + } + else if (count == 0) + return FALSE; + + bytes_read += count; + } + + return TRUE; +} + +int +write_all (int fd, char *buf, int len) +{ + int bytes_written = 0; + int count; + + while (bytes_written < len) + { + count = write (fd, buf + bytes_written, len - bytes_written); + if (count < 0) + { + if (errno != EAGAIN) + return FALSE; + } + + bytes_written += count; + } + + return TRUE; +} + +void +run_child (int in_fd, int out_fd) +{ + int i; + int val = 1; + + for (i = 0; i < n_iters; i++) + { + write_all (out_fd, (char *)&val, sizeof (val)); + read_all (in_fd, (char *)&val, sizeof (val)); + } + + val = 0; + write_all (out_fd, (char *)&val, sizeof (val)); + + exit (0); +} + +int +input_callback (int source, int dest) +{ + int val; + + if (!read_all (source, (char *)&val, sizeof(val))) + { + fprintf (stderr,"Unexpected EOF\n"); + exit (1); + } + + if (val) + { + write_all (dest, (char *)&val, sizeof(val)); + return TRUE; + } + else + { + close (source); + close (dest); + + n_active_children--; + return FALSE; + } +} + +void +create_child (int pos) +{ + int pid; + int in_fds[2]; + int out_fds[2]; + + my_pipe (in_fds); + my_pipe (out_fds); + + pid = fork (); + + if (pid > 0) /* Parent */ + { + close (in_fds[0]); + close (out_fds[1]); + + write_fds[pos] = in_fds[1]; + poll_fds[pos].fd = out_fds[0]; + poll_fds[pos].events = POLLIN; + } + else if (pid == 0) /* Child */ + { + close (in_fds[1]); + close (out_fds[0]); + + setsid (); + + run_child (in_fds[0], out_fds[1]); + } + else /* Error */ + { + fprintf (stderr,"Cannot fork: %s\n", strerror (errno)); + exit (1); + } +} + +static double +difftimeval (struct timeval *old, struct timeval *new) +{ + return + (new->tv_sec - old->tv_sec) * 1000. + (new->tv_usec - old->tv_usec) / 1000; +} + +int +main (int argc, char **argv) +{ + int i, j; + struct rusage old_usage; + struct rusage new_usage; + + if (argc > 1) + n_children = atoi(argv[1]); + + if (argc > 2) + n_iters = atoi(argv[2]); + + printf ("Children: %d Iters: %d\n", n_children, n_iters); + + n_active_children = n_children; + for (i = 0; i < n_children; i++) + create_child (i); + + getrusage (RUSAGE_SELF, &old_usage); + + while (n_active_children > 0) + { + int old_n_active_children = n_active_children; + + poll (poll_fds, n_active_children, -1); + + for (i=0; i n_active_children) + { + j = 0; + for (i=0; i +#include +#include +#include +#include +#include + +#include + +static int n_children = 3; +static int n_active_children; +static int n_iters = 10000; +static GMainLoop *loop; + +void +io_pipe (GIOChannel **channels) +{ + int fds[2]; + + if (pipe(fds) < 0) + { + fprintf (stderr, "Cannot create pipe %s\n", g_strerror (errno)); + exit (1); + } + + channels[0] = g_io_channel_unix_new (fds[0]); + channels[1] = g_io_channel_unix_new (fds[1]); +} + +gboolean +read_all (GIOChannel *channel, char *buf, int len) +{ + int bytes_read = 0; + int count; + GIOError err; + + while (bytes_read < len) + { + err = g_io_channel_read (channel, buf + bytes_read, len - bytes_read, &count); + if (err) + { + if (err != G_IO_ERROR_AGAIN) + return FALSE; + } + else if (count == 0) + return FALSE; + + bytes_read += count; + } + + return TRUE; +} + +gboolean +write_all (GIOChannel *channel, char *buf, int len) +{ + int bytes_written = 0; + int count; + GIOError err; + + while (bytes_written < len) + { + err = g_io_channel_write (channel, buf + bytes_written, len - bytes_written, &count); + if (err && err != G_IO_ERROR_AGAIN) + return FALSE; + + bytes_written += count; + } + + return TRUE; +} + +void +run_child (GIOChannel *in_channel, GIOChannel *out_channel) +{ + int i; + int val = 1; + GTimer *timer = g_timer_new(); + + for (i = 0; i < n_iters; i++) + { + write_all (out_channel, (char *)&val, sizeof (val)); + read_all (in_channel, (char *)&val, sizeof (val)); + } + + val = 0; + write_all (out_channel, (char *)&val, sizeof (val)); + + val = g_timer_elapsed (timer, NULL) * 1000; + + write_all (out_channel, (char *)&val, sizeof (val)); + g_timer_destroy (timer); + + exit (0); +} + +gboolean +input_callback (GIOChannel *source, + GIOCondition condition, + gpointer data) +{ + int val; + GIOChannel *dest = (GIOChannel *)data; + + if (!read_all (source, (char *)&val, sizeof(val))) + { + fprintf (stderr, "Unexpected EOF\n"); + exit (1); + } + + if (val) + { + write_all (dest, (char *)&val, sizeof(val)); + + return TRUE; + } + else + { + g_io_channel_close (source); + g_io_channel_close (dest); + + g_io_channel_unref (source); + g_io_channel_unref (dest); + + n_active_children--; + if (n_active_children == 0) + g_main_quit (loop); + + return FALSE; + } +} + +void +create_child () +{ + int pid; + GIOChannel *in_channels[2]; + GIOChannel *out_channels[2]; + + io_pipe (in_channels); + io_pipe (out_channels); + + pid = fork (); + + if (pid > 0) /* Parent */ + { + g_io_channel_close (in_channels[0]); + g_io_channel_close (out_channels[1]); + + g_io_add_watch (out_channels[0], G_IO_IN | G_IO_HUP, + input_callback, in_channels[1]); + } + else if (pid == 0) /* Child */ + { + g_io_channel_close (in_channels[1]); + g_io_channel_close (out_channels[0]); + + setsid (); + + run_child (in_channels[0], out_channels[1]); + } + else /* Error */ + { + fprintf (stderr, "Cannot fork: %s\n", g_strerror (errno)); + exit (1); + } +} + +static double +difftimeval (struct timeval *old, struct timeval *new) +{ + return + (new->tv_sec - old->tv_sec) * 1000. + (new->tv_usec - old->tv_usec) / 1000; +} + +int +main (int argc, char **argv) +{ + int i; + struct rusage old_usage; + struct rusage new_usage; + + if (argc > 1) + n_children = atoi(argv[1]); + + if (argc > 2) + n_iters = atoi(argv[2]); + + printf ("Children: %d Iters: %d\n", n_children, n_iters); + + n_active_children = n_children; + for (i = 0; i < n_children; i++) + create_child (); + + getrusage (RUSAGE_SELF, &old_usage); + loop = g_main_new (FALSE); + g_main_run (loop); + getrusage (RUSAGE_SELF, &new_usage); + + printf ("Elapsed user: %g\n", + difftimeval (&old_usage.ru_utime, &new_usage.ru_utime)); + printf ("Elapsed system: %g\n", + difftimeval (&old_usage.ru_stime, &new_usage.ru_stime)); + printf ("Elapsed total: %g\n", + difftimeval (&old_usage.ru_utime, &new_usage.ru_utime) + + difftimeval (&old_usage.ru_stime, &new_usage.ru_stime)); + printf ("total / iteration: %g\n", + (difftimeval (&old_usage.ru_utime, &new_usage.ru_utime) + + difftimeval (&old_usage.ru_stime, &new_usage.ru_stime)) / + (n_iters * n_children)); + + return 0; +}