diff options
Diffstat (limited to 'src/VBox/Main/src-all/AutoCaller.cpp')
-rw-r--r-- | src/VBox/Main/src-all/AutoCaller.cpp | 585 |
1 files changed, 585 insertions, 0 deletions
diff --git a/src/VBox/Main/src-all/AutoCaller.cpp b/src/VBox/Main/src-all/AutoCaller.cpp new file mode 100644 index 00000000..4e5de550 --- /dev/null +++ b/src/VBox/Main/src-all/AutoCaller.cpp @@ -0,0 +1,585 @@ +/* $Id: AutoCaller.cpp $ */ +/** @file + * VirtualBox object state implementation + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#define LOG_GROUP LOG_GROUP_MAIN +#include <iprt/semaphore.h> + +#include "VirtualBoxBase.h" +#include "AutoCaller.h" +#include "LoggingNew.h" + +#include "VBoxNls.h" + + +DECLARE_TRANSLATION_CONTEXT(AutoCallerCtx); + +//////////////////////////////////////////////////////////////////////////////// +// +// ObjectState methods +// +//////////////////////////////////////////////////////////////////////////////// + + +ObjectState::ObjectState() : mStateLock(LOCKCLASS_OBJECTSTATE) +{ + AssertFailed(); +} + +ObjectState::ObjectState(VirtualBoxBase *aObj) : + mObj(aObj), mStateLock(LOCKCLASS_OBJECTSTATE) +{ + Assert(mObj); + mState = NotReady; + mStateChangeThread = NIL_RTTHREAD; + mCallers = 0; + mFailedRC = S_OK; + mpFailedEI = NULL; + mZeroCallersSem = NIL_RTSEMEVENT; + mInitUninitSem = NIL_RTSEMEVENTMULTI; + mInitUninitWaiters = 0; +} + +ObjectState::~ObjectState() +{ + Assert(mInitUninitWaiters == 0); + Assert(mInitUninitSem == NIL_RTSEMEVENTMULTI); + if (mZeroCallersSem != NIL_RTSEMEVENT) + RTSemEventDestroy(mZeroCallersSem); + mCallers = 0; + mStateChangeThread = NIL_RTTHREAD; + mState = NotReady; + mFailedRC = S_OK; + if (mpFailedEI) + { + delete mpFailedEI; + mpFailedEI = NULL; + } + mObj = NULL; +} + +ObjectState::State ObjectState::getState() +{ + AutoReadLock stateLock(mStateLock COMMA_LOCKVAL_SRC_POS); + return mState; +} + +/** + * Increments the number of calls to this object by one. + * + * After this method succeeds, it is guaranteed that the object will remain + * in the Ready (or in the Limited) state at least until #releaseCaller() is + * called. + * + * This method is intended to mark the beginning of sections of code within + * methods of COM objects that depend on the readiness (Ready) state. The + * Ready state is a primary "ready to serve" state. Usually all code that + * works with component's data depends on it. On practice, this means that + * almost every public method, setter or getter of the object should add + * itself as an object's caller at the very beginning, to protect from an + * unexpected uninitialization that may happen on a different thread. + * + * Besides the Ready state denoting that the object is fully functional, + * there is a special Limited state. The Limited state means that the object + * is still functional, but its functionality is limited to some degree, so + * not all operations are possible. The @a aLimited argument to this method + * determines whether the caller represents this limited functionality or + * not. + * + * This method succeeds (and increments the number of callers) only if the + * current object's state is Ready. Otherwise, it will return E_ACCESSDENIED + * to indicate that the object is not operational. There are two exceptions + * from this rule: + * <ol> + * <li>If the @a aLimited argument is |true|, then this method will also + * succeed if the object's state is Limited (or Ready, of course). + * </li> + * <li>If this method is called from the same thread that placed + * the object to InInit or InUninit state (i.e. either from within the + * AutoInitSpan or AutoUninitSpan scope), it will succeed as well (but + * will not increase the number of callers). + * </li> + * </ol> + * + * Normally, calling addCaller() never blocks. However, if this method is + * called by a thread created from within the AutoInitSpan scope and this + * scope is still active (i.e. the object state is InInit), it will block + * until the AutoInitSpan destructor signals that it has finished + * initialization. + * + * When this method returns a failure, the caller must not use the object + * and should return the failed result code to its own caller. + * + * @param aLimited |true| to add a limited caller. + * + * @return S_OK on success or E_ACCESSDENIED on failure. + * + * @sa #releaseCaller() + */ +HRESULT ObjectState::addCaller(bool aLimited /* = false */) +{ + AutoWriteLock stateLock(mStateLock COMMA_LOCKVAL_SRC_POS); + + HRESULT hrc = E_ACCESSDENIED; + + if (mState == Ready || (aLimited && mState == Limited)) + { + /* if Ready or allows Limited, increase the number of callers */ + ++mCallers; + hrc = S_OK; + } + else + if (mState == InInit || mState == InUninit) + { + if (mStateChangeThread == RTThreadSelf()) + { + /* Called from the same thread that is doing AutoInitSpan or + * AutoUninitSpan, just succeed */ + hrc = S_OK; + } + else if (mState == InInit) + { + /* addCaller() is called by a "child" thread while the "parent" + * thread is still doing AutoInitSpan/AutoReinitSpan, so wait for + * the state to become either Ready/Limited or InitFailed (in + * case of init failure). + * + * Note that we increase the number of callers anyway -- to + * prevent AutoUninitSpan from early completion if we are + * still not scheduled to pick up the posted semaphore when + * uninit() is called. + */ + ++mCallers; + + /* lazy semaphore creation */ + if (mInitUninitSem == NIL_RTSEMEVENTMULTI) + { + RTSemEventMultiCreate(&mInitUninitSem); + Assert(mInitUninitWaiters == 0); + } + + ++mInitUninitWaiters; + + LogFlowThisFunc(("Waiting for AutoInitSpan/AutoReinitSpan to finish...\n")); + + stateLock.release(); + RTSemEventMultiWait(mInitUninitSem, RT_INDEFINITE_WAIT); + stateLock.acquire(); + + if (--mInitUninitWaiters == 0) + { + /* destroy the semaphore since no more necessary */ + RTSemEventMultiDestroy(mInitUninitSem); + mInitUninitSem = NIL_RTSEMEVENTMULTI; + } + + if (mState == Ready || (aLimited && mState == Limited)) + hrc = S_OK; + else + { + Assert(mCallers != 0); + --mCallers; + if (mCallers == 0 && mState == InUninit) + { + /* inform AutoUninitSpan ctor there are no more callers */ + RTSemEventSignal(mZeroCallersSem); + } + } + } + } + + if (FAILED(hrc)) + { + if (mState == Limited) + hrc = mObj->setError(hrc, AutoCallerCtx::tr("The object functionality is limited")); + else if (FAILED(mFailedRC) && mFailedRC != E_ACCESSDENIED) + { + /* replay recorded error information */ + if (mpFailedEI) + ErrorInfoKeeper eik(*mpFailedEI); + hrc = mFailedRC; + } + else + hrc = mObj->setError(hrc, AutoCallerCtx::tr("The object is not ready")); + } + + return hrc; +} + +/** + * Decreases the number of calls to this object by one. + * + * Must be called after every #addCaller() when protecting the object + * from uninitialization is no more necessary. + */ +void ObjectState::releaseCaller() +{ + AutoWriteLock stateLock(mStateLock COMMA_LOCKVAL_SRC_POS); + + if (mState == Ready || mState == Limited) + { + /* if Ready or Limited, decrease the number of callers */ + AssertMsgReturn(mCallers != 0, ("mCallers is ZERO!"), (void) 0); + --mCallers; + + return; + } + + if (mState == InInit || mState == InUninit) + { + if (mStateChangeThread == RTThreadSelf()) + { + /* Called from the same thread that is doing AutoInitSpan or + * AutoUninitSpan: just succeed */ + return; + } + + if (mState == InUninit) + { + /* the caller is being released after AutoUninitSpan has begun */ + AssertMsgReturn(mCallers != 0, ("mCallers is ZERO!"), (void) 0); + --mCallers; + + if (mCallers == 0) + /* inform the Auto*UninitSpan ctor there are no more callers */ + RTSemEventSignal(mZeroCallersSem); + + return; + } + } + + AssertMsgFailed(("mState = %d!", mState)); +} + +bool ObjectState::autoInitSpanConstructor(ObjectState::State aExpectedState) +{ + AutoWriteLock stateLock(mStateLock COMMA_LOCKVAL_SRC_POS); + + mFailedRC = S_OK; + if (mpFailedEI) + { + delete mpFailedEI; + mpFailedEI = NULL; + } + + if (mState == aExpectedState) + { + setState(InInit); + return true; + } + else + return false; +} + +void ObjectState::autoInitSpanDestructor(State aNewState, HRESULT aFailedRC, com::ErrorInfo *apFailedEI) +{ + AutoWriteLock stateLock(mStateLock COMMA_LOCKVAL_SRC_POS); + + Assert(mState == InInit); + + if (mCallers > 0 && mInitUninitWaiters > 0) + { + /* We have some pending addCaller() calls on other threads (created + * during InInit), signal that InInit is finished and they may go on. */ + RTSemEventMultiSignal(mInitUninitSem); + } + + if (aNewState == InitFailed || aNewState == Limited) + { + mFailedRC = aFailedRC; + /* apFailedEI may be NULL, when there is no explicit setFailed() or + * setLimited() call, which also implies that aFailedRC is S_OK. + * This case is used by objects (the majority) which don't want + * delayed error signalling. */ + mpFailedEI = apFailedEI; + } + else + { + Assert(SUCCEEDED(aFailedRC)); + Assert(apFailedEI == NULL); + Assert(mpFailedEI == NULL); + } + + setState(aNewState); +} + +ObjectState::State ObjectState::autoUninitSpanConstructor(bool fTry) +{ + AutoWriteLock stateLock(mStateLock COMMA_LOCKVAL_SRC_POS); + + Assert(mState != InInit); + + if (mState == NotReady) + { + /* do nothing if already uninitialized */ + return mState; + } + else if (mState == InUninit) + { + /* Another thread has already started uninitialization, wait for its + * completion. This is necessary to make sure that when this method + * returns, the object state is well-defined (NotReady). */ + + if (fTry) + return Ready; + + /* lazy semaphore creation */ + if (mInitUninitSem == NIL_RTSEMEVENTMULTI) + { + RTSemEventMultiCreate(&mInitUninitSem); + Assert(mInitUninitWaiters == 0); + } + ++mInitUninitWaiters; + + LogFlowFunc(("{%p}: Waiting for AutoUninitSpan to finish...\n", mObj)); + + stateLock.release(); + RTSemEventMultiWait(mInitUninitSem, RT_INDEFINITE_WAIT); + stateLock.acquire(); + + if (--mInitUninitWaiters == 0) + { + /* destroy the semaphore since no more necessary */ + RTSemEventMultiDestroy(mInitUninitSem); + mInitUninitSem = NIL_RTSEMEVENTMULTI; + } + + /* the other thread set it to NotReady */ + return mState; + } + + /* go to InUninit to prevent from adding new callers */ + setState(InUninit); + + /* wait for already existing callers to drop to zero */ + if (mCallers > 0) + { + if (fTry) + return Ready; + + /* lazy creation */ + Assert(mZeroCallersSem == NIL_RTSEMEVENT); + RTSemEventCreate(&mZeroCallersSem); + + /* wait until remaining callers release the object */ + LogFlowFunc(("{%p}: Waiting for callers (%d) to drop to zero...\n", + mObj, mCallers)); + + stateLock.release(); + RTSemEventWait(mZeroCallersSem, RT_INDEFINITE_WAIT); + } + return mState; +} + +void ObjectState::autoUninitSpanDestructor() +{ + AutoWriteLock stateLock(mStateLock COMMA_LOCKVAL_SRC_POS); + + Assert(mState == InUninit); + + setState(NotReady); +} + + +void ObjectState::setState(ObjectState::State aState) +{ + Assert(mState != aState); + mState = aState; + mStateChangeThread = RTThreadSelf(); +} + + +//////////////////////////////////////////////////////////////////////////////// +// +// AutoInitSpan methods +// +//////////////////////////////////////////////////////////////////////////////// + +/** + * Creates a smart initialization span object that places the object to + * InInit state. + * + * Please see the AutoInitSpan class description for more info. + * + * @param aObj |this| pointer of the managed VirtualBoxBase object whose + * init() method is being called. + * @param aResult Default initialization result. + */ +AutoInitSpan::AutoInitSpan(VirtualBoxBase *aObj, + Result aResult /* = Failed */) + : mObj(aObj), + mResult(aResult), + mOk(false), + mFailedRC(S_OK), + mpFailedEI(NULL) +{ + Assert(mObj); + mOk = mObj->getObjectState().autoInitSpanConstructor(ObjectState::NotReady); + AssertReturnVoid(mOk); +} + +/** + * Places the managed VirtualBoxBase object to Ready/Limited state if the + * initialization succeeded or partly succeeded, or places it to InitFailed + * state and calls the object's uninit() method. + * + * Please see the AutoInitSpan class description for more info. + */ +AutoInitSpan::~AutoInitSpan() +{ + /* if the state was other than NotReady, do nothing */ + if (!mOk) + { + Assert(SUCCEEDED(mFailedRC)); + Assert(mpFailedEI == NULL); + return; + } + + ObjectState::State newState; + if (mResult == Succeeded) + newState = ObjectState::Ready; + else if (mResult == Limited) + newState = ObjectState::Limited; + else + newState = ObjectState::InitFailed; + mObj->getObjectState().autoInitSpanDestructor(newState, mFailedRC, mpFailedEI); + mFailedRC = S_OK; + mpFailedEI = NULL; /* now owned by ObjectState instance */ + if (newState == ObjectState::InitFailed) + { + /* call uninit() to let the object uninit itself after failed init() */ + mObj->uninit(); + } +} + +// AutoReinitSpan methods +//////////////////////////////////////////////////////////////////////////////// + +/** + * Creates a smart re-initialization span object and places the object to + * InInit state. + * + * Please see the AutoInitSpan class description for more info. + * + * @param aObj |this| pointer of the managed VirtualBoxBase object whose + * re-initialization method is being called. + */ +AutoReinitSpan::AutoReinitSpan(VirtualBoxBase *aObj) + : mObj(aObj), + mSucceeded(false), + mOk(false) +{ + Assert(mObj); + mOk = mObj->getObjectState().autoInitSpanConstructor(ObjectState::Limited); + AssertReturnVoid(mOk); +} + +/** + * Places the managed VirtualBoxBase object to Ready state if the + * re-initialization succeeded (i.e. #setSucceeded() has been called) or back to + * Limited state otherwise. + * + * Please see the AutoInitSpan class description for more info. + */ +AutoReinitSpan::~AutoReinitSpan() +{ + /* if the state was other than Limited, do nothing */ + if (!mOk) + return; + + ObjectState::State newState; + if (mSucceeded) + newState = ObjectState::Ready; + else + newState = ObjectState::Limited; + mObj->getObjectState().autoInitSpanDestructor(newState, S_OK, NULL); + /* If later AutoReinitSpan can truly fail (today there is no way) then + * in this place there needs to be an mObj->uninit() call just like in + * the AutoInitSpan destructor. In that case it might make sense to + * let AutoReinitSpan inherit from AutoInitSpan, as the code can be + * made (almost) identical. */ +} + +// AutoUninitSpan methods +//////////////////////////////////////////////////////////////////////////////// + +/** + * Creates a smart uninitialization span object and places this object to + * InUninit state. + * + * Please see the AutoInitSpan class description for more info. + * + * @note This method blocks the current thread execution until the number of + * callers of the managed VirtualBoxBase object drops to zero! + * + * @param aObj |this| pointer of the VirtualBoxBase object whose uninit() + * method is being called. + * @param fTry @c true if the wait for other callers should be skipped, + * requiring checking if the uninit span is actually operational. + */ +AutoUninitSpan::AutoUninitSpan(VirtualBoxBase *aObj, bool fTry /* = false */) + : mObj(aObj), + mInitFailed(false), + mUninitDone(false), + mUninitFailed(false) +{ + Assert(mObj); + ObjectState::State state; + state = mObj->getObjectState().autoUninitSpanConstructor(fTry); + if (state == ObjectState::InitFailed) + mInitFailed = true; + else if (state == ObjectState::NotReady) + mUninitDone = true; + else if (state == ObjectState::Ready) + mUninitFailed = true; +} + +/** + * Places the managed VirtualBoxBase object to the NotReady state. + */ +AutoUninitSpan::~AutoUninitSpan() +{ + /* do nothing if already uninitialized */ + if (mUninitDone || mUninitFailed) + return; + + mObj->getObjectState().autoUninitSpanDestructor(); +} + +/** + * Marks the uninitializion as succeeded. + * + * Same as the destructor, and makes the destructor do nothing. + */ +void AutoUninitSpan::setSucceeded() +{ + /* do nothing if already uninitialized */ + if (mUninitDone || mUninitFailed) + return; + + mObj->getObjectState().autoUninitSpanDestructor(); + mUninitDone = true; +} |