hjl / source-git / glibc

Forked from source-git/glibc 3 years ago
Clone

Blame nptl/pthread_once.c

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