Add multithreaded test of sem_getvalue

Arjun Shankar arjun@redhat.com
Fri Nov 22 14:03:05 GMT 2024


Hi Joseph,

This looks good to me. I've noted a very minor nitpick below but I
actually don't think it's worth a v2 at all.

Reviewed-by: Arjun Shankar <arjun@redhat.com>

Review below:

> Test coverage of sem_getvalue is fairly limited.  Add a test that runs
> it on threads on each CPU.  For this purpose I adapted
> tst-skeleton-thread-affinity.c; it didn't seem very suitable to use
> as-is or include directly in a different test doing things per-CPU,
> but did seem a suitable starting point (thus sharing
> tst-skeleton-affinity.c) for such testing.
>
> Tested for x86_64.

OK.

> diff --git a/sysdeps/unix/sysv/linux/Makefile b/sysdeps/unix/sysv/linux/Makefile
> index 527c7a5ae8..eb9c697ce5 100644
> --- a/sysdeps/unix/sysv/linux/Makefile
> +++ b/sysdeps/unix/sysv/linux/Makefile
> @@ -668,6 +668,7 @@ ifeq ($(subdir),nptl)
>  tests += \
>    tst-align-clone \
>    tst-getpid1 \
> +  tst-sem_getvalue-affinity \
>    # tests
>
>  # tst-rseq-nptl is an internal test because it requires a definition of

OK. Add the new test.

> diff --git a/sysdeps/unix/sysv/linux/tst-sem_getvalue-affinity.c b/sysdeps/unix/sysv/linux/tst-sem_getvalue-affinity.c
> new file mode 100644
> index 0000000000..4176f67533
> --- /dev/null
> +++ b/sysdeps/unix/sysv/linux/tst-sem_getvalue-affinity.c
> @@ -0,0 +1,185 @@
> +/* Test sem_getvalue across CPUs.  Based on tst-skeleton-thread-affinity.c.

OK.

> +   Copyright (C) 2015-2024 Free Software Foundation, Inc.

OK. Copyright line from the skeleton that this file is based on.

> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C 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.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <https://www.gnu.org/licenses/>.  */
> +
> +#include <errno.h>
> +#include <pthread.h>
> +#include <semaphore.h>
> +#include <stdbool.h>
> +#include <stdlib.h>
> +#include <support/xthread.h>
> +#include <sys/time.h>
> +
> +struct conf;
> +static bool early_test (struct conf *);

OK. You declare this function here because you define it after
including the skeleton that uses it.

> +
> +static int
> +setaffinity (size_t size, const cpu_set_t *set)
> +{
> +  int ret = pthread_setaffinity_np (pthread_self (), size, set);
> +  if (ret != 0)
> +    {
> +      errno = ret;
> +      return -1;
> +    }
> +  return 0;
> +}

OK.

> +
> +static int
> +getaffinity (size_t size, cpu_set_t *set)
> +{
> +  int ret = pthread_getaffinity_np (pthread_self (), size, set);
> +  if (ret != 0)
> +    {
> +      errno = ret;
> +      return -1;
> +    }
> +  return 0;
> +}

OK.

> +
> +#include "tst-skeleton-affinity.c"

OK. This skeleton uses the above two set/get functions along with the
setup to run a number of tests reading and manipulating CPU affinity
at various set sizes.

> +
> +static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

OK.

> +static sem_t sem;

OK.

> +
> +static void *
> +tf (void *arg)

OK. This is a thread function...

> +{
> +  void *ret = NULL;
> +  xpthread_mutex_lock (&lock);

OK. We take a lock. As I read more, I expect I'll figure out why.

> +  int semval;
> +  if (sem_getvalue (&sem, &semval) != 0)
> +    {
> +      printf ("sem_getvalue failed: %m\n");
> +      ret = (void *) 1;
> +    }
> +  else if (semval != 12345)
> +    {
> +      printf ("sem_getvalue returned %d not 12345\n", semval);
> +      ret = (void *) 1;
> +    }

OK. We call get_semvalue and expect it to be 12345. So the lock is
presumably to ensure that the semaphore value doesn't change during
this call.

> +  xpthread_mutex_unlock (&lock);
> +  return ret;
> +}

OK. We will unlock and return. We fail and return "1" if things don't work out.

> +
> +static int
> +stop_and_join_threads (struct conf *conf, cpu_set_t *set,
> +                      pthread_t *pinned_first, pthread_t *pinned_last)

OK. Looks like an end-of-test function.

> +{
> +  int failed = 0;
> +  for (pthread_t *p = pinned_first; p < pinned_last; ++p)

OK. Presumably we are operating on an array of pthread_t's. I'm noting
that the entry at "pinned_last" is not iterated through. I'll look out
for why. I expect it's one past the end of the array.

> +    {
> +      int cpu = p - pinned_first;

OK. This is an index into the array pointing to the current entry in
the pthread_t array, *and* a CPU number.

> +      if (!CPU_ISSET_S (cpu, CPU_ALLOC_SIZE (conf->set_size), set))
> +       continue;

OK. We skip this entry if the corresponding CPU is not in the set.

> +
> +      void *retval = (void *) 1;
> +      int ret = pthread_join (*p, &retval);

OK. Otherwise, we join on this thread.
Question: Was it necessary to set retval here first? I expect it's
*always* set by pthread_join anyway.

> +      if (ret != 0)
> +       {
> +         printf ("error: Failed to join thread %d: %s\n", cpu, strerror (ret));
> +         fflush (stdout);
> +         /* Cannot shut down cleanly with threads still running.  */
> +         abort ();
> +       }

OK. Error out if we can't join.

> +      if (retval != NULL)
> +       failed = 1;
> +    }
> +  return failed;
> +}

OK. Return 0 usually, but if any of the threads ended in error, we return 1.

> +
> +static bool
> +early_test (struct conf *conf)

OK. This is a setup function called by the skeleton before it starts
manipulating CPU affinity. This is actually the meat of this test.

> +{
> +  int ret;
> +  ret = sem_init (&sem, 0, 12345);

OK. The number I was looking for.

> +  if (ret != 0)
> +    {
> +      printf ("error: sem_init failed: %m\n");
> +      return false;
> +    }

OK.

> +  xpthread_mutex_lock (&lock);

OK. We take a lock.

> +  pthread_t *pinned_threads
> +    = calloc (conf->last_cpu + 1, sizeof (*pinned_threads));

OK. We allocate enough pthread_t's to account for CPUs counted from 0
to last_cpu (inclusive).
Nitpick: we have xcalloc. But maybe it's not worth a v2. We check for
a couple of failed allocations below anyway.

> +  cpu_set_t *initial_set = CPU_ALLOC (conf->set_size);
> +  cpu_set_t *scratch_set = CPU_ALLOC (conf->set_size);

OK.

> +
> +  if (pinned_threads == NULL || initial_set == NULL || scratch_set == NULL)
> +    {
> +      puts ("error: Memory allocation failure");
> +      return false;
> +    }

OK.

> +  if (getaffinity (CPU_ALLOC_SIZE (conf->set_size), initial_set) < 0)
> +    {
> +      printf ("error: pthread_getaffinity_np failed: %m\n");
> +      return false;
> +    }

OK.

> +
> +  pthread_attr_t attr;
> +  ret = pthread_attr_init (&attr);
> +  if (ret != 0)
> +    {
> +      printf ("error: pthread_attr_init failed: %s\n", strerror (ret));
> +      return false;
> +    }
> +  support_set_small_thread_stack_size (&attr);

OK.

> +
> +  /* Spawn a thread pinned to each available CPU.  */
> +  for (int cpu = 0; cpu <= conf->last_cpu; ++cpu)

OK. We did allocate enough space for all these pthread_t's.

> +    {
> +      if (!CPU_ISSET_S (cpu, CPU_ALLOC_SIZE (conf->set_size), initial_set))
> +       continue;

OK.

> +      CPU_ZERO_S (CPU_ALLOC_SIZE (conf->set_size), scratch_set);
> +      CPU_SET_S (cpu, CPU_ALLOC_SIZE (conf->set_size), scratch_set);

OK. Only have *this* CPU in the affinity set we are about to use
during thread creation.

> +      ret = pthread_attr_setaffinity_np
> +       (&attr, CPU_ALLOC_SIZE (conf->set_size), scratch_set);
> +      if (ret != 0)
> +       {
> +         printf ("error: pthread_attr_setaffinity_np for CPU %d failed: %s\n",
> +                 cpu, strerror (ret));
> +         stop_and_join_threads (conf, initial_set,
> +                                pinned_threads, pinned_threads + cpu);
> +         return false;
> +       }

OK.

> +      ret = pthread_create (pinned_threads + cpu, &attr,
> +                           tf, (void *) (uintptr_t) cpu);
> +      if (ret != 0)
> +       {
> +         printf ("error: pthread_create for CPU %d failed: %s\n",
> +                 cpu, strerror (ret));
> +         stop_and_join_threads (conf, initial_set,
> +                                pinned_threads, pinned_threads + cpu);
> +         return false;
> +       }

OK. Returning false from early_test leads to tests not being run and a
failure log.

> +    }

> +
> +  /* Main thread.  */
> +  xpthread_mutex_unlock (&lock);

OK. This lets all the threads kick off. Each will call sem_getvalue,
verify the result, and return.

> +  int failed = stop_and_join_threads (conf, initial_set,
> +                                     pinned_threads,
> +                                     pinned_threads + conf->last_cpu + 1);

OK. Wait for the threads to finish. Also, the +1 lines up with the
loop in stop_and_join_threads.

> +
> +  printf ("info: Main thread ran on %d CPU(s) of %d available CPU(s)\n",
> +         CPU_COUNT_S (CPU_ALLOC_SIZE (conf->set_size), scratch_set),
> +         CPU_COUNT_S (CPU_ALLOC_SIZE (conf->set_size), initial_set));
> +
> +  pthread_attr_destroy (&attr);
> +  CPU_FREE (scratch_set);
> +  CPU_FREE (initial_set);
> +  free (pinned_threads);
> +  return failed == 0;
> +}

OK. After this function completes execution, the skeleton will run a
bunch of affinity tests.

Cheers!
-- 
Arjun Shankar
he/him/his



More information about the Libc-alpha mailing list