xref: /haiku/src/system/libroot/posix/pthread/pthread_once.cpp (revision 7a74a5df454197933bc6e80a542102362ee98703)
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