(root)/
glib-2.79.0/
glib/
tests/
thread-pool.c
       1  /* Unit tests for GThreadPool
       2   * Copyright (C) 2020 Sebastian Dröge <sebastian@centricular.com>
       3   *
       4   * SPDX-License-Identifier: LicenseRef-old-glib-tests
       5   *
       6   * This work is provided "as is"; redistribution and modification
       7   * in whole or in part, in any medium, physical or electronic is
       8   * permitted without restriction.
       9   *
      10   * This work is distributed in the hope that it will be useful,
      11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
      12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
      13   *
      14   * In no event shall the authors or contributors be liable for any
      15   * direct, indirect, incidental, special, exemplary, or consequential
      16   * damages (including, but not limited to, procurement of substitute
      17   * goods or services; loss of use, data, or profits; or business
      18   * interruption) however caused and on any theory of liability, whether
      19   * in contract, strict liability, or tort (including negligence or
      20   * otherwise) arising in any way out of the use of this software, even
      21   * if advised of the possibility of such damage.
      22   */
      23  
      24  #include <config.h>
      25  
      26  #include <glib.h>
      27  
      28  typedef struct {
      29    GMutex mutex;
      30    GCond cond;
      31    gboolean signalled;
      32  } MutexCond;
      33  
      34  static void
      35  pool_func (gpointer data, gpointer user_data)
      36  {
      37    MutexCond *m = user_data;
      38  
      39    g_mutex_lock (&m->mutex);
      40    g_assert_false (m->signalled);
      41    g_assert_true (data == GUINT_TO_POINTER (123));
      42    m->signalled = TRUE;
      43    g_cond_signal (&m->cond);
      44    g_mutex_unlock (&m->mutex);
      45  }
      46  
      47  static void
      48  test_simple (gconstpointer shared)
      49  {
      50    GThreadPool *pool;
      51    GError *err = NULL;
      52    MutexCond m;
      53    gboolean success;
      54  
      55    g_mutex_init (&m.mutex);
      56    g_cond_init (&m.cond);
      57  
      58    if (GPOINTER_TO_INT (shared))
      59      {
      60        g_test_summary ("Tests that a shared, non-exclusive thread pool "
      61                        "generally works.");
      62        pool = g_thread_pool_new (pool_func, &m, -1, FALSE, &err);
      63      }
      64    else
      65      {
      66        g_test_summary ("Tests that an exclusive thread pool generally works.");
      67        pool = g_thread_pool_new (pool_func, &m, 2, TRUE, &err);
      68      }
      69    g_assert_no_error (err);
      70    g_assert_nonnull (pool);
      71  
      72    g_mutex_lock (&m.mutex);
      73    m.signalled = FALSE;
      74  
      75    success = g_thread_pool_push (pool, GUINT_TO_POINTER (123), &err);
      76    g_assert_no_error (err);
      77    g_assert_true (success);
      78  
      79    while (!m.signalled)
      80      g_cond_wait (&m.cond, &m.mutex);
      81    g_mutex_unlock (&m.mutex);
      82  
      83    g_thread_pool_free (pool, TRUE, TRUE);
      84  }
      85  
      86  static void
      87  dummy_pool_func (gpointer data, gpointer user_data)
      88  {
      89    g_assert_true (data == GUINT_TO_POINTER (123));
      90  }
      91  
      92  static void
      93  test_create_first_pool (gconstpointer shared_first)
      94  {
      95    GThreadPool *pool;
      96    GError *err = NULL;
      97    gboolean success;
      98  
      99    g_test_bug ("https://gitlab.gnome.org/GNOME/glib/issues/2012");
     100    if (GPOINTER_TO_INT (shared_first))
     101      {
     102        g_test_summary ("Tests that creating an exclusive pool after a "
     103                        "shared one works.");
     104      }
     105    else
     106      {
     107        g_test_summary ("Tests that creating a shared pool after an "
     108                        "exclusive one works.");
     109      }
     110  
     111    if (!g_test_subprocess ())
     112      {
     113        g_test_trap_subprocess (NULL, 0, G_TEST_SUBPROCESS_DEFAULT);
     114        g_test_trap_assert_passed ();
     115        return;
     116      }
     117  
     118    g_thread_pool_set_max_unused_threads (0);
     119  
     120    if (GPOINTER_TO_INT (shared_first))
     121      pool = g_thread_pool_new (dummy_pool_func, NULL, -1, FALSE, &err);
     122    else
     123      pool = g_thread_pool_new (dummy_pool_func, NULL, 2, TRUE, &err);
     124    g_assert_no_error (err);
     125    g_assert_nonnull (pool);
     126  
     127    success = g_thread_pool_push (pool, GUINT_TO_POINTER (123), &err);
     128    g_assert_no_error (err);
     129    g_assert_true (success);
     130  
     131    g_thread_pool_free (pool, TRUE, TRUE);
     132  
     133    if (GPOINTER_TO_INT (shared_first))
     134      pool = g_thread_pool_new (dummy_pool_func, NULL, 2, TRUE, &err);
     135    else
     136      pool = g_thread_pool_new (dummy_pool_func, NULL, -1, FALSE, &err);
     137    g_assert_no_error (err);
     138    g_assert_nonnull (pool);
     139  
     140    success = g_thread_pool_push (pool, GUINT_TO_POINTER (123), &err);
     141    g_assert_no_error (err);
     142    g_assert_true (success);
     143  
     144    g_thread_pool_free (pool, TRUE, TRUE);
     145  }
     146  
     147  typedef struct
     148  {
     149    GMutex mutex;  /* (owned) */
     150    GCond cond;  /* (owned) */
     151    gboolean threads_should_block;  /* protected by mutex, cond */
     152  
     153    guint n_jobs_started;  /* (atomic) */
     154    guint n_jobs_completed;  /* (atomic) */
     155    guint n_free_func_calls;  /* (atomic) */
     156  } TestThreadPoolFullData;
     157  
     158  static void
     159  full_thread_func (gpointer data,
     160                    gpointer user_data)
     161  {
     162    TestThreadPoolFullData *test_data = data;
     163  
     164    g_atomic_int_inc (&test_data->n_jobs_started);
     165  
     166    /* Make the thread block until told to stop blocking. */
     167    g_mutex_lock (&test_data->mutex);
     168    while (test_data->threads_should_block)
     169      g_cond_wait (&test_data->cond, &test_data->mutex);
     170    g_mutex_unlock (&test_data->mutex);
     171  
     172    g_atomic_int_inc (&test_data->n_jobs_completed);
     173  }
     174  
     175  static void
     176  free_func (gpointer user_data)
     177  {
     178    TestThreadPoolFullData *test_data = user_data;
     179  
     180    g_atomic_int_inc (&test_data->n_free_func_calls);
     181  }
     182  
     183  static void
     184  test_thread_pool_full (gconstpointer shared_first)
     185  {
     186    guint i;
     187  
     188    g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/121");
     189  
     190    g_thread_pool_set_max_unused_threads (0);
     191  
     192    /* Run the test twice, once with a shared pool and once with an exclusive one. */
     193    for (i = 0; i < 2; i++)
     194      {
     195        GThreadPool *pool;
     196        TestThreadPoolFullData test_data;
     197        GError *local_error = NULL;
     198        gboolean success;
     199        guint j;
     200  
     201        g_mutex_init (&test_data.mutex);
     202        g_cond_init (&test_data.cond);
     203        test_data.threads_should_block = TRUE;
     204        test_data.n_jobs_started = 0;
     205        test_data.n_jobs_completed = 0;
     206        test_data.n_free_func_calls = 0;
     207  
     208        /* Create a thread pool with only one worker thread. The pool can be
     209         * created in shared or exclusive mode. */
     210        pool = g_thread_pool_new_full (full_thread_func, &test_data, free_func,
     211                                       1, (i == 0),
     212                                       &local_error);
     213        g_assert_no_error (local_error);
     214        g_assert_nonnull (pool);
     215  
     216        /* Push two jobs into the pool. The first one will start executing and
     217         * will block, the second one will wait in the queue as there’s only one
     218         * worker thread. */
     219        for (j = 0; j < 2; j++)
     220          {
     221            success = g_thread_pool_push (pool, &test_data, &local_error);
     222            g_assert_no_error (local_error);
     223            g_assert_true (success);
     224          }
     225  
     226        /* Wait for the first job to start. */
     227        while (g_atomic_int_get (&test_data.n_jobs_started) == 0);
     228  
     229        /* Free the pool. This won’t actually free the queued second job yet, as
     230         * the thread pool hangs around until the executing first job has
     231         * completed. The first job will complete only once @threads_should_block
     232         * is unset. */
     233        g_thread_pool_free (pool, TRUE, FALSE);
     234  
     235        g_assert_cmpuint (g_atomic_int_get (&test_data.n_jobs_started), ==, 1);
     236        g_assert_cmpuint (g_atomic_int_get (&test_data.n_jobs_completed), ==, 0);
     237        g_assert_cmpuint (g_atomic_int_get (&test_data.n_free_func_calls), ==, 0);
     238  
     239        /* Unblock the job and allow the pool to be freed. */
     240        g_mutex_lock (&test_data.mutex);
     241        test_data.threads_should_block = FALSE;
     242        g_cond_signal (&test_data.cond);
     243        g_mutex_unlock (&test_data.mutex);
     244  
     245        /* Wait for the first job to complete before freeing the mutex and cond. */
     246        while (g_atomic_int_get (&test_data.n_jobs_completed) != 1 ||
     247               g_atomic_int_get (&test_data.n_free_func_calls) != 1);
     248  
     249        g_assert_cmpuint (g_atomic_int_get (&test_data.n_jobs_started), ==, 1);
     250        g_assert_cmpuint (g_atomic_int_get (&test_data.n_jobs_completed), ==, 1);
     251        g_assert_cmpuint (g_atomic_int_get (&test_data.n_free_func_calls), ==, 1);
     252  
     253        g_cond_clear (&test_data.cond);
     254        g_mutex_clear (&test_data.mutex);
     255      }
     256  }
     257  
     258  int
     259  main (int argc, char *argv[])
     260  {
     261    g_test_init (&argc, &argv, NULL);
     262  
     263    g_test_add_data_func ("/thread_pool/shared", GINT_TO_POINTER (TRUE), test_simple);
     264    g_test_add_data_func ("/thread_pool/exclusive", GINT_TO_POINTER (FALSE), test_simple);
     265    g_test_add_data_func ("/thread_pool/create_shared_after_exclusive", GINT_TO_POINTER (FALSE), test_create_first_pool);
     266    g_test_add_data_func ("/thread_pool/create_full", NULL, test_thread_pool_full);
     267    g_test_add_data_func ("/thread_pool/create_exclusive_after_shared", GINT_TO_POINTER (TRUE), test_create_first_pool);
     268  
     269    return g_test_run ();
     270  }