glib/tests/mainloop-test.c
Dan Winship 5bc7729d16 Make threads mandatory
G_THREADS_ENABLED still exists, but is always defined. It is still
possible to use libglib without threads, but gobject (and everything
above it) is now guaranteed to be using threads (as, in fact, it was
before, since it was accidentally impossible to compile with
--disable-threads).

https://bugzilla.gnome.org/show_bug.cgi?id=616754
2011-09-09 12:41:55 -04:00

439 lines
8.9 KiB
C

#undef G_DISABLE_ASSERT
#undef G_LOG_DOMAIN
#include <errno.h>
#include <glib.h>
#ifdef G_OS_UNIX
#include <unistd.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#ifdef G_OS_WIN32
#include <fcntl.h> /* For _O_BINARY used by pipe() macro */
#include <io.h> /* for _pipe() */
#define pipe(fds) _pipe(fds, 4096, _O_BINARY)
#endif
#define ITERS 10000
#define INCREMENT 10
#define NTHREADS 4
#define NCRAWLERS 4
#define CRAWLER_TIMEOUT_RANGE 40
#define RECURSER_TIMEOUT 50
/* The partial ordering between the context array mutex and
* crawler array mutex is that the crawler array mutex cannot
* be locked while the context array mutex is locked
*/
GPtrArray *context_array;
GMutex *context_array_mutex;
GCond *context_array_cond;
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, gsize len)
{
gsize bytes_read = 0;
gsize 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, gsize len)
{
gsize bytes_written = 0;
gsize 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;
}
gpointer
adder_thread (gpointer data)
{
GMainContext *context;
GSource *adder_source;
GSource *timeout_source;
GIOChannel **channels = data;
AddrData addr_data;
context = g_main_context_new ();
g_mutex_lock (context_array_mutex);
g_ptr_array_add (context_array, context);
if (context_array->len == NTHREADS)
g_cond_broadcast (context_array_cond);
g_mutex_unlock (context_array_mutex);
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_name (adder_source, "Adder I/O");
g_source_set_callback (adder_source, (GSourceFunc)adder_callback, &addr_data, NULL);
g_source_attach (adder_source, context);
g_source_unref (adder_source);
timeout_source = g_timeout_source_new (10);
g_source_set_name (timeout_source, "Adder timeout");
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_source_unref (timeout_source);
g_main_loop_run (addr_data.loop);
g_io_channel_unref (channels[0]);
g_io_channel_unref (channels[1]);
g_free (channels);
g_main_loop_unref (addr_data.loop);
#ifdef VERBOSE
g_print ("Timeout run %d times\n", addr_data.count);
#endif
g_mutex_lock (context_array_mutex);
g_ptr_array_remove (context_array, context);
if (context_array->len == 0)
g_main_loop_quit (main_loop);
g_mutex_unlock (context_array_mutex);
cleanup_crawlers (context);
return NULL;
}
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]);
g_io_channel_set_close_on_unref (channels[0], TRUE);
g_io_channel_set_close_on_unref (channels[1], TRUE);
}
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_unref (source);
g_io_channel_unref (test_data->in);
g_free (test_data);
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, FALSE, &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_name (source, "Crawler timeout");
g_source_set_callback (source, (GSourceFunc)crawler_callback, source, NULL);
G_LOCK (crawler_array_lock);
g_ptr_array_add (crawler_array, source);
g_mutex_lock (context_array_mutex);
g_source_attach (source, context_array->pdata[g_random_int_range (0, context_array->len)]);
g_source_unref (source);
g_mutex_unlock (context_array_mutex);
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, FALSE);
return FALSE;
}
static gboolean
recurser_start (gpointer data)
{
GMainContext *context;
GSource *source;
g_mutex_lock (context_array_mutex);
context = context_array->pdata[g_random_int_range (0, context_array->len)];
source = g_idle_source_new ();
g_source_set_name (source, "Recursing idle source");
g_source_set_callback (source, recurser_idle, context, NULL);
g_source_attach (source, context);
g_source_unref (source);
g_mutex_unlock (context_array_mutex);
return TRUE;
}
int
main (int argc,
char *argv[])
{
gint i;
g_thread_init (NULL);
context_array = g_ptr_array_new ();
context_array_mutex = g_mutex_new ();
context_array_cond = g_cond_new ();
crawler_array = g_ptr_array_new ();
main_loop = g_main_loop_new (NULL, FALSE);
for (i = 0; i < NTHREADS; i++)
create_adder_thread ();
/* Wait for all threads to start
*/
g_mutex_lock (context_array_mutex);
if (context_array->len < NTHREADS)
g_cond_wait (context_array_cond, context_array_mutex);
g_mutex_unlock (context_array_mutex);
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_unref (main_loop);
return 0;
}