diff --git a/glib/tests/bitlock.c b/glib/tests/bitlock.c index a21672099..fe9c67044 100644 --- a/glib/tests/bitlock.c +++ b/glib/tests/bitlock.c @@ -1,5 +1,7 @@ #include +#include "gtestutils.h" + static void test_bitlocks (void) { @@ -28,12 +30,148 @@ test_bitlocks (void) } } +#define PARALLEL_N_THREADS 5 +#define PARALLEL_LOCKBIT 31 +#define PARALLEL_TOGGLEBIT 30 +#define PARALLEL_SETBIT 29 +#define PARALLEL_LOCKMASK (1 << PARALLEL_LOCKBIT) +#define PARALLEL_TOGGLEMASK (1 << PARALLEL_TOGGLEBIT) +#define PARALLEL_SETMASK (1 << PARALLEL_SETBIT) +#define PARALLEL_MAX_COUNT_SELF 500 +#define PARALLEL_MAX_COUNT_ALL (10 * PARALLEL_MAX_COUNT_SELF) +static int parallel_thread_ready = 0; +static int parallel_atomic = 0; + +static void +_test_parallel_randomly_toggle (void) +{ + if (g_random_boolean ()) + g_atomic_int_or (¶llel_atomic, PARALLEL_TOGGLEMASK); + else + g_atomic_int_and (¶llel_atomic, ~PARALLEL_TOGGLEMASK); +} + +static gpointer +_test_parallel_run (gpointer thread_arg) +{ + const int IDX = GPOINTER_TO_INT (thread_arg); + int count_self = 0; + + g_atomic_int_inc (¶llel_thread_ready); + while (g_atomic_int_get (¶llel_thread_ready) != PARALLEL_N_THREADS) + g_usleep (10); + + while (TRUE) + { + gint val; + gint val2; + gint new_val; + gint count_all; + + _test_parallel_randomly_toggle (); + + /* take a lock. */ + if (g_random_boolean ()) + { + g_bit_lock (¶llel_atomic, PARALLEL_LOCKBIT); + val = g_atomic_int_get (¶llel_atomic); + } + else + { + g_bit_lock_and_get (¶llel_atomic, PARALLEL_LOCKBIT, &val); + } + + _test_parallel_randomly_toggle (); + + /* the toggle bit is random. Clear it. */ + val &= ~PARALLEL_TOGGLEMASK; + + /* these bits must be set. */ + g_assert_true (val & PARALLEL_LOCKMASK); + g_assert_true (val & PARALLEL_SETMASK); + + /* If we fetch again, we must get the same value. Nobody changes the + * value while we hold the lock, except for the toggle bit. */ + val2 = g_atomic_int_get (¶llel_atomic); + val2 &= ~PARALLEL_TOGGLEMASK; + g_assert_cmpint (val, ==, val2); + + count_all = (val & ~(PARALLEL_LOCKMASK | PARALLEL_SETMASK)); + + if (!TRUE) + g_print ("th[%d] : self-count=%d, all-count=%d\n", IDX, count_self, count_all); + + if ((g_random_int () % 5) == 0) + { + /* regular unlock without any changes. */ + g_bit_unlock (¶llel_atomic, PARALLEL_LOCKBIT); + continue; + } + + /* unlock-and-set with an increased counter. */ + new_val = MIN (count_all + 1, PARALLEL_MAX_COUNT_ALL); + if (g_random_boolean ()) + new_val |= PARALLEL_SETMASK; + if (g_random_boolean ()) + new_val |= PARALLEL_TOGGLEMASK; + + g_bit_unlock_and_set (¶llel_atomic, PARALLEL_LOCKBIT, new_val, ((new_val & PARALLEL_SETMASK) && g_random_boolean ()) ? 0 : PARALLEL_SETMASK); + + count_self++; + + if (count_self < PARALLEL_MAX_COUNT_SELF) + continue; + if (count_all < PARALLEL_MAX_COUNT_ALL) + continue; + + break; + } + + /* To indicate success, we return a pointer to ¶llel_atomic. */ + return ¶llel_atomic; +} + +static void +test_parallel (void) +{ + GThread *threads[PARALLEL_N_THREADS]; + gpointer ptr; + int i; + gint val; + + g_atomic_int_or (¶llel_atomic, PARALLEL_SETMASK); + + for (i = 0; i < PARALLEL_N_THREADS; i++) + { + threads[i] = g_thread_new ("bitlock-parallel", _test_parallel_run, GINT_TO_POINTER (i)); + } + + for (i = 0; i < PARALLEL_N_THREADS; i++) + { + ptr = g_thread_join (threads[i]); + g_assert_true (ptr == ¶llel_atomic); + + /* After we join the first thread, we already expect that the resulting + * atomic's counter is set to PARALLEL_MAX_COUNT_ALL. This stays until + * the end. */ + val = g_atomic_int_get (¶llel_atomic); + if (i == PARALLEL_N_THREADS - 1) + { + /* at last, the atomic must be unlocked. */ + g_assert_true (!(val & PARALLEL_LOCKMASK)); + } + val &= ~(PARALLEL_LOCKMASK | PARALLEL_TOGGLEMASK | PARALLEL_SETMASK); + g_assert_cmpint (val, ==, PARALLEL_MAX_COUNT_ALL); + } +} + int main (int argc, char **argv) { g_test_init (&argc, &argv, NULL); g_test_add_func ("/bitlock/performance/uncontended", test_bitlocks); + g_test_add_func ("/bitlock/performance/parallel", test_parallel); return g_test_run (); }