diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:49:04 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:49:04 +0000 |
commit | 16f504a9dca3fe3b70568f67b7d41241ae485288 (patch) | |
tree | c60f36ada0496ba928b7161059ba5ab1ab224f9d /src/VBox/Main/src-all/EventImpl.cpp | |
parent | Initial commit. (diff) | |
download | virtualbox-16f504a9dca3fe3b70568f67b7d41241ae485288.tar.xz virtualbox-16f504a9dca3fe3b70568f67b7d41241ae485288.zip |
Adding upstream version 7.0.6-dfsg.upstream/7.0.6-dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Main/src-all/EventImpl.cpp')
-rw-r--r-- | src/VBox/Main/src-all/EventImpl.cpp | 1690 |
1 files changed, 1690 insertions, 0 deletions
diff --git a/src/VBox/Main/src-all/EventImpl.cpp b/src/VBox/Main/src-all/EventImpl.cpp new file mode 100644 index 00000000..85953d30 --- /dev/null +++ b/src/VBox/Main/src-all/EventImpl.cpp @@ -0,0 +1,1690 @@ +/* $Id: EventImpl.cpp $ */ +/** @file + * VirtualBox COM Event class implementation + */ + +/* + * Copyright (C) 2010-2022 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 + */ + +/** @page pg_main_events Events + * + * Theory of operations. + * + * This code implements easily extensible event mechanism, letting us + * to make any VirtualBox object an event source (by aggregating an EventSource instance). + * Another entity could subscribe to the event source for events it is interested in. + * If an event is waitable, it's possible to wait until all listeners + * registered at the moment of firing event as ones interested in this + * event acknowledged that they finished event processing (thus allowing + * vetoable events). + * + * Listeners can be registered as active or passive ones, defining policy of delivery. + * For *active* listeners, their HandleEvent() method is invoked when event is fired by + * the event source (pretty much callbacks). + * For *passive* listeners, it's up to an event consumer to perform GetEvent() operation + * with given listener, and then perform desired operation with returned event, if any. + * For passive listeners case, listener instance serves as merely a key referring to + * particular event consumer, thus HandleEvent() implementation isn't that important. + * IEventSource's CreateListener() could be used to create such a listener. + * Passive mode is designed for transports not allowing callbacks, such as webservices + * running on top of HTTP, and for situations where consumer wants exact control on + * context where event handler is executed (such as GUI thread for some toolkits). + * + * Internal EventSource data structures are optimized for fast event delivery, while + * listener registration/unregistration operations are expected being pretty rare. + * Passive mode listeners keep an internal event queue for all events they receive, + * and all waitable events are added to the pending events map. This map keeps track + * of how many listeners are still not acknowledged their event, and once this counter + * reach zero, element is removed from pending events map, and event is marked as processed. + * Thus if passive listener's user forgets to call IEventSource's EventProcessed() + * waiters may never know that event processing finished. + */ + +#define LOG_GROUP LOG_GROUP_MAIN_EVENT +#include <list> +#include <map> +#include <deque> + +#include "EventImpl.h" +#include "AutoCaller.h" +#include "LoggingNew.h" +#include "VBoxEvents.h" + +#include <iprt/asm.h> +#include <iprt/critsect.h> +#include <iprt/errcore.h> +#include <iprt/semaphore.h> +#include <iprt/time.h> + +#include <VBox/com/array.h> + +class ListenerRecord; + +struct VBoxEvent::Data +{ + Data() + : mType(VBoxEventType_Invalid), + mWaitEvent(NIL_RTSEMEVENT), + mWaitable(FALSE), + mProcessed(FALSE) + {} + + VBoxEventType_T mType; + RTSEMEVENT mWaitEvent; + BOOL mWaitable; + BOOL mProcessed; + ComPtr<IEventSource> mSource; +}; + +DEFINE_EMPTY_CTOR_DTOR(VBoxEvent) + +HRESULT VBoxEvent::FinalConstruct() +{ + m = new Data; + return BaseFinalConstruct(); +} + +void VBoxEvent::FinalRelease() +{ + if (m) + { + uninit(); + delete m; + m = NULL; + } + BaseFinalRelease(); +} + +HRESULT VBoxEvent::init(IEventSource *aSource, VBoxEventType_T aType, BOOL aWaitable) +{ + AssertReturn(aSource != NULL, E_INVALIDARG); + + AutoInitSpan autoInitSpan(this); + AssertReturn(autoInitSpan.isOk(), E_FAIL); + + m->mSource = aSource; + m->mType = aType; + m->mWaitable = aWaitable; + m->mProcessed = !aWaitable; + + do + { + if (aWaitable) + { + int vrc = ::RTSemEventCreate(&m->mWaitEvent); + + if (RT_FAILURE(vrc)) + { + AssertFailed(); + return setError(E_FAIL, + tr("Internal error (%Rrc)"), vrc); + } + } + } while (0); + + /* Confirm a successful initialization */ + autoInitSpan.setSucceeded(); + + return S_OK; +} + +void VBoxEvent::uninit() +{ + AutoUninitSpan autoUninitSpan(this); + if (autoUninitSpan.uninitDone()) + return; + + if (!m) + return; + + m->mProcessed = TRUE; + m->mType = VBoxEventType_Invalid; + m->mSource.setNull(); + + if (m->mWaitEvent != NIL_RTSEMEVENT) + { + Assert(m->mWaitable); + ::RTSemEventDestroy(m->mWaitEvent); + m->mWaitEvent = NIL_RTSEMEVENT; + } +} + +HRESULT VBoxEvent::getType(VBoxEventType_T *aType) +{ + // never changes while event alive, no locking + *aType = m->mType; + return S_OK; +} + +HRESULT VBoxEvent::getSource(ComPtr<IEventSource> &aSource) +{ + m->mSource.queryInterfaceTo(aSource.asOutParam()); + return S_OK; +} + +HRESULT VBoxEvent::getWaitable(BOOL *aWaitable) +{ + // never changes while event alive, no locking + *aWaitable = m->mWaitable; + return S_OK; +} + +HRESULT VBoxEvent::setProcessed() +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + if (m->mProcessed) + return S_OK; + + m->mProcessed = TRUE; + + // notify waiters + ::RTSemEventSignal(m->mWaitEvent); + + return S_OK; +} + +HRESULT VBoxEvent::waitProcessed(LONG aTimeout, BOOL *aResult) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + if (m->mProcessed) + { + *aResult = TRUE; + return S_OK; + } + + if (aTimeout == 0) + { + *aResult = m->mProcessed; + return S_OK; + } + + // must drop lock while waiting, because setProcessed() needs synchronization. + alock.release(); + /** @todo maybe while loop for spurious wakeups? */ + int vrc = ::RTSemEventWait(m->mWaitEvent, aTimeout < 0 ? RT_INDEFINITE_WAIT : (RTMSINTERVAL)aTimeout); + AssertMsg(RT_SUCCESS(vrc) || vrc == VERR_TIMEOUT || vrc == VERR_INTERRUPTED, + ("RTSemEventWait returned %Rrc\n", vrc)); + alock.acquire(); + + if (RT_SUCCESS(vrc)) + { + AssertMsg(m->mProcessed, + ("mProcessed must be set here\n")); + *aResult = m->mProcessed; + } + else + { + *aResult = FALSE; + /* + * If we timed out then one or more passive listeners didn't process this event + * within the time limit most likely due to the listener no longer being alive (e.g. + * the VirtualBox GUI crashed) so we flag this to our caller so it can remove this + * event from the list of events the passive listener is interested in. This avoids + * incurring this timeout every time the event is fired. + */ + if (vrc == VERR_TIMEOUT) + return E_ABORT; + } + + return S_OK; +} + +typedef std::list<Utf8Str> VetoList; +typedef std::list<Utf8Str> ApprovalList; +struct VBoxVetoEvent::Data +{ + Data() : + mVetoed(FALSE) + {} + ComObjPtr<VBoxEvent> mEvent; + BOOL mVetoed; + VetoList mVetoList; + ApprovalList mApprovalList; +}; + +HRESULT VBoxVetoEvent::FinalConstruct() +{ + m = new Data; + HRESULT hrc = m->mEvent.createObject(); + BaseFinalConstruct(); + return hrc; +} + +void VBoxVetoEvent::FinalRelease() +{ + if (m) + { + uninit(); + delete m; + m = NULL; + } + BaseFinalRelease(); +} + +DEFINE_EMPTY_CTOR_DTOR(VBoxVetoEvent) + +HRESULT VBoxVetoEvent::init(IEventSource *aSource, VBoxEventType_T aType) +{ + // all veto events are waitable + HRESULT hrc = m->mEvent->init(aSource, aType, TRUE); + if (FAILED(hrc)) + return hrc; + + AutoInitSpan autoInitSpan(this); + AssertReturn(autoInitSpan.isOk(), E_FAIL); + + m->mVetoed = FALSE; + m->mVetoList.clear(); + m->mApprovalList.clear(); + + /* Confirm a successful initialization */ + autoInitSpan.setSucceeded(); + + return S_OK; +} + +void VBoxVetoEvent::uninit() +{ + AutoUninitSpan autoUninitSpan(this); + if (autoUninitSpan.uninitDone()) + return; + + if (!m) + return; + + m->mVetoed = FALSE; + if (!m->mEvent.isNull()) + { + m->mEvent->uninit(); + m->mEvent.setNull(); + } +} + +HRESULT VBoxVetoEvent::getType(VBoxEventType_T *aType) +{ + return m->mEvent->COMGETTER(Type)(aType); +} + +HRESULT VBoxVetoEvent::getSource(ComPtr<IEventSource> &aSource) +{ + return m->mEvent->COMGETTER(Source)(aSource.asOutParam()); +} + +HRESULT VBoxVetoEvent::getWaitable(BOOL *aWaitable) +{ + return m->mEvent->COMGETTER(Waitable)(aWaitable); +} + +HRESULT VBoxVetoEvent::setProcessed() +{ + return m->mEvent->SetProcessed(); +} + +HRESULT VBoxVetoEvent::waitProcessed(LONG aTimeout, BOOL *aResult) +{ + return m->mEvent->WaitProcessed(aTimeout, aResult); +} + +HRESULT VBoxVetoEvent::addVeto(const com::Utf8Str &aReason) +{ + // AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + if (aReason.length()) + m->mVetoList.push_back(aReason); + + m->mVetoed = TRUE; + + return S_OK; +} + +HRESULT VBoxVetoEvent::isVetoed(BOOL *aResult) +{ + // AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + *aResult = m->mVetoed; + + return S_OK; +} + +HRESULT VBoxVetoEvent::getVetos(std::vector<com::Utf8Str> &aResult) +{ + // AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + aResult.resize(m->mVetoList.size()); + size_t i = 0; + for (VetoList::const_iterator it = m->mVetoList.begin(); it != m->mVetoList.end(); ++it, ++i) + aResult[i] = (*it); + + return S_OK; + +} + +HRESULT VBoxVetoEvent::addApproval(const com::Utf8Str &aReason) +{ + // AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + m->mApprovalList.push_back(aReason); + return S_OK; +} + +HRESULT VBoxVetoEvent::isApproved(BOOL *aResult) +{ + // AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + *aResult = !m->mApprovalList.empty(); + return S_OK; +} + +HRESULT VBoxVetoEvent::getApprovals(std::vector<com::Utf8Str> &aResult) +{ + // AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + aResult.resize(m->mApprovalList.size()); + size_t i = 0; + for (ApprovalList::const_iterator it = m->mApprovalList.begin(); it != m->mApprovalList.end(); ++it, ++i) + aResult[i] = (*it); + return S_OK; +} + +static const int FirstEvent = (int)VBoxEventType_LastWildcard + 1; +static const int LastEvent = (int)VBoxEventType_End; +static const int NumEvents = LastEvent - FirstEvent; + +/** + * Class replacing std::list and able to provide required stability + * during iteration. It's acheived by delaying structural modifications + * to the list till the moment particular element is no longer used by + * current iterators. + */ +class EventMapRecord +{ +public: + /** + * We have to be double linked, as structural modifications in list are delayed + * till element removed, so we have to know our previous one to update its next + */ + EventMapRecord *mNext; + bool mAlive; +private: + EventMapRecord *mPrev; + ListenerRecord *mRef; /* must be weak reference */ + int32_t mRefCnt; + +public: + EventMapRecord(ListenerRecord *aRef) : + mNext(0), mAlive(true), mPrev(0), mRef(aRef), mRefCnt(1) + {} + + EventMapRecord(EventMapRecord &aOther) + { + mNext = aOther.mNext; + mPrev = aOther.mPrev; + mRef = aOther.mRef; + mRefCnt = aOther.mRefCnt; + mAlive = aOther.mAlive; + } + + ~EventMapRecord() + { + if (mNext) + mNext->mPrev = mPrev; + if (mPrev) + mPrev->mNext = mNext; + } + + void addRef() + { + ASMAtomicIncS32(&mRefCnt); + } + + void release() + { + if (ASMAtomicDecS32(&mRefCnt) <= 0) + delete this; + } + + // Called when an element is no longer needed + void kill() + { + mAlive = false; + release(); + } + + ListenerRecord *ref() + { + return mAlive ? mRef : 0; + } + + friend class EventMapList; +}; + + +class EventMapList +{ + EventMapRecord *mHead; + uint32_t mSize; +public: + EventMapList() + : + mHead(0), + mSize(0) + {} + ~EventMapList() + { + EventMapRecord *pCur = mHead; + while (pCur) + { + EventMapRecord *pNext = pCur->mNext; + pCur->release(); + pCur = pNext; + } + } + + /* + * Elements have to be added to the front of the list, to make sure + * that iterators doesn't see newly added listeners, and iteration + * will always complete. + */ + void add(ListenerRecord *aRec) + { + EventMapRecord *pNew = new EventMapRecord(aRec); + pNew->mNext = mHead; + if (mHead) + mHead->mPrev = pNew; + mHead = pNew; + mSize++; + } + + /* + * Mark element as removed, actual removal could be delayed until + * all consumers release it too. This helps to keep list stable + * enough for iterators to allow long and probably intrusive callbacks. + */ + void remove(ListenerRecord *aRec) + { + EventMapRecord *pCur = mHead; + while (pCur) + { + EventMapRecord *aNext = pCur->mNext; + if (pCur->ref() == aRec) + { + if (pCur == mHead) + mHead = aNext; + pCur->kill(); + mSize--; + // break? + } + pCur = aNext; + } + } + + uint32_t size() const + { + return mSize; + } + + struct iterator + { + EventMapRecord *mCur; + + iterator() : + mCur(0) + {} + + explicit + iterator(EventMapRecord *aCur) : + mCur(aCur) + { + // Prevent element removal, till we're at it + if (mCur) + mCur->addRef(); + } + + ~iterator() + { + if (mCur) + mCur->release(); + } + + ListenerRecord * + operator*() const + { + return mCur->ref(); + } + + EventMapList::iterator & + operator++() + { + EventMapRecord *pPrev = mCur; + do { + mCur = mCur->mNext; + } while (mCur && !mCur->mAlive); + + // now we can safely release previous element + pPrev->release(); + + // And grab the new current + if (mCur) + mCur->addRef(); + + return *this; + } + + bool + operator==(const EventMapList::iterator &aOther) const + { + return mCur == aOther.mCur; + } + + bool + operator!=(const EventMapList::iterator &aOther) const + { + return mCur != aOther.mCur; + } + }; + + iterator begin() + { + return iterator(mHead); + } + + iterator end() + { + return iterator(0); + } +}; + +typedef EventMapList EventMap[NumEvents]; +typedef std::map<IEvent *, int32_t> PendingEventsMap; +typedef std::deque<ComPtr<IEvent> > PassiveQueue; + +class ListenerRecord +{ +private: + ComPtr<IEventListener> mListener; + BOOL const mActive; + EventSource *mOwner; + + RTSEMEVENT mQEvent; + int32_t volatile mQEventBusyCnt; + RTCRITSECT mcsQLock; + PassiveQueue mQueue; + int32_t volatile mRefCnt; + uint64_t mLastRead; + +public: + ListenerRecord(IEventListener *aListener, + com::SafeArray<VBoxEventType_T> &aInterested, + BOOL aActive, + EventSource *aOwner); + ~ListenerRecord(); + + HRESULT process(IEvent *aEvent, BOOL aWaitable, PendingEventsMap::iterator &pit, AutoLockBase &alock); + HRESULT enqueue(IEvent *aEvent); + HRESULT dequeue(IEvent **aEvent, LONG aTimeout, AutoLockBase &aAlock); + HRESULT eventProcessed(IEvent *aEvent, PendingEventsMap::iterator &pit); + void shutdown(); + + void addRef() + { + ASMAtomicIncS32(&mRefCnt); + } + + void release() + { + if (ASMAtomicDecS32(&mRefCnt) <= 0) + delete this; + } + + BOOL isActive() + { + return mActive; + } + + friend class EventSource; +}; + +/* Handy class with semantics close to ComPtr, but for list records */ +template<typename Held> +class RecordHolder +{ +public: + RecordHolder(Held *lr) : + held(lr) + { + addref(); + } + RecordHolder(const RecordHolder &that) : + held(that.held) + { + addref(); + } + RecordHolder() + : + held(0) + { + } + ~RecordHolder() + { + release(); + } + + Held *obj() + { + return held; + } + + RecordHolder &operator=(const RecordHolder &that) + { + safe_assign(that.held); + return *this; + } +private: + Held *held; + + void addref() + { + if (held) + held->addRef(); + } + void release() + { + if (held) + held->release(); + } + void safe_assign(Held *that_p) + { + if (that_p) + that_p->addRef(); + release(); + held = that_p; + } +}; + +typedef std::map<IEventListener *, RecordHolder<ListenerRecord> > Listeners; + +struct EventSource::Data +{ + Data() : fShutdown(false) + {} + + Listeners mListeners; + EventMap mEvMap; + PendingEventsMap mPendingMap; + bool fShutdown; +}; + +/** + * This function defines what wildcard expands to. + */ +static BOOL implies(VBoxEventType_T who, VBoxEventType_T what) +{ + switch (who) + { + case VBoxEventType_Any: + return TRUE; + case VBoxEventType_Vetoable: + return (what == VBoxEventType_OnExtraDataCanChange) + || (what == VBoxEventType_OnCanShowWindow); + case VBoxEventType_MachineEvent: + return (what == VBoxEventType_OnMachineStateChanged) + || (what == VBoxEventType_OnMachineDataChanged) + || (what == VBoxEventType_OnMachineRegistered) + || (what == VBoxEventType_OnSessionStateChanged) + || (what == VBoxEventType_OnGuestPropertyChanged); + case VBoxEventType_SnapshotEvent: + return (what == VBoxEventType_OnSnapshotTaken) + || (what == VBoxEventType_OnSnapshotDeleted) + || (what == VBoxEventType_OnSnapshotChanged) ; + case VBoxEventType_InputEvent: + return (what == VBoxEventType_OnKeyboardLedsChanged) + || (what == VBoxEventType_OnMousePointerShapeChanged) + || (what == VBoxEventType_OnMouseCapabilityChanged); + case VBoxEventType_Invalid: + return FALSE; + default: + break; + } + + return who == what; +} + +ListenerRecord::ListenerRecord(IEventListener *aListener, + com::SafeArray<VBoxEventType_T> &aInterested, + BOOL aActive, + EventSource *aOwner) : + mListener(aListener), mActive(aActive), mOwner(aOwner), mQEventBusyCnt(0), mRefCnt(0) +{ + EventMap *aEvMap = &aOwner->m->mEvMap; + + for (size_t i = 0; i < aInterested.size(); ++i) + { + VBoxEventType_T interested = aInterested[i]; + for (int j = FirstEvent; j < LastEvent; j++) + { + VBoxEventType_T candidate = (VBoxEventType_T)j; + if (implies(interested, candidate)) + { + (*aEvMap)[j - FirstEvent].add(this); + } + } + } + + if (!mActive) + { + ::RTCritSectInit(&mcsQLock); + ::RTSemEventCreate(&mQEvent); + mLastRead = RTTimeMilliTS(); + } + else + { + mQEvent = NIL_RTSEMEVENT; + RT_ZERO(mcsQLock); + mLastRead = 0; + } +} + +ListenerRecord::~ListenerRecord() +{ + /* Remove references to us from the event map */ + EventMap *aEvMap = &mOwner->m->mEvMap; + for (int j = FirstEvent; j < LastEvent; j++) + { + (*aEvMap)[j - FirstEvent].remove(this); + } + + if (!mActive) + { + // at this moment nobody could add elements to our queue, so we can safely + // clean it up, otherwise there will be pending events map elements + PendingEventsMap *aPem = &mOwner->m->mPendingMap; + while (true) + { + ComPtr<IEvent> aEvent; + + if (mQueue.empty()) + break; + + mQueue.front().queryInterfaceTo(aEvent.asOutParam()); + mQueue.pop_front(); + + BOOL fWaitable = FALSE; + aEvent->COMGETTER(Waitable)(&fWaitable); + if (fWaitable) + { + PendingEventsMap::iterator pit = aPem->find(aEvent); + if (pit != aPem->end()) + eventProcessed(aEvent, pit); + } + } + + ::RTCritSectDelete(&mcsQLock); + } + shutdown(); +} + +HRESULT ListenerRecord::process(IEvent *aEvent, + BOOL aWaitable, + PendingEventsMap::iterator &pit, + AutoLockBase &aAlock) +{ + if (mActive) + { + /* + * We release lock here to allow modifying ops on EventSource inside callback. + */ + HRESULT hrc = S_OK; + if (mListener) + { + aAlock.release(); + hrc = mListener->HandleEvent(aEvent); +#ifdef RT_OS_WINDOWS + Assert(hrc != RPC_E_WRONG_THREAD); +#endif + aAlock.acquire(); + } + if (aWaitable) + eventProcessed(aEvent, pit); + return hrc; + } + return enqueue(aEvent); +} + + +HRESULT ListenerRecord::enqueue(IEvent *aEvent) +{ + AssertMsg(!mActive, ("must be passive\n")); + + // put an event the queue + ::RTCritSectEnter(&mcsQLock); + + // If there was no events reading from the listener for the long time, + // and events keep coming, or queue is oversized we shall unregister this listener. + uint64_t sinceRead = RTTimeMilliTS() - mLastRead; + size_t queueSize = mQueue.size(); + if (queueSize > 1000 || (queueSize > 500 && sinceRead > 60 * 1000)) + { + ::RTCritSectLeave(&mcsQLock); + LogRel(("Event: forcefully unregistering passive event listener %p due to excessive queue size\n", this)); + return E_ABORT; + } + + + RTSEMEVENT hEvt = mQEvent; + if (queueSize != 0 && mQueue.back() == aEvent) + /* if same event is being pushed multiple times - it's reusable event and + we don't really need multiple instances of it in the queue */ + hEvt = NIL_RTSEMEVENT; + else if (hEvt != NIL_RTSEMEVENT) /* don't bother queuing after shutdown */ + { + mQueue.push_back(aEvent); + ASMAtomicIncS32(&mQEventBusyCnt); + } + + ::RTCritSectLeave(&mcsQLock); + + // notify waiters unless we've been shut down. + if (hEvt != NIL_RTSEMEVENT) + { + ::RTSemEventSignal(hEvt); + ASMAtomicDecS32(&mQEventBusyCnt); + } + + return S_OK; +} + +HRESULT ListenerRecord::dequeue(IEvent **aEvent, + LONG aTimeout, + AutoLockBase &aAlock) +{ + if (mActive) + return VBOX_E_INVALID_OBJECT_STATE; + + // retain listener record + RecordHolder<ListenerRecord> holder(this); + + ::RTCritSectEnter(&mcsQLock); + + mLastRead = RTTimeMilliTS(); + + /* + * If waiting both desired and necessary, then try grab the event + * semaphore and mark it busy. If it's NIL we've been shut down already. + */ + if (aTimeout != 0 && mQueue.empty()) + { + RTSEMEVENT hEvt = mQEvent; + if (hEvt != NIL_RTSEMEVENT) + { + ASMAtomicIncS32(&mQEventBusyCnt); + ::RTCritSectLeave(&mcsQLock); + + // release lock while waiting, listener will not go away due to above holder + aAlock.release(); + + ::RTSemEventWait(hEvt, aTimeout < 0 ? RT_INDEFINITE_WAIT : (RTMSINTERVAL)aTimeout); + ASMAtomicDecS32(&mQEventBusyCnt); + + // reacquire lock + aAlock.acquire(); + ::RTCritSectEnter(&mcsQLock); + } + } + + if (mQueue.empty()) + *aEvent = NULL; + else + { + mQueue.front().queryInterfaceTo(aEvent); + mQueue.pop_front(); + } + + ::RTCritSectLeave(&mcsQLock); + return S_OK; +} + +HRESULT ListenerRecord::eventProcessed(IEvent *aEvent, PendingEventsMap::iterator &pit) +{ + if (--pit->second == 0) + { + Assert(pit->first == aEvent); + aEvent->SetProcessed(); + mOwner->m->mPendingMap.erase(pit); + } + + return S_OK; +} + +void ListenerRecord::shutdown() +{ + if (mQEvent != NIL_RTSEMEVENT) + { + /* Grab the event semaphore. Must do this while owning the CS or we'll + be racing user wanting to use the handle. */ + ::RTCritSectEnter(&mcsQLock); + RTSEMEVENT hEvt = mQEvent; + mQEvent = NIL_RTSEMEVENT; + ::RTCritSectLeave(&mcsQLock); + + /* + * Signal waiters and wait for them and any other signallers to stop using the sempahore. + * + * Note! RTSemEventDestroy does not necessarily guarantee that waiting threads are + * out of RTSemEventWait or even woken up when it returns. Darwin is (or was?) + * an example of this, the result was undesirable freezes on shutdown. + */ + int32_t cBusy = ASMAtomicReadS32(&mQEventBusyCnt); + if (cBusy > 0) + { + Log(("Wait for %d waiters+signalers to release.\n", cBusy)); + while (cBusy-- > 0) + ::RTSemEventSignal(hEvt); + + for (uint32_t cLoops = 0;; cLoops++) + { + RTThreadSleep(RT_MIN(8, cLoops)); + if (ASMAtomicReadS32(&mQEventBusyCnt) <= 0) + break; + ::RTSemEventSignal(hEvt); /* (Technically unnecessary, but just in case.) */ + } + Log(("All waiters+signalers just released the lock.\n")); + } + + ::RTSemEventDestroy(hEvt); + } +} + +EventSource::EventSource() +{} + +EventSource::~EventSource() +{} + +HRESULT EventSource::FinalConstruct() +{ + m = new Data; + return BaseFinalConstruct(); +} + +void EventSource::FinalRelease() +{ + uninit(); + delete m; + BaseFinalRelease(); +} + +HRESULT EventSource::init() +{ + AutoInitSpan autoInitSpan(this); + AssertReturn(autoInitSpan.isOk(), E_FAIL); + + /* Confirm a successful initialization */ + autoInitSpan.setSucceeded(); + return S_OK; +} + +void EventSource::uninit() +{ + { + // First of all (before even thinking about entering the uninit span): + // make sure that all listeners are are shut down (no pending events or + // wait calls), because they cannot be alive without the associated + // event source. Otherwise API clients which use long-term (or + // indefinite) waits will block VBoxSVC termination (just one example) + // for a long time or even infinitely long. + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + if (!m->fShutdown) + { + m->fShutdown = true; + for (Listeners::iterator it = m->mListeners.begin(); + it != m->mListeners.end(); + ++it) + { + it->second.obj()->shutdown(); + } + } + } + + AutoUninitSpan autoUninitSpan(this); + if (autoUninitSpan.uninitDone()) + return; + + m->mListeners.clear(); + // m->mEvMap shall be cleared at this point too by destructors, assert? +} + +HRESULT EventSource::registerListener(const ComPtr<IEventListener> &aListener, + const std::vector<VBoxEventType_T> &aInteresting, + BOOL aActive) +{ + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + if (m->fShutdown) + return setError(VBOX_E_INVALID_OBJECT_STATE, + tr("This event source is already shut down")); + + Listeners::const_iterator it = m->mListeners.find(aListener); + if (it != m->mListeners.end()) + return setError(E_INVALIDARG, + tr("This listener already registered")); + + com::SafeArray<VBoxEventType_T> interested(aInteresting); + RecordHolder<ListenerRecord> lrh(new ListenerRecord(aListener, interested, aActive, this)); + m->mListeners.insert(Listeners::value_type((IEventListener *)aListener, lrh)); + + ::FireEventSourceChangedEvent(this, (IEventListener *)aListener, TRUE /*add*/); + + return S_OK; +} + +HRESULT EventSource::unregisterListener(const ComPtr<IEventListener> &aListener) +{ + HRESULT hrc = S_OK;; + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + Listeners::iterator it = m->mListeners.find(aListener); + + if (it != m->mListeners.end()) + { + it->second.obj()->shutdown(); + m->mListeners.erase(it); + // destructor removes refs from the event map + ::FireEventSourceChangedEvent(this, (IEventListener *)aListener, FALSE /*add*/); + hrc = S_OK; + } + else + hrc = setError(VBOX_E_OBJECT_NOT_FOUND, + tr("Listener was never registered")); + + return hrc; +} + +HRESULT EventSource::fireEvent(const ComPtr<IEvent> &aEvent, + LONG aTimeout, + BOOL *aResult) +{ + /* Get event attributes before take the source lock: */ + BOOL fWaitable = FALSE; + HRESULT hrc = aEvent->COMGETTER(Waitable)(&fWaitable); + AssertComRC(hrc); + + VBoxEventType_T evType; + hrc = aEvent->COMGETTER(Type)(&evType); + AssertComRCReturn(hrc, hrc); + + { + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + if (m->fShutdown) + return setError(VBOX_E_INVALID_OBJECT_STATE, + tr("This event source is already shut down")); + + EventMapList &listeners = m->mEvMap[(int)evType - FirstEvent]; + + /* Anyone interested in this event? */ + uint32_t cListeners = listeners.size(); + if (cListeners == 0) + { + aEvent->SetProcessed(); + // just leave the lock and update event object state + } + else + { + PendingEventsMap::iterator pit; + if (fWaitable) + { + m->mPendingMap.insert(PendingEventsMap::value_type(aEvent, cListeners)); + // we keep iterator here to allow processing active listeners without + // pending events lookup + pit = m->mPendingMap.find(aEvent); + } + + for (EventMapList::iterator it = listeners.begin(); + it != listeners.end(); + ++it) + { + // keep listener record reference, in case someone will remove it while in callback + RecordHolder<ListenerRecord> record(*it); + + /* + * We pass lock here to allow modifying ops on EventSource inside callback + * in active mode. Note that we expect list iterator stability as 'alock' + * could be temporary released when calling event handler. + */ + HRESULT cbRc = record.obj()->process(aEvent, fWaitable, pit, alock); + + /* Note that E_ABORT is used above to signal that a passive + * listener was unregistered due to not picking up its event. + * This overlaps with XPCOM specific use of E_ABORT to signal + * death of an active listener, but that's irrelevant here. */ + if (FAILED_DEAD_INTERFACE(cbRc) || cbRc == E_ABORT) + { + Listeners::iterator lit = m->mListeners.find(record.obj()->mListener); + if (lit != m->mListeners.end()) + { + lit->second.obj()->shutdown(); + m->mListeners.erase(lit); + } + } + // anything else to do with cbRc? + } + } + } + /* We leave the lock here */ + + if (fWaitable) + { + hrc = aEvent->WaitProcessed(aTimeout, aResult); + + /* + * If a passive listener times out without processing a vetoable event then we + * remove that event from the list of events this listener is interested in. + */ + if (!*aResult && hrc == E_ABORT && implies(VBoxEventType_Vetoable, evType)) + { + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + EventMapList &listeners = m->mEvMap[(int)evType - FirstEvent]; + for (EventMapList::iterator it = listeners.begin(); + it != listeners.end(); + ++it) + { + RecordHolder<ListenerRecord> record(*it); + if (record.obj()->mQueue.size() != 0 && record.obj()->mQueue.back() == aEvent) + m->mEvMap[(int)evType - FirstEvent].remove(record.obj()); + } + + PendingEventsMap::iterator pit = m->mPendingMap.find(aEvent); + if (pit != m->mPendingMap.end()) + m->mPendingMap.erase(pit); + + /* + * VBoxEventDesc::fire() requires TRUE to be returned so it can handle + * vetoable events. + */ + return S_OK; + } + } + else + *aResult = TRUE; + + return hrc; +} + +HRESULT EventSource::getEvent(const ComPtr<IEventListener> &aListener, + LONG aTimeout, + ComPtr<IEvent> &aEvent) +{ + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + if (m->fShutdown) + return setError(VBOX_E_INVALID_OBJECT_STATE, + tr("This event source is already shut down")); + + Listeners::iterator it = m->mListeners.find(aListener); + HRESULT hrc = S_OK; + + if (it != m->mListeners.end()) + hrc = it->second.obj()->dequeue(aEvent.asOutParam(), aTimeout, alock); + else + hrc = setError(VBOX_E_OBJECT_NOT_FOUND, + tr("Listener was never registered")); + + if (hrc == VBOX_E_INVALID_OBJECT_STATE) + return setError(hrc, tr("Listener must be passive")); + + return hrc; +} + +HRESULT EventSource::eventProcessed(const ComPtr<IEventListener> &aListener, + const ComPtr<IEvent> &aEvent) +{ + BOOL fWaitable = FALSE; + HRESULT hrc = aEvent->COMGETTER(Waitable)(&fWaitable); + AssertComRC(hrc); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + + if (m->fShutdown) + return setError(VBOX_E_INVALID_OBJECT_STATE, + tr("This event source is already shut down")); + + Listeners::iterator it = m->mListeners.find(aListener); + + if (it != m->mListeners.end()) + { + ListenerRecord *aRecord = it->second.obj(); + + if (aRecord->isActive()) + return setError(E_INVALIDARG, + tr("Only applicable to passive listeners")); + + if (fWaitable) + { + PendingEventsMap::iterator pit = m->mPendingMap.find(aEvent); + + if (pit == m->mPendingMap.end()) + { + AssertFailed(); + hrc = setError(VBOX_E_OBJECT_NOT_FOUND, + tr("Unknown event")); + } + else + hrc = aRecord->eventProcessed(aEvent, pit); + } + else + { + // for non-waitable events we're done + hrc = S_OK; + } + } + else + hrc = setError(VBOX_E_OBJECT_NOT_FOUND, + tr("Listener was never registered")); + + return hrc; +} + +/** + * This class serves as feasible listener implementation + * which could be used by clients not able to create local + * COM objects, but still willing to receive event + * notifications in passive mode, such as webservices. + */ +class ATL_NO_VTABLE PassiveEventListener : + public VirtualBoxBase, + VBOX_SCRIPTABLE_IMPL(IEventListener) +{ +public: + + VIRTUALBOXBASE_ADD_ERRORINFO_SUPPORT(PassiveEventListener, IEventListener) + + DECLARE_NOT_AGGREGATABLE(PassiveEventListener) + + DECLARE_PROTECT_FINAL_CONSTRUCT() + + BEGIN_COM_MAP(PassiveEventListener) + COM_INTERFACE_ENTRY(ISupportErrorInfo) + COM_INTERFACE_ENTRY(IEventListener) + COM_INTERFACE_ENTRY2(IDispatch, IEventListener) + VBOX_TWEAK_INTERFACE_ENTRY(IEventListener) + END_COM_MAP() + + PassiveEventListener() + {} + ~PassiveEventListener() + {} + + HRESULT FinalConstruct() + { + return BaseFinalConstruct(); + } + void FinalRelease() + { + BaseFinalRelease(); + } + + // IEventListener methods + STDMETHOD(HandleEvent)(IEvent *) + { + ComAssertMsgRet(false, (tr("HandleEvent() of wrapper shall never be called")), + E_FAIL); + } +}; + +/* Proxy listener class, used to aggregate multiple event sources into one */ +class ATL_NO_VTABLE ProxyEventListener : + public VirtualBoxBase, + VBOX_SCRIPTABLE_IMPL(IEventListener) +{ + ComPtr<IEventSource> mSource; +public: + + VIRTUALBOXBASE_ADD_ERRORINFO_SUPPORT(ProxyEventListener, IEventListener) + + DECLARE_NOT_AGGREGATABLE(ProxyEventListener) + + DECLARE_PROTECT_FINAL_CONSTRUCT() + + BEGIN_COM_MAP(ProxyEventListener) + COM_INTERFACE_ENTRY(ISupportErrorInfo) + COM_INTERFACE_ENTRY(IEventListener) + COM_INTERFACE_ENTRY2(IDispatch, IEventListener) + VBOX_TWEAK_INTERFACE_ENTRY(IEventListener) + END_COM_MAP() + + ProxyEventListener() + {} + ~ProxyEventListener() + {} + + HRESULT FinalConstruct() + { + return BaseFinalConstruct(); + } + void FinalRelease() + { + BaseFinalRelease(); + } + + HRESULT init(IEventSource *aSource) + { + mSource = aSource; + return S_OK; + } + + // IEventListener methods + STDMETHOD(HandleEvent)(IEvent *aEvent) + { + BOOL fProcessed = FALSE; + if (mSource) + return mSource->FireEvent(aEvent, 0, &fProcessed); + else + return S_OK; + } +}; + +class ATL_NO_VTABLE EventSourceAggregator : + public VirtualBoxBase, + VBOX_SCRIPTABLE_IMPL(IEventSource) +{ + typedef std::list <ComPtr<IEventSource> > EventSourceList; + /* key is weak reference */ + typedef std::map<IEventListener *, ComPtr<IEventListener> > ProxyListenerMap; + + EventSourceList mEventSources; + ProxyListenerMap mListenerProxies; + ComObjPtr<EventSource> mSource; + +public: + + VIRTUALBOXBASE_ADD_ERRORINFO_SUPPORT(EventSourceAggregator, IEventSource) + + DECLARE_NOT_AGGREGATABLE(EventSourceAggregator) + + DECLARE_PROTECT_FINAL_CONSTRUCT() + + BEGIN_COM_MAP(EventSourceAggregator) + COM_INTERFACE_ENTRY(ISupportErrorInfo) + COM_INTERFACE_ENTRY(IEventSource) + COM_INTERFACE_ENTRY2(IDispatch, IEventSource) + VBOX_TWEAK_INTERFACE_ENTRY(IEventSource) + END_COM_MAP() + + EventSourceAggregator() + {} + ~EventSourceAggregator() + {} + + HRESULT FinalConstruct() + { + return BaseFinalConstruct(); + } + void FinalRelease() + { + mEventSources.clear(); + mListenerProxies.clear(); + mSource->uninit(); + BaseFinalRelease(); + } + + // internal public + HRESULT init(const std::vector<ComPtr<IEventSource> > aSourcesIn); + + // IEventSource methods + STDMETHOD(CreateListener)(IEventListener **aListener); + STDMETHOD(CreateAggregator)(ComSafeArrayIn(IEventSource *, aSubordinates), + IEventSource **aAggregator); + STDMETHOD(RegisterListener)(IEventListener *aListener, + ComSafeArrayIn(VBoxEventType_T, aInterested), + BOOL aActive); + STDMETHOD(UnregisterListener)(IEventListener *aListener); + STDMETHOD(FireEvent)(IEvent *aEvent, + LONG aTimeout, + BOOL *aProcessed); + STDMETHOD(GetEvent)(IEventListener *aListener, + LONG aTimeout, + IEvent **aEvent); + STDMETHOD(EventProcessed)(IEventListener *aListener, + IEvent *aEvent); + + protected: + HRESULT createProxyListener(IEventListener *aListener, + IEventListener **aProxy); + HRESULT getProxyListener(IEventListener *aListener, + IEventListener **aProxy); + HRESULT removeProxyListener(IEventListener *aListener); +}; + +#ifdef VBOX_WITH_XPCOM +NS_DECL_CLASSINFO(ProxyEventListener) +NS_IMPL_THREADSAFE_ISUPPORTS1_CI(ProxyEventListener, IEventListener) +NS_DECL_CLASSINFO(PassiveEventListener) +NS_IMPL_THREADSAFE_ISUPPORTS1_CI(PassiveEventListener, IEventListener) +NS_DECL_CLASSINFO(EventSourceAggregator) +NS_IMPL_THREADSAFE_ISUPPORTS1_CI(EventSourceAggregator, IEventSource) +#endif + + +HRESULT EventSource::createListener(ComPtr<IEventListener> &aListener) +{ + ComObjPtr<PassiveEventListener> listener; + + HRESULT hrc = listener.createObject(); + ComAssertMsgRet(SUCCEEDED(hrc), (tr("Could not create wrapper object (%Rhrc)"), hrc), + E_FAIL); + listener.queryInterfaceTo(aListener.asOutParam()); + return S_OK; +} + +HRESULT EventSource::createAggregator(const std::vector<ComPtr<IEventSource> > &aSubordinates, + ComPtr<IEventSource> &aResult) +{ + ComObjPtr<EventSourceAggregator> agg; + + HRESULT hrc = agg.createObject(); + ComAssertMsgRet(SUCCEEDED(hrc), (tr("Could not create aggregator (%Rhrc)"), hrc), + E_FAIL); + + hrc = agg->init(aSubordinates); + if (FAILED(hrc)) + return hrc; + + agg.queryInterfaceTo(aResult.asOutParam()); + return S_OK; +} + +HRESULT EventSourceAggregator::init(const std::vector<ComPtr<IEventSource> > aSourcesIn) +{ + AutoInitSpan autoInitSpan(this); + AssertReturn(autoInitSpan.isOk(), E_FAIL); + + HRESULT hrc = mSource.createObject(); + ComAssertMsgRet(SUCCEEDED(hrc), (tr("Could not create source (%Rhrc)"), hrc), + E_FAIL); + hrc = mSource->init(); + ComAssertMsgRet(SUCCEEDED(hrc), (tr("Could not init source (%Rhrc)"), hrc), + E_FAIL); + + for (size_t i = 0; i < aSourcesIn.size(); i++) + { + if (aSourcesIn[i] != NULL) + mEventSources.push_back(aSourcesIn[i]); + } + + /* Confirm a successful initialization */ + autoInitSpan.setSucceeded(); + + return hrc; +} + +STDMETHODIMP EventSourceAggregator::CreateListener(IEventListener **aListener) +{ + return mSource->CreateListener(aListener); +} + +STDMETHODIMP EventSourceAggregator::CreateAggregator(ComSafeArrayIn(IEventSource *, aSubordinates), + IEventSource **aResult) +{ + return mSource->CreateAggregator(ComSafeArrayInArg(aSubordinates), aResult); +} + +STDMETHODIMP EventSourceAggregator::RegisterListener(IEventListener *aListener, + ComSafeArrayIn(VBoxEventType_T, aInterested), + BOOL aActive) +{ + CheckComArgNotNull(aListener); + CheckComArgSafeArrayNotNull(aInterested); + + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) + return autoCaller.rc(); + + ComPtr<IEventListener> proxy; + HRESULT hrc = createProxyListener(aListener, proxy.asOutParam()); + if (FAILED(hrc)) + return hrc; + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + for (EventSourceList::const_iterator it = mEventSources.begin(); it != mEventSources.end(); + ++it) + { + ComPtr<IEventSource> es = *it; + /* Register active proxy listener on real event source */ + hrc = es->RegisterListener(proxy, ComSafeArrayInArg(aInterested), TRUE); + } + /* And add real listener on our event source */ + hrc = mSource->RegisterListener(aListener, ComSafeArrayInArg(aInterested), aActive); + + return S_OK; +} + +STDMETHODIMP EventSourceAggregator::UnregisterListener(IEventListener *aListener) +{ + CheckComArgNotNull(aListener); + + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) + return autoCaller.rc(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + + ComPtr<IEventListener> proxy; + HRESULT hrc = getProxyListener(aListener, proxy.asOutParam()); + if (FAILED(hrc)) + return hrc; + + for (EventSourceList::const_iterator it = mEventSources.begin(); it != mEventSources.end(); + ++it) + { + ComPtr<IEventSource> es = *it; + hrc = es->UnregisterListener(proxy); + } + hrc = mSource->UnregisterListener(aListener); + + return removeProxyListener(aListener); + +} + +STDMETHODIMP EventSourceAggregator::FireEvent(IEvent *aEvent, + LONG aTimeout, + BOOL *aProcessed) +{ + CheckComArgNotNull(aEvent); + CheckComArgOutPointerValid(aProcessed); + + AutoCaller autoCaller(this); + if (FAILED(autoCaller.rc())) + return autoCaller.rc(); + + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + /* Aggregator event source shall not have direct event firing, but we may + wish to support aggregation chains */ + for (EventSourceList::const_iterator it = mEventSources.begin(); it != mEventSources.end(); + ++it) + { + ComPtr<IEventSource> es = *it; + HRESULT hrc = es->FireEvent(aEvent, aTimeout, aProcessed); + /* Current behavior is that aggregator's FireEvent() always succeeds, + so that multiple event sources don't affect each other. */ + NOREF(hrc); + } + + return S_OK; +} + +STDMETHODIMP EventSourceAggregator::GetEvent(IEventListener *aListener, + LONG aTimeout, + IEvent **aEvent) +{ + return mSource->GetEvent(aListener, aTimeout, aEvent); +} + +STDMETHODIMP EventSourceAggregator::EventProcessed(IEventListener *aListener, + IEvent *aEvent) +{ + return mSource->EventProcessed(aListener, aEvent); +} + +HRESULT EventSourceAggregator::createProxyListener(IEventListener *aListener, + IEventListener **aProxy) +{ + ComObjPtr<ProxyEventListener> proxy; + + HRESULT hrc = proxy.createObject(); + ComAssertMsgRet(SUCCEEDED(hrc), (tr("Could not create proxy (%Rhrc)"), hrc), + E_FAIL); + + hrc = proxy->init(mSource); + if (FAILED(hrc)) + return hrc; + + ProxyListenerMap::const_iterator it = mListenerProxies.find(aListener); + if (it != mListenerProxies.end()) + return setError(E_INVALIDARG, + tr("This listener already registered")); + + mListenerProxies.insert(ProxyListenerMap::value_type(aListener, proxy)); + + proxy.queryInterfaceTo(aProxy); + return S_OK; +} + +HRESULT EventSourceAggregator::getProxyListener(IEventListener *aListener, + IEventListener **aProxy) +{ + ProxyListenerMap::const_iterator it = mListenerProxies.find(aListener); + if (it == mListenerProxies.end()) + return setError(E_INVALIDARG, + tr("This listener never registered")); + + (*it).second.queryInterfaceTo(aProxy); + return S_OK; +} + +HRESULT EventSourceAggregator::removeProxyListener(IEventListener *aListener) +{ + ProxyListenerMap::iterator it = mListenerProxies.find(aListener); + if (it == mListenerProxies.end()) + return setError(E_INVALIDARG, + tr("This listener never registered")); + + mListenerProxies.erase(it); + return S_OK; +} |