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