VirtualBox

source: vbox/trunk/src/VBox/Main/src-all/AutoCaller.cpp

Last change on this file was 98103, checked in by vboxsync, 16 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: 17.5 KB
Line 
1/* $Id: AutoCaller.cpp 98103 2023-01-17 14:15:46Z vboxsync $ */
2/** @file
3 * VirtualBox object state implementation
4 */
5
6/*
7 * Copyright (C) 2006-2023 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 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28#define LOG_GROUP LOG_GROUP_MAIN
29#include <iprt/semaphore.h>
30
31#include "VirtualBoxBase.h"
32#include "AutoCaller.h"
33#include "LoggingNew.h"
34
35#include "VBoxNls.h"
36
37
38DECLARE_TRANSLATION_CONTEXT(AutoCallerCtx);
39
40////////////////////////////////////////////////////////////////////////////////
41//
42// ObjectState methods
43//
44////////////////////////////////////////////////////////////////////////////////
45
46
47ObjectState::ObjectState() : mStateLock(LOCKCLASS_OBJECTSTATE)
48{
49 AssertFailed();
50}
51
52ObjectState::ObjectState(VirtualBoxBase *aObj) :
53 mObj(aObj), mStateLock(LOCKCLASS_OBJECTSTATE)
54{
55 Assert(mObj);
56 mState = NotReady;
57 mStateChangeThread = NIL_RTTHREAD;
58 mCallers = 0;
59 mFailedRC = S_OK;
60 mpFailedEI = NULL;
61 mZeroCallersSem = NIL_RTSEMEVENT;
62 mInitUninitSem = NIL_RTSEMEVENTMULTI;
63 mInitUninitWaiters = 0;
64}
65
66ObjectState::~ObjectState()
67{
68 Assert(mInitUninitWaiters == 0);
69 Assert(mInitUninitSem == NIL_RTSEMEVENTMULTI);
70 if (mZeroCallersSem != NIL_RTSEMEVENT)
71 RTSemEventDestroy(mZeroCallersSem);
72 mCallers = 0;
73 mStateChangeThread = NIL_RTTHREAD;
74 mState = NotReady;
75 mFailedRC = S_OK;
76 if (mpFailedEI)
77 {
78 delete mpFailedEI;
79 mpFailedEI = NULL;
80 }
81 mObj = NULL;
82}
83
84ObjectState::State ObjectState::getState()
85{
86 AutoReadLock stateLock(mStateLock COMMA_LOCKVAL_SRC_POS);
87 return mState;
88}
89
90/**
91 * Increments the number of calls to this object by one.
92 *
93 * After this method succeeds, it is guaranteed that the object will remain
94 * in the Ready (or in the Limited) state at least until #releaseCaller() is
95 * called.
96 *
97 * This method is intended to mark the beginning of sections of code within
98 * methods of COM objects that depend on the readiness (Ready) state. The
99 * Ready state is a primary "ready to serve" state. Usually all code that
100 * works with component's data depends on it. On practice, this means that
101 * almost every public method, setter or getter of the object should add
102 * itself as an object's caller at the very beginning, to protect from an
103 * unexpected uninitialization that may happen on a different thread.
104 *
105 * Besides the Ready state denoting that the object is fully functional,
106 * there is a special Limited state. The Limited state means that the object
107 * is still functional, but its functionality is limited to some degree, so
108 * not all operations are possible. The @a aLimited argument to this method
109 * determines whether the caller represents this limited functionality or
110 * not.
111 *
112 * This method succeeds (and increments the number of callers) only if the
113 * current object's state is Ready. Otherwise, it will return E_ACCESSDENIED
114 * to indicate that the object is not operational. There are two exceptions
115 * from this rule:
116 * <ol>
117 * <li>If the @a aLimited argument is |true|, then this method will also
118 * succeed if the object's state is Limited (or Ready, of course).
119 * </li>
120 * <li>If this method is called from the same thread that placed
121 * the object to InInit or InUninit state (i.e. either from within the
122 * AutoInitSpan or AutoUninitSpan scope), it will succeed as well (but
123 * will not increase the number of callers).
124 * </li>
125 * </ol>
126 *
127 * Normally, calling addCaller() never blocks. However, if this method is
128 * called by a thread created from within the AutoInitSpan scope and this
129 * scope is still active (i.e. the object state is InInit), it will block
130 * until the AutoInitSpan destructor signals that it has finished
131 * initialization.
132 *
133 * When this method returns a failure, the caller must not use the object
134 * and should return the failed result code to its own caller.
135 *
136 * @param aLimited |true| to add a limited caller.
137 *
138 * @return S_OK on success or E_ACCESSDENIED on failure.
139 *
140 * @sa #releaseCaller()
141 */
142HRESULT ObjectState::addCaller(bool aLimited /* = false */)
143{
144 AutoWriteLock stateLock(mStateLock COMMA_LOCKVAL_SRC_POS);
145
146 HRESULT hrc = E_ACCESSDENIED;
147
148 if (mState == Ready || (aLimited && mState == Limited))
149 {
150 /* if Ready or allows Limited, increase the number of callers */
151 ++mCallers;
152 hrc = S_OK;
153 }
154 else
155 if (mState == InInit || mState == InUninit)
156 {
157 if (mStateChangeThread == RTThreadSelf())
158 {
159 /* Called from the same thread that is doing AutoInitSpan or
160 * AutoUninitSpan, just succeed */
161 hrc = S_OK;
162 }
163 else if (mState == InInit)
164 {
165 /* addCaller() is called by a "child" thread while the "parent"
166 * thread is still doing AutoInitSpan/AutoReinitSpan, so wait for
167 * the state to become either Ready/Limited or InitFailed (in
168 * case of init failure).
169 *
170 * Note that we increase the number of callers anyway -- to
171 * prevent AutoUninitSpan from early completion if we are
172 * still not scheduled to pick up the posted semaphore when
173 * uninit() is called.
174 */
175 ++mCallers;
176
177 /* lazy semaphore creation */
178 if (mInitUninitSem == NIL_RTSEMEVENTMULTI)
179 {
180 RTSemEventMultiCreate(&mInitUninitSem);
181 Assert(mInitUninitWaiters == 0);
182 }
183
184 ++mInitUninitWaiters;
185
186 LogFlowThisFunc(("Waiting for AutoInitSpan/AutoReinitSpan to finish...\n"));
187
188 stateLock.release();
189 RTSemEventMultiWait(mInitUninitSem, RT_INDEFINITE_WAIT);
190 stateLock.acquire();
191
192 if (--mInitUninitWaiters == 0)
193 {
194 /* destroy the semaphore since no more necessary */
195 RTSemEventMultiDestroy(mInitUninitSem);
196 mInitUninitSem = NIL_RTSEMEVENTMULTI;
197 }
198
199 if (mState == Ready || (aLimited && mState == Limited))
200 hrc = S_OK;
201 else
202 {
203 Assert(mCallers != 0);
204 --mCallers;
205 if (mCallers == 0 && mState == InUninit)
206 {
207 /* inform AutoUninitSpan ctor there are no more callers */
208 RTSemEventSignal(mZeroCallersSem);
209 }
210 }
211 }
212 }
213
214 if (FAILED(hrc))
215 {
216 if (mState == Limited)
217 hrc = mObj->setError(hrc, AutoCallerCtx::tr("The object functionality is limited"));
218 else if (FAILED(mFailedRC) && mFailedRC != E_ACCESSDENIED)
219 {
220 /* replay recorded error information */
221 if (mpFailedEI)
222 ErrorInfoKeeper eik(*mpFailedEI);
223 hrc = mFailedRC;
224 }
225 else
226 hrc = mObj->setError(hrc, AutoCallerCtx::tr("The object is not ready"));
227 }
228
229 return hrc;
230}
231
232/**
233 * Decreases the number of calls to this object by one.
234 *
235 * Must be called after every #addCaller() when protecting the object
236 * from uninitialization is no more necessary.
237 */
238void ObjectState::releaseCaller()
239{
240 AutoWriteLock stateLock(mStateLock COMMA_LOCKVAL_SRC_POS);
241
242 if (mState == Ready || mState == Limited)
243 {
244 /* if Ready or Limited, decrease the number of callers */
245 AssertMsgReturn(mCallers != 0, ("mCallers is ZERO!"), (void) 0);
246 --mCallers;
247
248 return;
249 }
250
251 if (mState == InInit || mState == InUninit)
252 {
253 if (mStateChangeThread == RTThreadSelf())
254 {
255 /* Called from the same thread that is doing AutoInitSpan or
256 * AutoUninitSpan: just succeed */
257 return;
258 }
259
260 if (mState == InUninit)
261 {
262 /* the caller is being released after AutoUninitSpan has begun */
263 AssertMsgReturn(mCallers != 0, ("mCallers is ZERO!"), (void) 0);
264 --mCallers;
265
266 if (mCallers == 0)
267 /* inform the Auto*UninitSpan ctor there are no more callers */
268 RTSemEventSignal(mZeroCallersSem);
269
270 return;
271 }
272 }
273
274 AssertMsgFailed(("mState = %d!", mState));
275}
276
277bool ObjectState::autoInitSpanConstructor(ObjectState::State aExpectedState)
278{
279 AutoWriteLock stateLock(mStateLock COMMA_LOCKVAL_SRC_POS);
280
281 mFailedRC = S_OK;
282 if (mpFailedEI)
283 {
284 delete mpFailedEI;
285 mpFailedEI = NULL;
286 }
287
288 if (mState == aExpectedState)
289 {
290 setState(InInit);
291 return true;
292 }
293 else
294 return false;
295}
296
297void ObjectState::autoInitSpanDestructor(State aNewState, HRESULT aFailedRC, com::ErrorInfo *apFailedEI)
298{
299 AutoWriteLock stateLock(mStateLock COMMA_LOCKVAL_SRC_POS);
300
301 Assert(mState == InInit);
302
303 if (mCallers > 0 && mInitUninitWaiters > 0)
304 {
305 /* We have some pending addCaller() calls on other threads (created
306 * during InInit), signal that InInit is finished and they may go on. */
307 RTSemEventMultiSignal(mInitUninitSem);
308 }
309
310 if (aNewState == InitFailed || aNewState == Limited)
311 {
312 mFailedRC = aFailedRC;
313 /* apFailedEI may be NULL, when there is no explicit setFailed() or
314 * setLimited() call, which also implies that aFailedRC is S_OK.
315 * This case is used by objects (the majority) which don't want
316 * delayed error signalling. */
317 mpFailedEI = apFailedEI;
318 }
319 else
320 {
321 Assert(SUCCEEDED(aFailedRC));
322 Assert(apFailedEI == NULL);
323 Assert(mpFailedEI == NULL);
324 }
325
326 setState(aNewState);
327}
328
329ObjectState::State ObjectState::autoUninitSpanConstructor(bool fTry)
330{
331 AutoWriteLock stateLock(mStateLock COMMA_LOCKVAL_SRC_POS);
332
333 Assert(mState != InInit);
334
335 if (mState == NotReady)
336 {
337 /* do nothing if already uninitialized */
338 return mState;
339 }
340 else if (mState == InUninit)
341 {
342 /* Another thread has already started uninitialization, wait for its
343 * completion. This is necessary to make sure that when this method
344 * returns, the object state is well-defined (NotReady). */
345
346 if (fTry)
347 return Ready;
348
349 /* lazy semaphore creation */
350 if (mInitUninitSem == NIL_RTSEMEVENTMULTI)
351 {
352 RTSemEventMultiCreate(&mInitUninitSem);
353 Assert(mInitUninitWaiters == 0);
354 }
355 ++mInitUninitWaiters;
356
357 LogFlowFunc(("{%p}: Waiting for AutoUninitSpan to finish...\n", mObj));
358
359 stateLock.release();
360 RTSemEventMultiWait(mInitUninitSem, RT_INDEFINITE_WAIT);
361 stateLock.acquire();
362
363 if (--mInitUninitWaiters == 0)
364 {
365 /* destroy the semaphore since no more necessary */
366 RTSemEventMultiDestroy(mInitUninitSem);
367 mInitUninitSem = NIL_RTSEMEVENTMULTI;
368 }
369
370 /* the other thread set it to NotReady */
371 return mState;
372 }
373
374 /* go to InUninit to prevent from adding new callers */
375 setState(InUninit);
376
377 /* wait for already existing callers to drop to zero */
378 if (mCallers > 0)
379 {
380 if (fTry)
381 return Ready;
382
383 /* lazy creation */
384 Assert(mZeroCallersSem == NIL_RTSEMEVENT);
385 RTSemEventCreate(&mZeroCallersSem);
386
387 /* wait until remaining callers release the object */
388 LogFlowFunc(("{%p}: Waiting for callers (%d) to drop to zero...\n",
389 mObj, mCallers));
390
391 stateLock.release();
392 RTSemEventWait(mZeroCallersSem, RT_INDEFINITE_WAIT);
393 }
394 return mState;
395}
396
397void ObjectState::autoUninitSpanDestructor()
398{
399 AutoWriteLock stateLock(mStateLock COMMA_LOCKVAL_SRC_POS);
400
401 Assert(mState == InUninit);
402
403 setState(NotReady);
404}
405
406
407void ObjectState::setState(ObjectState::State aState)
408{
409 Assert(mState != aState);
410 mState = aState;
411 mStateChangeThread = RTThreadSelf();
412}
413
414
415////////////////////////////////////////////////////////////////////////////////
416//
417// AutoInitSpan methods
418//
419////////////////////////////////////////////////////////////////////////////////
420
421/**
422 * Creates a smart initialization span object that places the object to
423 * InInit state.
424 *
425 * Please see the AutoInitSpan class description for more info.
426 *
427 * @param aObj |this| pointer of the managed VirtualBoxBase object whose
428 * init() method is being called.
429 * @param aResult Default initialization result.
430 */
431AutoInitSpan::AutoInitSpan(VirtualBoxBase *aObj,
432 Result aResult /* = Failed */)
433 : mObj(aObj),
434 mResult(aResult),
435 mOk(false),
436 mFailedRC(S_OK),
437 mpFailedEI(NULL)
438{
439 Assert(mObj);
440 mOk = mObj->getObjectState().autoInitSpanConstructor(ObjectState::NotReady);
441 AssertReturnVoid(mOk);
442}
443
444/**
445 * Places the managed VirtualBoxBase object to Ready/Limited state if the
446 * initialization succeeded or partly succeeded, or places it to InitFailed
447 * state and calls the object's uninit() method.
448 *
449 * Please see the AutoInitSpan class description for more info.
450 */
451AutoInitSpan::~AutoInitSpan()
452{
453 /* if the state was other than NotReady, do nothing */
454 if (!mOk)
455 {
456 Assert(SUCCEEDED(mFailedRC));
457 Assert(mpFailedEI == NULL);
458 return;
459 }
460
461 ObjectState::State newState;
462 if (mResult == Succeeded)
463 newState = ObjectState::Ready;
464 else if (mResult == Limited)
465 newState = ObjectState::Limited;
466 else
467 newState = ObjectState::InitFailed;
468 mObj->getObjectState().autoInitSpanDestructor(newState, mFailedRC, mpFailedEI);
469 mFailedRC = S_OK;
470 mpFailedEI = NULL; /* now owned by ObjectState instance */
471 if (newState == ObjectState::InitFailed)
472 {
473 /* call uninit() to let the object uninit itself after failed init() */
474 mObj->uninit();
475 }
476}
477
478// AutoReinitSpan methods
479////////////////////////////////////////////////////////////////////////////////
480
481/**
482 * Creates a smart re-initialization span object and places the object to
483 * InInit state.
484 *
485 * Please see the AutoInitSpan class description for more info.
486 *
487 * @param aObj |this| pointer of the managed VirtualBoxBase object whose
488 * re-initialization method is being called.
489 */
490AutoReinitSpan::AutoReinitSpan(VirtualBoxBase *aObj)
491 : mObj(aObj),
492 mSucceeded(false),
493 mOk(false)
494{
495 Assert(mObj);
496 mOk = mObj->getObjectState().autoInitSpanConstructor(ObjectState::Limited);
497 AssertReturnVoid(mOk);
498}
499
500/**
501 * Places the managed VirtualBoxBase object to Ready state if the
502 * re-initialization succeeded (i.e. #setSucceeded() has been called) or back to
503 * Limited state otherwise.
504 *
505 * Please see the AutoInitSpan class description for more info.
506 */
507AutoReinitSpan::~AutoReinitSpan()
508{
509 /* if the state was other than Limited, do nothing */
510 if (!mOk)
511 return;
512
513 ObjectState::State newState;
514 if (mSucceeded)
515 newState = ObjectState::Ready;
516 else
517 newState = ObjectState::Limited;
518 mObj->getObjectState().autoInitSpanDestructor(newState, S_OK, NULL);
519 /* If later AutoReinitSpan can truly fail (today there is no way) then
520 * in this place there needs to be an mObj->uninit() call just like in
521 * the AutoInitSpan destructor. In that case it might make sense to
522 * let AutoReinitSpan inherit from AutoInitSpan, as the code can be
523 * made (almost) identical. */
524}
525
526// AutoUninitSpan methods
527////////////////////////////////////////////////////////////////////////////////
528
529/**
530 * Creates a smart uninitialization span object and places this object to
531 * InUninit state.
532 *
533 * Please see the AutoInitSpan class description for more info.
534 *
535 * @note This method blocks the current thread execution until the number of
536 * callers of the managed VirtualBoxBase object drops to zero!
537 *
538 * @param aObj |this| pointer of the VirtualBoxBase object whose uninit()
539 * method is being called.
540 * @param fTry @c true if the wait for other callers should be skipped,
541 * requiring checking if the uninit span is actually operational.
542 */
543AutoUninitSpan::AutoUninitSpan(VirtualBoxBase *aObj, bool fTry /* = false */)
544 : mObj(aObj),
545 mInitFailed(false),
546 mUninitDone(false),
547 mUninitFailed(false)
548{
549 Assert(mObj);
550 ObjectState::State state;
551 state = mObj->getObjectState().autoUninitSpanConstructor(fTry);
552 if (state == ObjectState::InitFailed)
553 mInitFailed = true;
554 else if (state == ObjectState::NotReady)
555 mUninitDone = true;
556 else if (state == ObjectState::Ready)
557 mUninitFailed = true;
558}
559
560/**
561 * Places the managed VirtualBoxBase object to the NotReady state.
562 */
563AutoUninitSpan::~AutoUninitSpan()
564{
565 /* do nothing if already uninitialized */
566 if (mUninitDone || mUninitFailed)
567 return;
568
569 mObj->getObjectState().autoUninitSpanDestructor();
570}
571
572/**
573 * Marks the uninitializion as succeeded.
574 *
575 * Same as the destructor, and makes the destructor do nothing.
576 */
577void AutoUninitSpan::setSucceeded()
578{
579 /* do nothing if already uninitialized */
580 if (mUninitDone || mUninitFailed)
581 return;
582
583 mObj->getObjectState().autoUninitSpanDestructor();
584 mUninitDone = true;
585}
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use