Blame nptl/pthread_once.c

Packit Service 82fcde
/* Copyright (C) 2003-2018 Free Software Foundation, Inc.
Packit Service 82fcde
   This file is part of the GNU C Library.
Packit Service 82fcde
   Contributed by Jakub Jelinek <jakub@redhat.com>, 2003.
Packit Service 82fcde
Packit Service 82fcde
   The GNU C Library is free software; you can redistribute it and/or
Packit Service 82fcde
   modify it under the terms of the GNU Lesser General Public
Packit Service 82fcde
   License as published by the Free Software Foundation; either
Packit Service 82fcde
   version 2.1 of the License, or (at your option) any later version.
Packit Service 82fcde
Packit Service 82fcde
   The GNU C Library is distributed in the hope that it will be useful,
Packit Service 82fcde
   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit Service 82fcde
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
Packit Service 82fcde
   Lesser General Public License for more details.
Packit Service 82fcde
Packit Service 82fcde
   You should have received a copy of the GNU Lesser General Public
Packit Service 82fcde
   License along with the GNU C Library; if not, see
Packit Service 82fcde
   <http://www.gnu.org/licenses/>.  */
Packit Service 82fcde
Packit Service 82fcde
#include "pthreadP.h"
Packit Service 82fcde
#include <futex-internal.h>
Packit Service 82fcde
#include <atomic.h>
Packit Service 82fcde
Packit Service 82fcde
Packit Service 82fcde
unsigned long int __fork_generation attribute_hidden;
Packit Service 82fcde
Packit Service 82fcde
Packit Service 82fcde
static void
Packit Service 82fcde
clear_once_control (void *arg)
Packit Service 82fcde
{
Packit Service 82fcde
  pthread_once_t *once_control = (pthread_once_t *) arg;
Packit Service 82fcde
Packit Service 82fcde
  /* Reset to the uninitialized state here.  We don't need a stronger memory
Packit Service 82fcde
     order because we do not need to make any other of our writes visible to
Packit Service 82fcde
     other threads that see this value: This function will be called if we
Packit Service 82fcde
     get interrupted (see __pthread_once), so all we need to relay to other
Packit Service 82fcde
     threads is the state being reset again.  */
Packit Service 82fcde
  atomic_store_relaxed (once_control, 0);
Packit Service 82fcde
  futex_wake ((unsigned int *) once_control, INT_MAX, FUTEX_PRIVATE);
Packit Service 82fcde
}
Packit Service 82fcde
Packit Service 82fcde
Packit Service 82fcde
/* This is similar to a lock implementation, but we distinguish between three
Packit Service 82fcde
   states: not yet initialized (0), initialization in progress
Packit Service 82fcde
   (__fork_generation | __PTHREAD_ONCE_INPROGRESS), and initialization
Packit Service 82fcde
   finished (__PTHREAD_ONCE_DONE); __fork_generation does not use the bits
Packit Service 82fcde
   that are used for __PTHREAD_ONCE_INPROGRESS and __PTHREAD_ONCE_DONE (which
Packit Service 82fcde
   is what __PTHREAD_ONCE_FORK_GEN_INCR is used for).  If in the first state,
Packit Service 82fcde
   threads will try to run the initialization by moving to the second state;
Packit Service 82fcde
   the first thread to do so via a CAS on once_control runs init_routine,
Packit Service 82fcde
   other threads block.
Packit Service 82fcde
   When forking the process, some threads can be interrupted during the second
Packit Service 82fcde
   state; they won't be present in the forked child, so we need to restart
Packit Service 82fcde
   initialization in the child.  To distinguish an in-progress initialization
Packit Service 82fcde
   from an interrupted initialization (in which case we need to reclaim the
Packit Service 82fcde
   lock), we look at the fork generation that's part of the second state: We
Packit Service 82fcde
   can reclaim iff it differs from the current fork generation.
Packit Service 82fcde
   XXX: This algorithm has an ABA issue on the fork generation: If an
Packit Service 82fcde
   initialization is interrupted, we then fork 2^30 times (30 bits of
Packit Service 82fcde
   once_control are used for the fork generation), and try to initialize
Packit Service 82fcde
   again, we can deadlock because we can't distinguish the in-progress and
Packit Service 82fcde
   interrupted cases anymore.
Packit Service 82fcde
   XXX: We split out this slow path because current compilers do not generate
Packit Service 82fcde
   as efficient code when the fast path in __pthread_once below is not in a
Packit Service 82fcde
   separate function.  */
Packit Service 82fcde
static int
Packit Service 82fcde
__attribute__ ((noinline))
Packit Service 82fcde
__pthread_once_slow (pthread_once_t *once_control, void (*init_routine) (void))
Packit Service 82fcde
{
Packit Service 82fcde
  while (1)
Packit Service 82fcde
    {
Packit Service 82fcde
      int val, newval;
Packit Service 82fcde
Packit Service 82fcde
      /* We need acquire memory order for this load because if the value
Packit Service 82fcde
         signals that initialization has finished, we need to see any
Packit Service 82fcde
         data modifications done during initialization.  */
Packit Service 82fcde
      val = atomic_load_acquire (once_control);
Packit Service 82fcde
      do
Packit Service 82fcde
	{
Packit Service 82fcde
	  /* Check if the initialization has already been done.  */
Packit Service 82fcde
	  if (__glibc_likely ((val & __PTHREAD_ONCE_DONE) != 0))
Packit Service 82fcde
	    return 0;
Packit Service 82fcde
Packit Service 82fcde
	  /* We try to set the state to in-progress and having the current
Packit Service 82fcde
	     fork generation.  We don't need atomic accesses for the fork
Packit Service 82fcde
	     generation because it's immutable in a particular process, and
Packit Service 82fcde
	     forked child processes start with a single thread that modified
Packit Service 82fcde
	     the generation.  */
Packit Service 82fcde
	  newval = __fork_generation | __PTHREAD_ONCE_INPROGRESS;
Packit Service 82fcde
	  /* We need acquire memory order here for the same reason as for the
Packit Service 82fcde
	     load from once_control above.  */
Packit Service 82fcde
	}
Packit Service 82fcde
      while (__glibc_unlikely (!atomic_compare_exchange_weak_acquire (
Packit Service 82fcde
	  once_control, &val, newval)));
Packit Service 82fcde
Packit Service 82fcde
      /* Check if another thread already runs the initializer.	*/
Packit Service 82fcde
      if ((val & __PTHREAD_ONCE_INPROGRESS) != 0)
Packit Service 82fcde
	{
Packit Service 82fcde
	  /* Check whether the initializer execution was interrupted by a
Packit Service 82fcde
	     fork.  We know that for both values, __PTHREAD_ONCE_INPROGRESS
Packit Service 82fcde
	     is set and __PTHREAD_ONCE_DONE is not.  */
Packit Service 82fcde
	  if (val == newval)
Packit Service 82fcde
	    {
Packit Service 82fcde
	      /* Same generation, some other thread was faster.  Wait and
Packit Service 82fcde
		 retry.  */
Packit Service 82fcde
	      futex_wait_simple ((unsigned int *) once_control,
Packit Service 82fcde
				 (unsigned int) newval, FUTEX_PRIVATE);
Packit Service 82fcde
	      continue;
Packit Service 82fcde
	    }
Packit Service 82fcde
	}
Packit Service 82fcde
Packit Service 82fcde
      /* This thread is the first here.  Do the initialization.
Packit Service 82fcde
	 Register a cleanup handler so that in case the thread gets
Packit Service 82fcde
	 interrupted the initialization can be restarted.  */
Packit Service 82fcde
      pthread_cleanup_push (clear_once_control, once_control);
Packit Service 82fcde
Packit Service 82fcde
      init_routine ();
Packit Service 82fcde
Packit Service 82fcde
      pthread_cleanup_pop (0);
Packit Service 82fcde
Packit Service 82fcde
Packit Service 82fcde
      /* Mark *once_control as having finished the initialization.  We need
Packit Service 82fcde
         release memory order here because we need to synchronize with other
Packit Service 82fcde
         threads that want to use the initialized data.  */
Packit Service 82fcde
      atomic_store_release (once_control, __PTHREAD_ONCE_DONE);
Packit Service 82fcde
Packit Service 82fcde
      /* Wake up all other threads.  */
Packit Service 82fcde
      futex_wake ((unsigned int *) once_control, INT_MAX, FUTEX_PRIVATE);
Packit Service 82fcde
      break;
Packit Service 82fcde
    }
Packit Service 82fcde
Packit Service 82fcde
  return 0;
Packit Service 82fcde
}
Packit Service 82fcde
Packit Service 82fcde
int
Packit Service 82fcde
__pthread_once (pthread_once_t *once_control, void (*init_routine) (void))
Packit Service 82fcde
{
Packit Service 82fcde
  /* Fast path.  See __pthread_once_slow.  */
Packit Service 82fcde
  int val;
Packit Service 82fcde
  val = atomic_load_acquire (once_control);
Packit Service 82fcde
  if (__glibc_likely ((val & __PTHREAD_ONCE_DONE) != 0))
Packit Service 82fcde
    return 0;
Packit Service 82fcde
  else
Packit Service 82fcde
    return __pthread_once_slow (once_control, init_routine);
Packit Service 82fcde
}
Packit Service 82fcde
weak_alias (__pthread_once, pthread_once)
Packit Service 82fcde
hidden_def (__pthread_once)