1/*
2 * Copyright 2008-2011, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Copyright 2023, Haiku, Inc. All rights reserved.
4 * Distributed under the terms of the MIT License.
5 */
6
7#include <pthread.h>
8#include <threads.h>
9
10#include <OS.h>
11#include <Debug.h>
12
13
14enum {
15	STATE_UNINITIALIZED	= -1,	// keep in sync with PTHREAD_ONCE_INIT
16	STATE_INITIALIZING	= -2,
17	STATE_SPINNING		= -3,
18	STATE_INITIALIZED	= -4
19};
20
21#if __cplusplus >= 201103L
22STATIC_ASSERT(((pthread_once_t)ONCE_FLAG_INIT).state == ((pthread_once_t)PTHREAD_ONCE_INIT).state);
23STATIC_ASSERT(((pthread_once_t)PTHREAD_ONCE_INIT).state == STATE_UNINITIALIZED);
24#endif
25
26
27/*!	Called when the thread performing the initialization function was canceled.
28
29	\param data Pointer to the \c pthread_once_t structure in question.
30*/
31static void
32init_function_canceled(void* data)
33{
34	pthread_once_t* onceControl = (pthread_once_t*)data;
35
36	// reset the control state to uninitialized
37	int32 value = atomic_get_and_set((int32*)&onceControl->state,
38			STATE_UNINITIALIZED);
39
40	// If someone has set a semaphore, delete it.
41	if (value >= 0)
42		delete_sem(value);
43}
44
45
46// #pragma mark -
47
48
49int
50pthread_once(pthread_once_t* onceControl, void (*initRoutine)(void))
51{
52	// Algorithm:
53	// The state goes through at most four states:
54	// STATE_UNINITIALIZED: The initial uninitialized state.
55	// STATE_INITIALIZING: Set by the first thread entering the function. It
56	// will call initRoutine.
57	// semaphore/STATE_SPINNING: Set by the second thread entering the function,
58	// when the first thread is still executing initRoutine. The normal case is
59	// that the thread manages to create a semaphore. This thread (and all
60	// following threads) will block on the semaphore until the first thread is
61	// done.
62	// STATE_INITIALIZED: Set by the first thread when it returns from
63	// initRoutine. All following threads will return right away.
64
65	while (true) {
66		int32 value = atomic_test_and_set((int32*)&onceControl->state,
67			STATE_INITIALIZING, STATE_UNINITIALIZED);
68
69		if (value == STATE_INITIALIZED)
70			return 0;
71
72		if (value == STATE_UNINITIALIZED) {
73			// we're the first -- perform the initialization
74			pthread_cleanup_push(&init_function_canceled, onceControl);
75			initRoutine();
76			pthread_cleanup_pop(false);
77
78			value = atomic_get_and_set((int32*)&onceControl->state,
79					STATE_INITIALIZED);
80
81			// If someone else is waiting, we need to delete the semaphore.
82			if (value >= 0)
83				delete_sem(value);
84
85			return 0;
86		}
87
88		if (value == STATE_INITIALIZING) {
89			// someone is initializing -- we need to create a semaphore we can
90			// wait on
91			sem_id semaphore = create_sem(0, "pthread once");
92			if (semaphore >= 0) {
93				// successfully created -- set it
94				value = atomic_test_and_set((int32*)&onceControl->state,
95					semaphore, STATE_INITIALIZING);
96				if (value == STATE_INITIALIZING)
97					value = semaphore;
98				else
99					delete_sem(semaphore);
100			} else {
101				// Failed to create the semaphore. Can only happen when the
102				// system runs out of semaphores, but we can still handle the
103				// situation gracefully by spinning.
104				value = atomic_test_and_set((int32*)&onceControl->state,
105					STATE_SPINNING, STATE_INITIALIZING);
106				if (value == STATE_INITIALIZING)
107					value = STATE_SPINNING;
108			}
109		}
110
111		if (value >= 0) {
112			// wait on the semaphore
113			while (acquire_sem(value) == B_INTERRUPTED);
114
115			return 0;
116		} else if (value == STATE_SPINNING) {
117			// out of semaphores -- spin
118			while (atomic_get((int32*)&onceControl->state) == STATE_SPINNING);
119		}
120	}
121}
122