/* Unit tests for GCond * Copyright (C) 2011 Red Hat, Inc * Author: Matthias Clasen * * This work is provided "as is"; redistribution and modification * in whole or in part, in any medium, physical or electronic is * permitted without restriction. * * This work 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. * * In no event shall the authors or contributors be liable for any * direct, indirect, incidental, special, exemplary, or consequential * damages (including, but not limited to, procurement of substitute * goods or services; loss of use, data, or profits; or business * interruption) however caused and on any theory of liability, whether * in contract, strict liability, or tort (including negligence or * otherwise) arising in any way out of the use of this software, even * if advised of the possibility of such damage. */ /* We are testing some deprecated APIs here */ #define GLIB_DISABLE_DEPRECATION_WARNINGS #include static GCond cond; static GMutex mutex; static volatile gint next; static void push_value (gint value) { g_mutex_lock (&mutex); while (next != 0) g_cond_wait (&cond, &mutex); next = value; if (g_test_verbose ()) g_printerr ("Thread %p producing next value: %d\n", g_thread_self (), value); if (value % 10 == 0) g_cond_broadcast (&cond); else g_cond_signal (&cond); g_mutex_unlock (&mutex); } static gint pop_value (void) { gint value; g_mutex_lock (&mutex); while (next == 0) { if (g_test_verbose ()) g_printerr ("Thread %p waiting for cond\n", g_thread_self ()); g_cond_wait (&cond, &mutex); } value = next; next = 0; g_cond_broadcast (&cond); if (g_test_verbose ()) g_printerr ("Thread %p consuming value %d\n", g_thread_self (), value); g_mutex_unlock (&mutex); return value; } static gpointer produce_values (gpointer data) { gint total; gint i; total = 0; for (i = 1; i < 100; i++) { total += i; push_value (i); } push_value (-1); push_value (-1); if (g_test_verbose ()) g_printerr ("Thread %p produced %d altogether\n", g_thread_self (), total); return GINT_TO_POINTER (total); } static gpointer consume_values (gpointer data) { gint accum = 0; gint value; while (TRUE) { value = pop_value (); if (value == -1) break; accum += value; } if (g_test_verbose ()) g_printerr ("Thread %p accumulated %d\n", g_thread_self (), accum); return GINT_TO_POINTER (accum); } static GThread *producer, *consumer1, *consumer2; static void test_cond1 (void) { gint total, acc1, acc2; producer = g_thread_create (produce_values, NULL, TRUE, NULL); consumer1 = g_thread_create (consume_values, NULL, TRUE, NULL); consumer2 = g_thread_create (consume_values, NULL, TRUE, NULL); total = GPOINTER_TO_INT (g_thread_join (producer)); acc1 = GPOINTER_TO_INT (g_thread_join (consumer1)); acc2 = GPOINTER_TO_INT (g_thread_join (consumer2)); g_assert_cmpint (total, ==, acc1 + acc2); } typedef struct { GMutex mutex; GCond cond; gint limit; gint count; } Barrier; static void barrier_init (Barrier *barrier, gint limit) { g_mutex_init (&barrier->mutex); g_cond_init (&barrier->cond); barrier->limit = limit; barrier->count = limit; } static gint barrier_wait (Barrier *barrier) { gint ret; g_mutex_lock (&barrier->mutex); barrier->count--; if (barrier->count == 0) { ret = -1; barrier->count = barrier->limit; g_cond_broadcast (&barrier->cond); } else { ret = 0; while (barrier->count != barrier->limit) g_cond_wait (&barrier->cond, &barrier->mutex); } g_mutex_unlock (&barrier->mutex); return ret; } static void barrier_clear (Barrier *barrier) { g_mutex_clear (&barrier->mutex); g_cond_clear (&barrier->cond); } static Barrier b; static gint check; static gpointer cond2_func (gpointer data) { gint value = GPOINTER_TO_INT (data); gint ret; g_atomic_int_inc (&check); if (g_test_verbose ()) g_printerr ("thread %d starting, check %d\n", value, g_atomic_int_get (&check)); g_usleep (10000 * value); g_atomic_int_inc (&check); if (g_test_verbose ()) g_printerr ("thread %d reaching barrier, check %d\n", value, g_atomic_int_get (&check)); ret = barrier_wait (&b); g_assert_cmpint (g_atomic_int_get (&check), ==, 10); if (g_test_verbose ()) g_printerr ("thread %d leaving barrier (%d), check %d\n", value, ret, g_atomic_int_get (&check)); return NULL; } /* this test demonstrates how to use a condition variable * to implement a barrier */ static void test_cond2 (void) { gint i; GThread *threads[5]; g_atomic_int_set (&check, 0); barrier_init (&b, 5); for (i = 0; i < 5; i++) threads[i] = g_thread_create (cond2_func, GINT_TO_POINTER (i), TRUE, NULL); for (i = 0; i < 5; i++) g_thread_join (threads[i]); g_assert_cmpint (g_atomic_int_get (&check), ==, 10); barrier_clear (&b); } static void test_wait_until (void) { gint64 until; GMutex lock; GCond cond; /* This test will make sure we don't wait too much or too little. * * We check the 'too long' with a timeout of 60 seconds. * * We check the 'too short' by verifying a guarantee of the API: we * should not wake up until the specified time has passed. */ g_mutex_init (&lock); g_cond_init (&cond); until = g_get_monotonic_time () + G_TIME_SPAN_SECOND; /* Could still have spurious wakeups, so we must loop... */ g_mutex_lock (&lock); while (g_cond_wait_until (&cond, &lock, until)) ; g_mutex_unlock (&lock); /* Make sure it's after the until time */ g_assert_cmpint (until, <=, g_get_monotonic_time ()); /* Make sure it returns FALSE on timeout */ until = g_get_monotonic_time () + G_TIME_SPAN_SECOND / 50; g_mutex_lock (&lock); g_assert (g_cond_wait_until (&cond, &lock, until) == FALSE); g_mutex_unlock (&lock); g_mutex_clear (&lock); g_cond_clear (&cond); } int main (int argc, char *argv[]) { g_test_init (&argc, &argv, NULL); g_test_add_func ("/thread/cond1", test_cond1); g_test_add_func ("/thread/cond2", test_cond2); g_test_add_func ("/thread/cond/wait-until", test_wait_until); return g_test_run (); }