VirtualBox

source: vbox/trunk/src/VBox/Runtime/common/misc/once.cpp@ 106275

Last change on this file since 106275 was 106061, checked in by vboxsync, 3 months ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 15.2 KB
Line 
1/* $Id: once.cpp 106061 2024-09-16 14:03:52Z vboxsync $ */
2/** @file
3 * IPRT - Execute Once.
4 */
5
6/*
7 * Copyright (C) 2007-2024 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * The contents of this file may alternatively be used under the terms
26 * of the Common Development and Distribution License Version 1.0
27 * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
28 * in the VirtualBox distribution, in which case the provisions of the
29 * CDDL are applicable instead of those of the GPL.
30 *
31 * You may elect to license modified versions of this file under the
32 * terms and conditions of either the GPL or the CDDL or both.
33 *
34 * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
35 */
36
37
38/*********************************************************************************************************************************
39* Header Files *
40*********************************************************************************************************************************/
41#include <iprt/once.h>
42#include "internal/iprt.h"
43
44#include <iprt/asm.h>
45#include <iprt/assert.h>
46#ifdef IN_RING3
47# include <iprt/critsect.h>
48# define RTONCE_USE_CRITSECT_FOR_TERM
49#elif defined(IN_RING0)
50# include <iprt/spinlock.h>
51# define RTONCE_USE_SPINLOCK_FOR_TERM
52#else
53# define RTONCE_NO_TERM
54#endif
55#include <iprt/err.h>
56#include <iprt/initterm.h>
57#include <iprt/semaphore.h>
58#include <iprt/thread.h>
59
60
61/*********************************************************************************************************************************
62* Global Variables *
63*********************************************************************************************************************************/
64#ifndef RTONCE_NO_TERM
65/** For initializing the clean-up list code. */
66static RTONCE g_OnceCleanUp = RTONCE_INITIALIZER;
67/** Lock protecting the clean-up list. */
68#ifdef RTONCE_USE_CRITSECT_FOR_TERM
69static RTCRITSECT g_CleanUpCritSect;
70#else
71static RTSEMFASTMUTEX g_hCleanUpLock;
72#endif
73/** The clean-up list. */
74static RTLISTANCHOR g_CleanUpList;
75
76/** Locks the clean-up list. */
77#ifdef RTONCE_USE_CRITSECT_FOR_TERM
78# define RTONCE_CLEANUP_LOCK() RTCritSectEnter(&g_CleanUpCritSect)
79#else
80# define RTONCE_CLEANUP_LOCK() RTSemFastMutexRequest(g_hCleanUpLock);
81#endif
82
83/** Unlocks the clean-up list. */
84#ifdef RTONCE_USE_CRITSECT_FOR_TERM
85# define RTONCE_CLEANUP_UNLOCK() RTCritSectLeave(&g_CleanUpCritSect);
86#else
87# define RTONCE_CLEANUP_UNLOCK() RTSemFastMutexRelease(g_hCleanUpLock);
88#endif
89
90
91
92/** @callback_method_impl{FNRTTERMCALLBACK} */
93static DECLCALLBACK(void) rtOnceTermCallback(RTTERMREASON enmReason, int32_t iStatus, void *pvUser)
94{
95 bool const fLazyCleanUpOk = RTTERMREASON_IS_LAZY_CLEANUP_OK(enmReason);
96 RTONCE_CLEANUP_LOCK(); /* Potentially dangerous. */
97
98 PRTONCE pCur, pPrev;
99 RTListForEachReverseSafe(&g_CleanUpList, pCur, pPrev, RTONCE, CleanUpNode)
100 {
101 /*
102 * Mostly reset it before doing the callback.
103 *
104 * Should probably introduce some new states here, but I'm not sure
105 * it's really worth it at this point.
106 */
107 PFNRTONCECLEANUP pfnCleanUp = pCur->pfnCleanUp;
108 void *pvUserCleanUp = pCur->pvUser;
109 pCur->pvUser = NULL;
110 pCur->pfnCleanUp = NULL;
111 ASMAtomicWriteS32(&pCur->rc, VERR_WRONG_ORDER);
112
113 pfnCleanUp(pvUserCleanUp, fLazyCleanUpOk);
114
115 /*
116 * Reset the reset of the state if we're being unloaded or smth.
117 */
118 if (!fLazyCleanUpOk)
119 {
120 ASMAtomicWriteS32(&pCur->rc, VERR_INTERNAL_ERROR);
121 ASMAtomicWriteS32(&pCur->iState, RTONCESTATE_UNINITIALIZED);
122 }
123 }
124
125 RTONCE_CLEANUP_UNLOCK();
126
127 /*
128 * Reset our own structure and the critsect / mutex.
129 */
130 if (!fLazyCleanUpOk)
131 {
132# ifdef RTONCE_USE_CRITSECT_FOR_TERM
133 RTCritSectDelete(&g_CleanUpCritSect);
134# else
135 RTSemFastMutexDestroy(g_hCleanUpLock);
136 g_hCleanUpLock = NIL_RTSEMFASTMUTEX;
137# endif
138
139 ASMAtomicWriteS32(&g_OnceCleanUp.rc, VERR_INTERNAL_ERROR);
140 ASMAtomicWriteS32(&g_OnceCleanUp.iState, RTONCESTATE_UNINITIALIZED);
141 }
142
143 NOREF(pvUser); NOREF(iStatus);
144}
145
146
147
148/**
149 * Initializes the globals (using RTOnce).
150 *
151 * @returns IPRT status code
152 * @param pvUser Unused.
153 */
154static DECLCALLBACK(int32_t) rtOnceInitCleanUp(void *pvUser)
155{
156 NOREF(pvUser);
157 RTListInit(&g_CleanUpList);
158# ifdef RTONCE_USE_CRITSECT_FOR_TERM
159 int rc = RTCritSectInit(&g_CleanUpCritSect);
160# else
161 int rc = RTSemFastMutexCreate(&g_hCleanUpLock);
162# endif
163 if (RT_SUCCESS(rc))
164 {
165 rc = RTTermRegisterCallback(rtOnceTermCallback, NULL);
166 if (RT_SUCCESS(rc))
167 return rc;
168
169# ifdef RTONCE_USE_CRITSECT_FOR_TERM
170 RTCritSectDelete(&g_CleanUpCritSect);
171# else
172 RTSemFastMutexDestroy(g_hCleanUpLock);
173 g_hCleanUpLock = NIL_RTSEMFASTMUTEX;
174# endif
175 }
176 return rc;
177}
178
179#endif /* !RTONCE_NO_TERM */
180
181/**
182 * The state loop of the other threads.
183 *
184 * @returns VINF_SUCCESS when everything went smoothly. IPRT status code if we
185 * encountered trouble.
186 * @param pOnce The execute once structure.
187 * @param phEvtM Where to store the semaphore handle so the caller
188 * can do the cleaning up for us.
189 */
190static int rtOnceOtherThread(PRTONCE pOnce, PRTSEMEVENTMULTI phEvtM)
191{
192 uint32_t cYields = 0;
193 for (;;)
194 {
195 int32_t iState = ASMAtomicReadS32(&pOnce->iState);
196 switch (iState)
197 {
198 /*
199 * No semaphore, try create one.
200 */
201 case RTONCESTATE_BUSY_NO_SEM:
202 if (ASMAtomicCmpXchgS32(&pOnce->iState, RTONCESTATE_BUSY_CREATING_SEM, RTONCESTATE_BUSY_NO_SEM))
203 {
204 int rc = RTSemEventMultiCreate(phEvtM);
205 if (RT_SUCCESS(rc))
206 {
207 ASMAtomicWriteHandle(&pOnce->hEventMulti, *phEvtM);
208 int32_t cRefs = ASMAtomicIncS32(&pOnce->cEventRefs); Assert(cRefs == 1); NOREF(cRefs);
209
210 if (!ASMAtomicCmpXchgS32(&pOnce->iState, RTONCESTATE_BUSY_HAVE_SEM, RTONCESTATE_BUSY_CREATING_SEM))
211 {
212 /* Too slow. */
213 AssertReturn(ASMAtomicCmpXchgS32(&pOnce->iState, RTONCESTATE_DONE, RTONCESTATE_DONE_CREATING_SEM)
214 , VERR_INTERNAL_ERROR_5);
215
216 ASMAtomicWriteHandle(&pOnce->hEventMulti, NIL_RTSEMEVENTMULTI);
217 cRefs = ASMAtomicDecS32(&pOnce->cEventRefs); Assert(cRefs == 0);
218
219 RTSemEventMultiDestroy(*phEvtM);
220 *phEvtM = NIL_RTSEMEVENTMULTI;
221 }
222 }
223 else
224 {
225 AssertReturn( ASMAtomicCmpXchgS32(&pOnce->iState, RTONCESTATE_BUSY_SPIN, RTONCESTATE_BUSY_CREATING_SEM)
226 || ASMAtomicCmpXchgS32(&pOnce->iState, RTONCESTATE_DONE, RTONCESTATE_DONE_CREATING_SEM)
227 , VERR_INTERNAL_ERROR_4);
228 *phEvtM = NIL_RTSEMEVENTMULTI;
229 }
230 }
231 break;
232
233 /*
234 * This isn't nice, but it's the easy way out.
235 */
236 case RTONCESTATE_BUSY_CREATING_SEM:
237 case RTONCESTATE_BUSY_SPIN:
238 cYields++;
239 if (!(++cYields % 8))
240 RTThreadSleep(1);
241 else
242 RTThreadYield();
243 break;
244
245 /*
246 * There is a semaphore, try wait on it.
247 *
248 * We continue waiting after reaching DONE_HAVE_SEM if we
249 * already got the semaphore to avoid racing the first thread.
250 */
251 case RTONCESTATE_DONE_HAVE_SEM:
252 if (*phEvtM == NIL_RTSEMEVENTMULTI)
253 return VINF_SUCCESS;
254 RT_FALL_THRU();
255 case RTONCESTATE_BUSY_HAVE_SEM:
256 {
257 /*
258 * Grab the semaphore if we haven't got it yet.
259 * We must take care not to increment the counter if it
260 * is 0. This may happen if we're racing a state change.
261 */
262 if (*phEvtM == NIL_RTSEMEVENTMULTI)
263 {
264 int32_t cEventRefs = ASMAtomicUoReadS32(&pOnce->cEventRefs);
265 while ( cEventRefs > 0
266 && ASMAtomicUoReadS32(&pOnce->iState) == RTONCESTATE_BUSY_HAVE_SEM)
267 {
268 if (ASMAtomicCmpXchgExS32(&pOnce->cEventRefs, cEventRefs + 1, cEventRefs, &cEventRefs))
269 break;
270 ASMNopPause();
271 }
272 if (cEventRefs <= 0)
273 break;
274
275 ASMAtomicReadHandle(&pOnce->hEventMulti, phEvtM);
276 AssertReturn(*phEvtM != NIL_RTSEMEVENTMULTI, VERR_INTERNAL_ERROR_2);
277 }
278
279 /*
280 * We've got a sempahore, do the actual waiting.
281 */
282 do
283 RTSemEventMultiWaitNoResume(*phEvtM, RT_INDEFINITE_WAIT);
284 while (ASMAtomicReadS32(&pOnce->iState) == RTONCESTATE_BUSY_HAVE_SEM);
285 break;
286 }
287
288 case RTONCESTATE_DONE_CREATING_SEM:
289 case RTONCESTATE_DONE:
290 return VINF_SUCCESS;
291
292 default:
293 AssertMsgFailedReturn(("%d\n", iState), VERR_INTERNAL_ERROR_3);
294 }
295 }
296}
297
298
299RTDECL(int) RTOnceSlow(PRTONCE pOnce, PFNRTONCE pfnOnce, PFNRTONCECLEANUP pfnCleanUp, void *pvUser)
300{
301 /*
302 * Validate input (strict builds only).
303 */
304 AssertPtr(pOnce);
305 AssertPtr(pfnOnce);
306
307 /*
308 * Deal with the 'initialized' case first
309 */
310 int32_t iState = ASMAtomicUoReadS32(&pOnce->iState);
311 if (RT_LIKELY( iState == RTONCESTATE_DONE
312 || iState == RTONCESTATE_DONE_CREATING_SEM
313 || iState == RTONCESTATE_DONE_HAVE_SEM
314 ))
315 return ASMAtomicUoReadS32(&pOnce->rc);
316
317 AssertReturn( iState == RTONCESTATE_UNINITIALIZED
318 || iState == RTONCESTATE_BUSY_NO_SEM
319 || iState == RTONCESTATE_BUSY_SPIN
320 || iState == RTONCESTATE_BUSY_CREATING_SEM
321 || iState == RTONCESTATE_BUSY_HAVE_SEM
322 , VERR_INTERNAL_ERROR);
323
324#ifdef RTONCE_NO_TERM
325 AssertReturn(!pfnCleanUp, VERR_NOT_SUPPORTED);
326#else /* !RTONCE_NO_TERM */
327
328 /*
329 * Make sure our clean-up bits are working if needed later.
330 */
331 if (pfnCleanUp)
332 {
333 int rc = RTOnce(&g_OnceCleanUp, rtOnceInitCleanUp, NULL);
334 if (RT_FAILURE(rc))
335 return rc;
336 }
337#endif /* !RTONCE_NO_TERM */
338
339 /*
340 * Do we initialize it?
341 */
342 int32_t rcOnce;
343 if ( iState == RTONCESTATE_UNINITIALIZED
344 && ASMAtomicCmpXchgS32(&pOnce->iState, RTONCESTATE_BUSY_NO_SEM, RTONCESTATE_UNINITIALIZED))
345 {
346 /*
347 * Yes, so do the execute once stuff.
348 */
349 rcOnce = pfnOnce(pvUser);
350 ASMAtomicWriteS32(&pOnce->rc, rcOnce);
351
352#ifndef RTONCE_NO_TERM
353 /*
354 * Register clean-up if requested and we were successful.
355 */
356 if (pfnCleanUp && RT_SUCCESS(rcOnce))
357 {
358 RTONCE_CLEANUP_LOCK();
359
360 pOnce->pfnCleanUp = pfnCleanUp;
361 pOnce->pvUser = pvUser;
362 RTListAppend(&g_CleanUpList, &pOnce->CleanUpNode);
363
364 RTONCE_CLEANUP_UNLOCK();
365 }
366#endif /* !RTONCE_NO_TERM */
367
368 /*
369 * If there is a sempahore to signal, we're in for some extra work here.
370 */
371 if ( !ASMAtomicCmpXchgS32(&pOnce->iState, RTONCESTATE_DONE, RTONCESTATE_BUSY_NO_SEM)
372 && !ASMAtomicCmpXchgS32(&pOnce->iState, RTONCESTATE_DONE, RTONCESTATE_BUSY_SPIN)
373 && !ASMAtomicCmpXchgS32(&pOnce->iState, RTONCESTATE_DONE_CREATING_SEM, RTONCESTATE_BUSY_CREATING_SEM)
374 )
375 {
376 /* Grab the sempahore by switching to 'DONE_HAVE_SEM' before reaching 'DONE'. */
377 AssertReturn(ASMAtomicCmpXchgS32(&pOnce->iState, RTONCESTATE_DONE_HAVE_SEM, RTONCESTATE_BUSY_HAVE_SEM),
378 VERR_INTERNAL_ERROR_2);
379
380 int32_t cRefs = ASMAtomicIncS32(&pOnce->cEventRefs);
381 Assert(cRefs > 1); NOREF(cRefs);
382
383 RTSEMEVENTMULTI hEvtM;
384 ASMAtomicReadHandle(&pOnce->hEventMulti, &hEvtM);
385 Assert(hEvtM != NIL_RTSEMEVENTMULTI);
386
387 ASMAtomicWriteS32(&pOnce->iState, RTONCESTATE_DONE);
388
389 /* Signal it and return. */
390 RTSemEventMultiSignal(hEvtM);
391 }
392 }
393 else
394 {
395 /*
396 * Wait for the first thread to complete. Delegate this to a helper
397 * function to simplify cleanup and keep things a bit shorter.
398 */
399 RTSEMEVENTMULTI hEvtM = NIL_RTSEMEVENTMULTI;
400 rcOnce = rtOnceOtherThread(pOnce, &hEvtM);
401 if (hEvtM != NIL_RTSEMEVENTMULTI)
402 {
403 if (ASMAtomicDecS32(&pOnce->cEventRefs) == 0)
404 {
405 bool fRc;
406 ASMAtomicCmpXchgHandle(&pOnce->hEventMulti, NIL_RTSEMEVENTMULTI, hEvtM, fRc); Assert(fRc);
407 fRc = ASMAtomicCmpXchgS32(&pOnce->iState, RTONCESTATE_DONE, RTONCESTATE_DONE_HAVE_SEM); Assert(fRc);
408 RTSemEventMultiDestroy(hEvtM);
409 }
410 }
411 if (RT_SUCCESS(rcOnce))
412 rcOnce = ASMAtomicUoReadS32(&pOnce->rc);
413 }
414
415 return rcOnce;
416}
417RT_EXPORT_SYMBOL(RTOnceSlow);
418
419
420RTDECL(void) RTOnceReset(PRTONCE pOnce)
421{
422 /* Cannot be done while busy! */
423 AssertPtr(pOnce);
424 Assert(pOnce->hEventMulti == NIL_RTSEMEVENTMULTI);
425 int32_t iState = ASMAtomicUoReadS32(&pOnce->iState);
426 AssertMsg( iState == RTONCESTATE_DONE
427 || iState == RTONCESTATE_UNINITIALIZED,
428 ("%d\n", iState));
429 NOREF(iState);
430
431#ifndef RTONCE_NO_TERM
432 /* Unregister clean-up. */
433 if (pOnce->pfnCleanUp)
434 {
435 RTONCE_CLEANUP_LOCK();
436
437 RTListNodeRemove(&pOnce->CleanUpNode);
438 pOnce->pfnCleanUp = NULL;
439 pOnce->pvUser = NULL;
440
441 RTONCE_CLEANUP_UNLOCK();
442 }
443#endif /* !RTONCE_NO_TERM */
444
445 /* Do the same as RTONCE_INITIALIZER does. */
446 ASMAtomicWriteS32(&pOnce->rc, VERR_INTERNAL_ERROR);
447 ASMAtomicWriteS32(&pOnce->iState, RTONCESTATE_UNINITIALIZED);
448}
449RT_EXPORT_SYMBOL(RTOnceReset);
450
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette