EventImpl.cpp revision 8019ec0b8df2f4a4a90f6222689297533bd83fe4
/* $Id$ */
/** @file
* VirtualBox COM Event class implementation
*/
/*
* Copyright (C) 2010 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*/
/**
* 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 addded 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.
*/
#include <list>
#include <map>
#include <deque>
#include "EventImpl.h"
#include "AutoCaller.h"
#include "Logging.h"
#include <iprt/semaphore.h>
#include <iprt/critsect.h>
{
Data()
:
{}
};
{
m = new Data;
return S_OK;
}
void VBoxEvent::FinalRelease()
{
if (m)
{
uninit();
delete m;
m = 0;
}
}
{
AutoInitSpan autoInitSpan(this);
m->mProcessed = !aWaitable;
do {
if (aWaitable)
{
if (RT_FAILURE(vrc))
{
AssertFailed ();
}
}
} while (0);
/* Confirm a successful initialization */
return rc;
}
{
if (!m)
return;
m->mProcessed = TRUE;
m->mType = VBoxEventType_Invalid;
if (m->mWaitEvent != NIL_RTSEMEVENT)
{
::RTSemEventDestroy(m->mWaitEvent);
m->mWaitEvent = NIL_RTSEMEVENT;
}
}
{
AutoCaller autoCaller(this);
// never changes till event alive, no locking?
return S_OK;
}
{
AutoCaller autoCaller(this);
return S_OK;
}
{
AutoCaller autoCaller(this);
// never changes till event alive, no locking?
return S_OK;
}
{
AutoCaller autoCaller(this);
if (m->mProcessed)
return S_OK;
m->mProcessed = TRUE;
// notify waiters
::RTSemEventSignal(m->mWaitEvent);
return S_OK;
}
{
AutoCaller autoCaller(this);
{
if (m->mProcessed)
{
return S_OK;
}
if (aTimeout == 0)
{
*aResult = m->mProcessed;
return S_OK;
}
}
/* @todo: maybe while loop for spurious wakeups? */
("RTSemEventWait returned %Rrc\n", vrc));
if (RT_SUCCESS(vrc))
{
AssertMsg(m->mProcessed,
("mProcessed must be set here\n"));
*aResult = m->mProcessed;
}
else
{
}
return S_OK;
}
struct VBoxVetoEvent::Data
{
Data()
:
{}
};
{
m = new Data;
return S_OK;
}
void VBoxVetoEvent::FinalRelease()
{
if (m)
{
uninit();
delete m;
m = 0;
}
}
{
// all veto events are waitable
return rc;
}
void VBoxVetoEvent::uninit()
{
if (!m)
return;
}
{
AutoCaller autoCaller(this);
if (aVeto)
return S_OK;
}
{
AutoCaller autoCaller(this);
return S_OK;
}
{
if (ComSafeArrayOutIsNull(aVetos))
return E_POINTER;
AutoCaller autoCaller(this);
int i = 0;
++it, ++i)
{
}
return S_OK;
}
static const int LastEvent = (int)VBoxEventType_Last;
class ListenerRecord;
class ListenerRecord
{
private:
public:
~ListenerRecord();
HRESULT process(IEvent* aEvent, BOOL aWaitable, PendingEventsMap::iterator& pit, AutoLockBase& alock);
void addRef()
{
}
void release()
{
if (ASMAtomicDecS32(&mRefCnt) <= 0) delete this;
}
{
return mActive;
}
friend class EventSource;
};
/* Handy class with semantics close to ComPtr, but for ListenerRecord */
class ListenerRecordHolder
{
public:
:
{
addref();
}
:
{
addref();
}
:
held(0)
{
}
{
release();
}
{
return held;
}
{
return *this;
}
private:
void addref()
{
if (held)
}
void release()
{
if (held)
}
{
if (that_p)
release();
}
};
struct EventSource::Data
{
Data() {}
};
/**
* This function defines what wildcard expands to.
*/
{
switch (who)
{
case VBoxEventType_Any:
return TRUE;
return (what == VBoxEventType_OnMachineStateChange)
|| (what == VBoxEventType_OnGuestPropertyChange);
return (what == VBoxEventType_OnSnapshotTaken)
|| (what == VBoxEventType_OnSnapshotDeleted)
|| (what == VBoxEventType_OnSnapshotChange)
;
case VBoxEventType_Invalid:
return FALSE;
}
}
:
mRefCnt(0)
{
{
for (int j = FirstEvent; j < LastEvent; j++)
{
{
}
}
}
if (!mActive)
{
::RTCritSectInit(&mcsQLock);
::RTSemEventCreate (&mQEvent);
}
}
{
/* Remove references to us from the event map */
for (int j = FirstEvent; j < LastEvent; j++)
{
}
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
while (true)
{
break;
if (aWaitable)
{
}
}
::RTCritSectDelete(&mcsQLock);
}
}
{
if (mActive)
{
/*
* We release lock here to allow modifying ops on EventSource inside callback.
*/
if (mListener)
{
}
if (aWaitable)
return rc;
}
else
}
{
// put an event the queue
::RTCritSectEnter(&mcsQLock);
::RTCritSectLeave(&mcsQLock);
// notify waiters
return S_OK;
}
{
::RTCritSectEnter(&mcsQLock);
{
// retain listener record
ListenerRecordHolder holder(this);
::RTCritSectLeave(&mcsQLock);
// Speed up common case
if (aTimeout == 0)
{
return S_OK;
}
// release lock while waiting, listener will not go away due to above holder
// reacquire lock
::RTCritSectEnter(&mcsQLock);
}
{
}
else
{
}
::RTCritSectLeave(&mcsQLock);
return S_OK;
}
{
{
aEvent->SetProcessed();
}
return S_OK;
}
{}
{}
{
m = new Data;
return S_OK;
}
void EventSource::FinalRelease()
{
uninit();
delete m;
}
{
AutoInitSpan autoInitSpan(this);
/* Confirm a successful initialization */
return rc;
}
void EventSource::uninit()
{
AutoUninitSpan autoUninitSpan(this);
if (autoUninitSpan.uninitDone())
return;
m->mListeners.clear();
// m->mEvMap shall be cleared at this point too by destructors, assert?
}
{
AutoCaller autoCaller(this);
{
return setError(E_INVALIDARG,
tr("This listener already registered"));
}
return S_OK;
}
{
AutoCaller autoCaller(this);
{
{
// destructor removes refs from the event map
}
else
{
tr("Listener was never registered"));
}
}
{
}
return rc;
}
{
AutoCaller autoCaller(this);
do {
/* See comment in EventSource::GetEvent() */
/* Anyone interested in this event? */
if (cListeners == 0)
{
aEvent->SetProcessed();
break; // just leave the lock and update event object state
}
if (aWaitable)
{
// we keep iterator here to allow processing active listeners without
// pending events lookup
}
{
// keep listener record reference, in case someone will remove it while in callback
/**
* 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.
*/
if (FAILED_DEAD_INTERFACE(cbRc))
{
}
// anything else to do with cbRc?
}
} while (0);
/* We leave the lock here */
if (aWaitable)
else
*aProcessed = TRUE;
return hrc;
}
{
AutoCaller autoCaller(this);
/**
* There's subtle dependency between this lock and one in FireEvent():
* we need to be able to access event queue in FireEvent() while waiting
* here, to make this wait preemptible, thus both take read lock (write
* lock in FireEvent() would do too, and probably is a bit stricter),
* but will be unable to .
*/
else
tr("Listener was never registered"));
return rc;
}
{
AutoCaller autoCaller(this);
{
return setError(E_INVALIDARG,
tr("Only applicable to passive listeners"));
if (aWaitable)
{
{
AssertFailed();
tr("Unknown event"));
}
else
}
else
{
// for non-waitable events we're done
}
}
else
{
tr("Listener was never registered"));
}
return rc;
}
/**
* 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,
{
public:
{}
{}
{
return S_OK;
}
void FinalRelease()
{}
// IEventListener methods
{
ComAssertMsgRet(false, ("HandleEvent() of wrapper shall never be called"),
E_FAIL);
}
// for VirtualBoxSupportErrorInfoImpl
static const wchar_t *getComponentName() { return L"PassiveEventListener"; }
};
#ifdef VBOX_WITH_XPCOM
#endif
{
AutoCaller autoCaller(this);
E_FAIL);
return S_OK;
}