(root)/
glibc-2.38/
sysdeps/
unix/
sysv/
linux/
tst-skeleton-thread-affinity.c
       1  /* Generic test for CPU affinity functions, multi-threaded variant.
       2     Copyright (C) 2015-2023 Free Software Foundation, Inc.
       3     This file is part of the GNU C Library.
       4  
       5     The GNU C Library is free software; you can redistribute it and/or
       6     modify it under the terms of the GNU Lesser General Public
       7     License as published by the Free Software Foundation; either
       8     version 2.1 of the License, or (at your option) any later version.
       9  
      10     The GNU C Library 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.  See the GNU
      13     Lesser General Public License for more details.
      14  
      15     You should have received a copy of the GNU Lesser General Public
      16     License along with the GNU C Library; if not, see
      17     <https://www.gnu.org/licenses/>.  */
      18  
      19  /* Before including this file, a test has to declare the helper
      20     getaffinity and setaffinity functions described in
      21     tst-skeleton-affinity.c, which is included below.  */
      22  
      23  #include <errno.h>
      24  #include <pthread.h>
      25  #include <stdbool.h>
      26  #include <stdlib.h>
      27  #include <support/xthread.h>
      28  #include <sys/time.h>
      29  
      30  struct conf;
      31  static bool early_test (struct conf *);
      32  
      33  /* Arbitrary run time for each pass.  */
      34  #define PASS_TIMEOUT 2
      35  
      36  /* There are two passes (one with sched_yield, one without), and we
      37     double the timeout to be on the safe side.  */
      38  #define TIMEOUT (2 * PASS_TIMEOUT * 2)
      39  
      40  #include "tst-skeleton-affinity.c"
      41  
      42  /* 0 if still running, 1 of stopping requested.  */
      43  static int still_running;
      44  
      45  /* 0 if no scheduling failures, 1 if failures are encountered.  */
      46  static int failed;
      47  
      48  static void *
      49  thread_burn_one_cpu (void *closure)
      50  {
      51    int cpu = (uintptr_t) closure;
      52    while (__atomic_load_n (&still_running, __ATOMIC_RELAXED) == 0)
      53      {
      54        int current = sched_getcpu ();
      55        if (sched_getcpu () != cpu)
      56  	{
      57  	  printf ("error: Pinned thread %d ran on impossible cpu %d\n",
      58  		  cpu, current);
      59  	  __atomic_store_n (&failed, 1, __ATOMIC_RELAXED);
      60  	  /* Terminate early.  */
      61  	  __atomic_store_n (&still_running, 1, __ATOMIC_RELAXED);
      62  	}
      63      }
      64    return NULL;
      65  }
      66  
      67  struct burn_thread
      68  {
      69    pthread_t self;
      70    struct conf *conf;
      71    cpu_set_t *initial_set;
      72    cpu_set_t *seen_set;
      73    int thread;
      74  };
      75  
      76  static void *
      77  thread_burn_any_cpu (void *closure)
      78  {
      79    struct burn_thread *param = closure;
      80  
      81    /* Schedule this thread around a bit to see if it lands on another
      82       CPU.  Run this for 2 seconds, once with sched_yield, once
      83       without.  */
      84    for (int pass = 1; pass <= 2; ++pass)
      85      {
      86        time_t start = time (NULL);
      87        while (time (NULL) - start <= PASS_TIMEOUT)
      88  	{
      89  	  int cpu = sched_getcpu ();
      90  	  if (cpu > param->conf->last_cpu
      91  	      || !CPU_ISSET_S (cpu, CPU_ALLOC_SIZE (param->conf->set_size),
      92  			       param->initial_set))
      93  	    {
      94  	      printf ("error: Unpinned thread %d ran on impossible CPU %d\n",
      95  		      param->thread, cpu);
      96  	      __atomic_store_n (&failed, 1, __ATOMIC_RELAXED);
      97  	      return NULL;
      98  	    }
      99  	  CPU_SET_S (cpu, CPU_ALLOC_SIZE (param->conf->set_size),
     100  		     param->seen_set);
     101  	  if (pass == 1)
     102  	    sched_yield ();
     103  	}
     104      }
     105    return NULL;
     106  }
     107  
     108  static void
     109  stop_and_join_threads (struct conf *conf, cpu_set_t *set,
     110  		       pthread_t *pinned_first, pthread_t *pinned_last,
     111  		       struct burn_thread *other_first,
     112  		       struct burn_thread *other_last)
     113  {
     114    __atomic_store_n (&still_running, 1, __ATOMIC_RELAXED);
     115    for (pthread_t *p = pinned_first; p < pinned_last; ++p)
     116      {
     117        int cpu = p - pinned_first;
     118        if (!CPU_ISSET_S (cpu, CPU_ALLOC_SIZE (conf->set_size), set))
     119  	continue;
     120  
     121        int ret = pthread_join (*p, NULL);
     122        if (ret != 0)
     123  	{
     124  	  printf ("error: Failed to join thread %d: %s\n", cpu, strerror (ret));
     125  	  fflush (stdout);
     126  	  /* Cannot shut down cleanly with threads still running.  */
     127  	  abort ();
     128  	}
     129      }
     130  
     131    for (struct burn_thread *p = other_first; p < other_last; ++p)
     132      {
     133        int cpu = p - other_first;
     134        if (!CPU_ISSET_S (cpu, CPU_ALLOC_SIZE (conf->set_size), set))
     135  	continue;
     136  
     137        int ret = pthread_join (p->self, NULL);
     138        if (ret != 0)
     139  	{
     140  	  printf ("error: Failed to join thread %d: %s\n", cpu, strerror (ret));
     141  	  fflush (stdout);
     142  	  /* Cannot shut down cleanly with threads still running.  */
     143  	  abort ();
     144  	}
     145      }
     146  }
     147  
     148  /* Tries to check that the initial set of CPUs is complete and that
     149     the main thread will not run on any other threads.  */
     150  static bool
     151  early_test (struct conf *conf)
     152  {
     153    pthread_t *pinned_threads
     154      = calloc (conf->last_cpu + 1, sizeof (*pinned_threads));
     155    struct burn_thread *other_threads
     156      = calloc (conf->last_cpu + 1, sizeof (*other_threads));
     157    cpu_set_t *initial_set = CPU_ALLOC (conf->set_size);
     158    cpu_set_t *scratch_set = CPU_ALLOC (conf->set_size);
     159  
     160    if (pinned_threads == NULL || other_threads == NULL
     161        || initial_set == NULL || scratch_set == NULL)
     162      {
     163        puts ("error: Memory allocation failure");
     164        return false;
     165      }
     166    if (getaffinity (CPU_ALLOC_SIZE (conf->set_size), initial_set) < 0)
     167      {
     168        printf ("error: pthread_getaffinity_np failed: %m\n");
     169        return false;
     170      }
     171    for (int cpu = 0; cpu <= conf->last_cpu; ++cpu)
     172      {
     173        if (!CPU_ISSET_S (cpu, CPU_ALLOC_SIZE (conf->set_size), initial_set))
     174  	continue;
     175        other_threads[cpu].conf = conf;
     176        other_threads[cpu].initial_set = initial_set;
     177        other_threads[cpu].thread = cpu;
     178        other_threads[cpu].seen_set = CPU_ALLOC (conf->set_size);
     179        if (other_threads[cpu].seen_set == NULL)
     180  	{
     181  	  puts ("error: Memory allocation failure");
     182  	  return false;
     183  	}
     184        CPU_ZERO_S (CPU_ALLOC_SIZE (conf->set_size),
     185  		  other_threads[cpu].seen_set);
     186      }
     187  
     188    pthread_attr_t attr;
     189    int ret = pthread_attr_init (&attr);
     190    if (ret != 0)
     191      {
     192        printf ("error: pthread_attr_init failed: %s\n", strerror (ret));
     193        return false;
     194      }
     195    support_set_small_thread_stack_size (&attr);
     196  
     197    /* Spawn a thread pinned to each available CPU.  */
     198    for (int cpu = 0; cpu <= conf->last_cpu; ++cpu)
     199      {
     200        if (!CPU_ISSET_S (cpu, CPU_ALLOC_SIZE (conf->set_size), initial_set))
     201  	continue;
     202        CPU_ZERO_S (CPU_ALLOC_SIZE (conf->set_size), scratch_set);
     203        CPU_SET_S (cpu, CPU_ALLOC_SIZE (conf->set_size), scratch_set);
     204        ret = pthread_attr_setaffinity_np
     205  	(&attr, CPU_ALLOC_SIZE (conf->set_size), scratch_set);
     206        if (ret != 0)
     207  	{
     208  	  printf ("error: pthread_attr_setaffinity_np for CPU %d failed: %s\n",
     209  		  cpu, strerror (ret));
     210  	  stop_and_join_threads (conf, initial_set,
     211  				 pinned_threads, pinned_threads + cpu,
     212  				 NULL, NULL);
     213  	  return false;
     214  	}
     215        ret = pthread_create (pinned_threads + cpu, &attr,
     216  			    thread_burn_one_cpu, (void *) (uintptr_t) cpu);
     217        if (ret != 0)
     218  	{
     219  	  printf ("error: pthread_create for CPU %d failed: %s\n",
     220  		  cpu, strerror (ret));
     221  	  stop_and_join_threads (conf, initial_set,
     222  				 pinned_threads, pinned_threads + cpu,
     223  				 NULL, NULL);
     224  	  return false;
     225  	}
     226      }
     227  
     228    /* Spawn another set of threads running on all CPUs.  */
     229    for (int cpu = 0; cpu <= conf->last_cpu; ++cpu)
     230      {
     231        if (!CPU_ISSET_S (cpu, CPU_ALLOC_SIZE (conf->set_size), initial_set))
     232  	continue;
     233        ret = pthread_create (&other_threads[cpu].self,
     234  			    support_small_stack_thread_attribute (),
     235  			    thread_burn_any_cpu, other_threads + cpu);
     236        if (ret != 0)
     237  	{
     238  	  printf ("error: pthread_create for thread %d failed: %s\n",
     239  		  cpu, strerror (ret));
     240  	  stop_and_join_threads (conf, initial_set,
     241  				 pinned_threads,
     242  				 pinned_threads + conf->last_cpu + 1,
     243  				 other_threads, other_threads + cpu);
     244  	  return false;
     245  	}
     246      }
     247  
     248    /* Main thread.  */
     249    struct burn_thread main_thread;
     250    main_thread.conf = conf;
     251    main_thread.initial_set = initial_set;
     252    main_thread.seen_set = scratch_set;
     253    main_thread.thread = -1;
     254    CPU_ZERO_S (CPU_ALLOC_SIZE (conf->set_size), main_thread.seen_set);
     255    thread_burn_any_cpu (&main_thread);
     256    stop_and_join_threads (conf, initial_set,
     257  			 pinned_threads,
     258  			 pinned_threads + conf->last_cpu + 1,
     259  			 other_threads, other_threads + conf->last_cpu + 1);
     260  
     261    printf ("info: Main thread ran on %d CPU(s) of %d available CPU(s)\n",
     262  	  CPU_COUNT_S (CPU_ALLOC_SIZE (conf->set_size), scratch_set),
     263  	  CPU_COUNT_S (CPU_ALLOC_SIZE (conf->set_size), initial_set));
     264    CPU_ZERO_S (CPU_ALLOC_SIZE (conf->set_size), scratch_set);
     265    for (int cpu = 0; cpu <= conf->last_cpu; ++cpu)
     266      {
     267        if (!CPU_ISSET_S (cpu, CPU_ALLOC_SIZE (conf->set_size), initial_set))
     268  	continue;
     269        CPU_OR_S (CPU_ALLOC_SIZE (conf->set_size),
     270  		scratch_set, scratch_set, other_threads[cpu].seen_set);
     271        CPU_FREE (other_threads[cpu].seen_set);
     272      }
     273    printf ("info: Other threads ran on %d CPU(s)\n",
     274  	  CPU_COUNT_S (CPU_ALLOC_SIZE (conf->set_size), scratch_set));;
     275  
     276  
     277    pthread_attr_destroy (&attr);
     278    CPU_FREE (scratch_set);
     279    CPU_FREE (initial_set);
     280    free (pinned_threads);
     281    free (other_threads);
     282    return failed == 0;
     283  }