⛏️ index : haiku.git

/*
 * Copyright 2008-2011, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Copyright 2023, Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 */

#include <pthread.h>
#include <threads.h>

#include <OS.h>
#include <Debug.h>


enum {
	STATE_UNINITIALIZED	= -1,	// keep in sync with PTHREAD_ONCE_INIT
	STATE_INITIALIZING	= -2,
	STATE_SPINNING		= -3,
	STATE_INITIALIZED	= -4
};

#if __cplusplus >= 201103L
STATIC_ASSERT(((pthread_once_t)ONCE_FLAG_INIT).state == ((pthread_once_t)PTHREAD_ONCE_INIT).state);
STATIC_ASSERT(((pthread_once_t)PTHREAD_ONCE_INIT).state == STATE_UNINITIALIZED);
#endif


/*!	Called when the thread performing the initialization function was canceled.

	\param data Pointer to the \c pthread_once_t structure in question.
*/
static void
init_function_canceled(void* data)
{
	pthread_once_t* onceControl = (pthread_once_t*)data;

	// reset the control state to uninitialized
	int32 value = atomic_get_and_set((int32*)&onceControl->state,
			STATE_UNINITIALIZED);

	// If someone has set a semaphore, delete it.
	if (value >= 0)
		delete_sem(value);
}


// #pragma mark -


int
pthread_once(pthread_once_t* onceControl, void (*initRoutine)(void))
{
	// Algorithm:
	// The state goes through at most four states:
	// STATE_UNINITIALIZED: The initial uninitialized state.
	// STATE_INITIALIZING: Set by the first thread entering the function. It
	// will call initRoutine.
	// semaphore/STATE_SPINNING: Set by the second thread entering the function,
	// when the first thread is still executing initRoutine. The normal case is
	// that the thread manages to create a semaphore. This thread (and all
	// following threads) will block on the semaphore until the first thread is
	// done.
	// STATE_INITIALIZED: Set by the first thread when it returns from
	// initRoutine. All following threads will return right away.

	while (true) {
		int32 value = atomic_test_and_set((int32*)&onceControl->state,
			STATE_INITIALIZING, STATE_UNINITIALIZED);

		if (value == STATE_INITIALIZED)
			return 0;

		if (value == STATE_UNINITIALIZED) {
			// we're the first -- perform the initialization
			pthread_cleanup_push(&init_function_canceled, onceControl);
			initRoutine();
			pthread_cleanup_pop(false);

			value = atomic_get_and_set((int32*)&onceControl->state,
					STATE_INITIALIZED);

			// If someone else is waiting, we need to delete the semaphore.
			if (value >= 0)
				delete_sem(value);

			return 0;
		}

		if (value == STATE_INITIALIZING) {
			// someone is initializing -- we need to create a semaphore we can
			// wait on
			sem_id semaphore = create_sem(0, "pthread once");
			if (semaphore >= 0) {
				// successfully created -- set it
				value = atomic_test_and_set((int32*)&onceControl->state,
					semaphore, STATE_INITIALIZING);
				if (value == STATE_INITIALIZING)
					value = semaphore;
				else
					delete_sem(semaphore);
			} else {
				// Failed to create the semaphore. Can only happen when the
				// system runs out of semaphores, but we can still handle the
				// situation gracefully by spinning.
				value = atomic_test_and_set((int32*)&onceControl->state,
					STATE_SPINNING, STATE_INITIALIZING);
				if (value == STATE_INITIALIZING)
					value = STATE_SPINNING;
			}
		}

		if (value >= 0) {
			// wait on the semaphore
			while (acquire_sem(value) == B_INTERRUPTED);

			return 0;
		} else if (value == STATE_SPINNING) {
			// out of semaphores -- spin
			while (atomic_get((int32*)&onceControl->state) == STATE_SPINNING);
		}
	}
}