/* $Id: AutoCaller.cpp 98103 2023-01-17 14:15:46Z vboxsync $ */ /** @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 . * * SPDX-License-Identifier: GPL-3.0-only */ #define LOG_GROUP LOG_GROUP_MAIN #include #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: *
    *
  1. If the @a aLimited argument is |true|, then this method will also * succeed if the object's state is Limited (or Ready, of course). *
  2. *
  3. 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). *
  4. *
* * 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; }