| /* POSIX once-only control. | |
| Copyright (C) 2019-2026 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 <https://www.gnu.org/licenses/>. */ | |
| /* Written by Bruno Haible <bruno@clisp.org>, 2019. */ | |
| #include <config.h> | |
| /* Specification. */ | |
| #include <pthread.h> | |
| #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 <stdlib.h> | |
| 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 = (<number of waiting threads> << 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 |