/* POSIX once-only control. Copyright (C) 2019-2024 Free Software Foundation, Inc. This file 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. This file 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 this program. If not, see . */ /* Written by Bruno Haible , 2019. */ #include /* Specification. */ #include #if (defined _WIN32 && ! defined __CYGWIN__) && USE_WINDOWS_THREADS # include "windows-once.h" #endif #if (defined _WIN32 && ! defined __CYGWIN__) && USE_WINDOWS_THREADS /* Use Windows threads. */ int pthread_once (pthread_once_t *once_control, void (*initfunction) (void)) { glwthread_once (once_control, initfunction); return 0; } #elif HAVE_PTHREAD_H /* Provide workarounds for POSIX threads. */ # if defined __CYGWIN__ # include int pthread_once (pthread_once_t *once_control, void (*initfunction) (void)) { /* In this implementation, we reuse the type typedef struct { pthread_mutex_t mutex; int state; } pthread_once_t; #define PTHREAD_ONCE_INIT { PTHREAD_MUTEX_INITIALIZER, 0 } while assigning the following meaning to the state: state = ( << 16) + <1 if done> In other words: state = { unsigned int num_threads : 16; unsigned int done : 16; } */ struct actual_state { _Atomic unsigned short num_threads; /* done == 0: initial state done == 1: initfunction executed, lock still active done == 2: initfunction executed, lock no longer usable */ _Atomic unsigned short done; }; struct actual_state *state_p = (struct actual_state *) &once_control->state; /* This test is not necessary. It's only an optimization, to establish a fast path for the common case that the 'done' word is already > 0. */ if (state_p->done == 0) { /* Increment num_threads (atomically), to indicate that this thread will possibly take the lock. */ state_p->num_threads += 1; /* Test the 'done' word. */ if (state_p->done == 0) { /* The 'done' word is still zero. Now take the lock. */ pthread_mutex_lock (&once_control->mutex); /* Test the 'done' word again. */ if (state_p->done == 0) { /* Execute the initfunction. */ (*initfunction) (); /* Set the 'done' word to 1 (atomically). */ state_p->done = 1; } /* Now the 'done' word is 1. Release the lock. */ pthread_mutex_unlock (&once_control->mutex); } /* Here, done is > 0. */ /* Decrement num_threads (atomically). */ if ((state_p->num_threads -= 1) == 0) { /* num_threads is now zero, and done is > 0. No other thread will need to use the lock. We can therefore destroy the lock, to free resources. */ if (__sync_bool_compare_and_swap (&state_p->done, 1, 2)) pthread_mutex_destroy (&once_control->mutex); } } /* Proof of correctness: * num_threads is incremented and then decremented by some threads. Therefore, num_threads always stays >= 0, and is == 0 at the end. * The 'done' word, once > 0, stays > 0 (since it is never assigned 0). * The 'done' word is changed from == 0 to > 0 only while the lock is taken. Therefore, only the first thread that succeeds in taking the lock executes the initfunction and sets the 'done' word to a value > 0; the other threads that take the lock do no side effects between taking and releasing the lock. * The 'done' word does not change any more once it is 2. Therefore, it can be changed from 1 to 2 only once. * pthread_mutex_destroy gets invoked right after 'done' has been changed from 1 to 2. Therefore, pthread_mutex_destroy gets invoked only once. * After a moment where num_threads was 0 and done was > 0, no thread can reach the pthread_mutex_lock invocation. Proof: - At such a moment, no thread is in the code range between state_p->num_threads += 1 and state_p->num_threads -= 1 - After such a moment, some thread can increment num_threads, but from there they cannot reach the pthread_mutex_lock invocation, because the if (state_p->done == 0) test prevents that. * From this it follows that: - pthread_mutex_destroy cannot be executed while the lock is taken (because pthread_mutex_destroy is only executed after a moment where num_threads was 0 and done was > 0). - Once pthread_mutex_destroy has been executed, the lock is not used any more. */ return 0; } # endif #else /* Provide a dummy implementation for single-threaded applications. */ int pthread_once (pthread_once_t *once_control, void (*initfunction) (void)) { if (*once_control == 0) { *once_control = ~ 0; initfunction (); } return 0; } #endif