xref: /haiku/src/system/libroot/posix/pthread/pthread_once.cpp (revision 3b07762c548ec4016dea480d1061577cd15ec614)
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_get_and_set((int32*)&onceControl->state,
30 			STATE_UNINITIALIZED);
31 
32 	// If someone has set a semaphore, delete it.
33 	if (value >= 0)
34 		delete_sem(value);
35 }
36 
37 
38 // #pragma mark -
39 
40 
41 int
42 pthread_once(pthread_once_t* onceControl, void (*initRoutine)(void))
43 {
44 	// Algorithm:
45 	// The state goes through at most four states:
46 	// STATE_UNINITIALIZED: The initial uninitialized state.
47 	// STATE_INITIALIZING: Set by the first thread entering the function. It
48 	// will call initRoutine.
49 	// semaphore/STATE_SPINNING: Set by the second thread entering the function,
50 	// when the first thread is still executing initRoutine. The normal case is
51 	// that the thread manages to create a semaphore. This thread (and all
52 	// following threads) will block on the semaphore until the first thread is
53 	// done.
54 	// STATE_INITIALIZED: Set by the first thread when it returns from
55 	// initRoutine. All following threads will return right away.
56 
57 	while (true) {
58 		int32 value = atomic_test_and_set((int32*)&onceControl->state,
59 			STATE_INITIALIZING, STATE_UNINITIALIZED);
60 
61 		if (value == STATE_INITIALIZED)
62 			return 0;
63 
64 		if (value == STATE_UNINITIALIZED) {
65 			// we're the first -- perform the initialization
66 			pthread_cleanup_push(&init_function_canceled, onceControl);
67 			initRoutine();
68 			pthread_cleanup_pop(false);
69 
70 			value = atomic_get_and_set((int32*)&onceControl->state,
71 					STATE_INITIALIZED);
72 
73 			// If someone else is waiting, we need to delete the semaphore.
74 			if (value >= 0)
75 				delete_sem(value);
76 
77 			return 0;
78 		}
79 
80 		if (value == STATE_INITIALIZING) {
81 			// someone is initializing -- we need to create a semaphore we can
82 			// wait on
83 			sem_id semaphore = create_sem(0, "pthread once");
84 			if (semaphore >= 0) {
85 				// successfully created -- set it
86 				value = atomic_test_and_set((int32*)&onceControl->state,
87 					semaphore, STATE_INITIALIZING);
88 				if (value == STATE_INITIALIZING)
89 					value = semaphore;
90 				else
91 					delete_sem(semaphore);
92 			} else {
93 				// Failed to create the semaphore. Can only happen when the
94 				// system runs out of semaphores, but we can still handle the
95 				// situation gracefully by spinning.
96 				value = atomic_test_and_set((int32*)&onceControl->state,
97 					STATE_SPINNING, STATE_INITIALIZING);
98 				if (value == STATE_INITIALIZING)
99 					value = STATE_SPINNING;
100 			}
101 		}
102 
103 		if (value >= 0) {
104 			// wait on the semaphore
105 			while (acquire_sem(value) == B_INTERRUPTED);
106 
107 			return 0;
108 		} else if (value == STATE_SPINNING) {
109 			// out of semaphores -- spin
110 			while (atomic_get((int32*)&onceControl->state) == STATE_SPINNING);
111 		}
112 	}
113 }
114