EventQueue.cpp revision 4f7bc3e9b035984d494d38afa2913fa560e259d6
/* $Id$ */
/** @file
*
* MS COM / XPCOM Abstraction Layer:
* Event and EventQueue class declaration
*/
/*
* Copyright (C) 2006-2007 Sun Microsystems, Inc.
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* you can redistribute it and/or modify it under the terms of the GNU
* 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.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
* Clara, CA 95054 USA or visit http://www.sun.com if you need
* additional information or have any questions.
*/
#include "VBox/com/EventQueue.h"
#ifdef RT_OS_DARWIN
# include <CoreFoundation/CFRunLoop.h>
#endif
#if defined(VBOX_WITH_XPCOM) && !defined(RT_OS_DARWIN) && !defined(RT_OS_OS2)
# define USE_XPCOM_QUEUE
#endif
#include <iprt/err.h>
#include <iprt/time.h>
#include <iprt/thread.h>
#ifdef USE_XPCOM_QUEUE
# include <errno.h>
#endif
namespace com
{
// EventQueue class
////////////////////////////////////////////////////////////////////////////////
#if defined (RT_OS_WINDOWS)
#define CHECK_THREAD_RET(ret) \
do { \
AssertMsg (GetCurrentThreadId() == mThreadId, ("Must be on event queue thread!")); \
if (GetCurrentThreadId() != mThreadId) \
return ret; \
} while (0)
#else // !defined (RT_OS_WINDOWS)
#define CHECK_THREAD_RET(ret) \
do { \
if (!mEventQ) \
return ret; \
BOOL isOnCurrentThread = FALSE; \
mEventQ->IsOnCurrentThread (&isOnCurrentThread); \
AssertMsg (isOnCurrentThread, ("Must be on event queue thread!")); \
if (!isOnCurrentThread) \
return ret; \
} while (0)
#endif // !defined (RT_OS_WINDOWS)
EventQueue *EventQueue::mMainQueue = NULL;
/**
* Constructs an event queue for the current thread.
*
* Currently, there can be only one event queue per thread, so if an event
* queue for the current thread already exists, this object is simply attached
* to the existing event queue.
*/
EventQueue::EventQueue()
{
#if defined (RT_OS_WINDOWS)
mThreadId = GetCurrentThreadId();
// force the system to create the message queue for the current thread
MSG msg;
PeekMessage (&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
if (!DuplicateHandle (GetCurrentProcess(),
GetCurrentThread(),
GetCurrentProcess(),
&mhThread,
0 /*dwDesiredAccess*/,
FALSE /*bInheritHandle*/,
DUPLICATE_SAME_ACCESS))
mhThread = INVALID_HANDLE_VALUE;
#else
mEQCreated = FALSE;
mLastEvent = NULL;
mGotEvent = FALSE;
// Here we reference the global nsIEventQueueService instance and hold it
// until we're destroyed. This is necessary to keep NS_ShutdownXPCOM() away
// from calling StopAcceptingEvents() on all event queues upon destruction of
// nsIEventQueueService, and makes sense when, for some reason, this happens
// *before* we're able to send a NULL event to stop our event handler thread
// when doing unexpected cleanup caused indirectly by NS_ShutdownXPCOM()
// that is performing a global cleanup of everything. A good example of such
// situation is when NS_ShutdownXPCOM() is called while the VirtualBox component
// is still alive (because it is still referenced): eventually, it results in
// a VirtualBox::uninit() call from where it is already not possible to post
// NULL to the event thread (because it stopped accepting events).
nsresult rc = NS_GetEventQueueService (getter_AddRefs (mEventQService));
if (NS_SUCCEEDED(rc))
{
rc = mEventQService->GetThreadEventQueue (NS_CURRENT_THREAD,
getter_AddRefs (mEventQ));
if (rc == NS_ERROR_NOT_AVAILABLE)
{
rc = mEventQService->CreateMonitoredThreadEventQueue();
if (NS_SUCCEEDED(rc))
{
mEQCreated = TRUE;
rc = mEventQService->GetThreadEventQueue (NS_CURRENT_THREAD,
getter_AddRefs (mEventQ));
}
}
}
AssertComRC (rc);
#endif
}
EventQueue::~EventQueue()
{
#if defined (RT_OS_WINDOWS)
if (mhThread != INVALID_HANDLE_VALUE)
{
CloseHandle (mhThread);
mhThread = INVALID_HANDLE_VALUE;
}
#else
// process all pending events before destruction
if (mEventQ)
{
if (mEQCreated)
{
mEventQ->StopAcceptingEvents();
mEventQ->ProcessPendingEvents();
mEventQService->DestroyThreadEventQueue();
}
mEventQ = nsnull;
mEventQService = nsnull;
}
#endif
}
/**
* Initializes the main event queue instance.
* @returns VBox status code.
*
* @remarks If you're using the rest of the COM/XPCOM glue library,
* com::Initialize() will take care of initializing and uninitializing
* the EventQueue class. If you don't call com::Initialize, you must
* make sure to call this method on the same thread that did the
* XPCOM initialization or we'll end up using the wrong main queue.
*/
/* static */ int
EventQueue::init()
{
Assert(mMainQueue == NULL);
Assert(RTThreadIsMain(RTThreadSelf()));
mMainQueue = new EventQueue();
#if defined (VBOX_WITH_XPCOM)
/* Check that it actually is the main event queue, i.e. that
we're called on the right thread. */
nsCOMPtr<nsIEventQueue> q;
nsresult rv = NS_GetMainEventQ(getter_AddRefs(q));
Assert(NS_SUCCEEDED(rv));
Assert(q == mMainQueue->mEventQ);
/* Check that it's a native queue. */
PRBool fIsNative = PR_FALSE;
rv = mMainQueue->mEventQ->IsQueueNative(&fIsNative);
Assert(NS_SUCCEEDED(rv) && fIsNative);
#endif
return VINF_SUCCESS;
}
/**
* Uninitialize the global resources (i.e. the main event queue instance).
* @returns VINF_SUCCESS
*/
/* static */ int
EventQueue::uninit()
{
Assert(mMainQueue);
delete mMainQueue;
mMainQueue = NULL;
return VINF_SUCCESS;
}
/**
* Get main event queue instance.
*
* Depends on init() being called first.
*/
/* static */ EventQueue *
EventQueue::getMainEventQueue()
{
return mMainQueue;
}
#ifdef RT_OS_DARWIN
/**
* Wait for events and process them (Darwin).
*
* @returns VINF_SUCCESS or VERR_TIMEOUT.
*
* @param cMsTimeout How long to wait, or RT_INDEFINITE_WAIT.
*/
static int
waitForEventsOnDarwin(unsigned cMsTimeout)
{
/*
* Wait for the requested time, if we get a hit we do a poll to process
* any other pending messages.
*
* Note! About 1.0e10: According to the sources anything above 3.1556952e+9
* means indefinite wait and 1.0e10 is what CFRunLoopRun() uses.
*/
CFTimeInterval rdTimeout = cMsTimeout == RT_INDEFINITE_WAIT ? 1e10 : (double)cMsTimeout / 1000;
OSStatus orc = CFRunLoopRunInMode(kCFRunLoopDefaultMode, rdTimeout, true /*returnAfterSourceHandled*/);
/** @todo Not entire sure if the poll actually processes more than one message.
* Feel free to check the sources anyone. */
if (orc == kCFRunLoopRunHandledSource)
orc = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, false /*returnAfterSourceHandled*/);
if ( orc == 0
|| orc == kCFRunLoopRunHandledSource)
return VINF_SUCCESS;
if ( orc == kCFRunLoopRunStopped
|| orc == kCFRunLoopRunFinished)
return VERR_INTERRUPTED;
AssertMsg(orc == kCFRunLoopRunTimedOut, ("Unexpected status code from CFRunLoopRunInMode: %#x", orc));
return VERR_TIMEOUT;
}
#elif !defined(RT_OS_WINDOWS)
/**
* Wait for events (Unix).
*
* @returns VINF_SUCCESS or VERR_TIMEOUT.
*
* @param pQueue The queue to wait on.
* @param cMsTimeout How long to wait, or RT_INDEFINITE_WAIT.
*/
static int
waitForEventsOnUnix(nsIEventQueue *pQueue, unsigned cMsTimeout)
{
int fd = pQueue->GetEventQueueSelectFD();
fd_set fdsetR;
FD_ZERO(&fdsetR);
FD_SET(fd, &fdsetR);
fd_set fdsetE = fdsetR;
struct timeval tv = {0,0};
struct timeval *ptv;
if (cMsTimeout == RT_INDEFINITE_WAIT)
ptv = NULL;
else
{
tv.tv_sec = cMsTimeout / 1000;
tv.tv_usec = (cMsTimeout % 1000) * 1000;
ptv = &tv;
}
int rc = select(fd + 1, &fdsetR, NULL, &fdsetE, ptv);
if (rc > 0)
rc = VINF_SUCCESS;
else if (rc == 0)
rc = VERR_TIMEOUT;
else if (errno == EINTR)
rc = VERR_INTERRUPTED;
else
{
AssertMsgFailed(("rc=%d errno=%d\n", rc, errno));
rc = VERR_INTERNAL_ERROR_4;
}
return rc;
}
#endif
#ifdef RT_OS_WINDOWS
/**
* Process pending events (Windows).
* @returns VINF_SUCCESS, VERR_TIMEOUT or VERR_INTERRUPTED.
*/
static int
processPendingEvents(void)
{
MSG Msg;
int rc = VERR_TIMEOUT;
while (PeekMessage(&Msg, NULL /*hWnd*/, 0 /*wMsgFilterMin*/, 0 /*wMsgFilterMax*/, PM_REMOVE))
{
if (Msg.message == WM_QUIT)
rc = VERR_INTERRUPTED;
DispatchMessage(&Msg);
if (rc == VERR_INTERRUPTED)
break;
rc = VINF_SUCCESS;
}
return rc;
}
#else /* !RT_OS_WINDOWS */
/**
* Process pending XPCOM events.
* @param pQueue The queue to process events on.
* @returns VINF_SUCCESS or VERR_TIMEOUT.
*/
static int
processPendingEvents(nsIEventQueue *pQueue)
{
/* Check for timeout condition so the caller can be a bit more lazy. */
PRBool fHasEvents = PR_FALSE;
nsresult hr = pQueue->PendingEvents(&fHasEvents);
if (NS_FAILED(hr))
return VERR_INTERNAL_ERROR_2;
if (!fHasEvents)
return VERR_TIMEOUT;
/** @todo: rethink interruption events, current NULL event approach is bad */
pQueue->ProcessPendingEvents();
return VINF_SUCCESS;
}
#endif /* !RT_OS_WINDOWS */
/**
* Process events pending on this event queue, and wait up to given timeout, if
* nothing is available.
*
* Must be called on same thread this event queue was created on.
*
* @param cMsTimeout The timeout specified as milliseconds. Use
* RT_INDEFINITE_WAIT to wait till an event is posted on the
* queue.
*
* @returns VBox status code
* @retval VINF_SUCCESS
* @retval VERR_TIMEOUT
* @retval VERR_INVALID_CONTEXT
*/
int EventQueue::processEventQueue(uint32_t cMsTimeout)
{
int rc;
CHECK_THREAD_RET(VERR_INVALID_CONTEXT);
#if defined (VBOX_WITH_XPCOM)
/*
* Process pending events, if none are available and we're not in a
* poll call, wait for some to appear. (We have to be a little bit
* careful after waiting for the events since darwin will process
* them as part of the wait, while the unix case will not.)
*
* Note! Unfortunately, WaitForEvent isn't interruptible with Ctrl-C,
* while select() is. So we cannot use it for indefinite waits.
*/
rc = processPendingEvents(mEventQ);
if ( rc == VERR_TIMEOUT
&& cMsTimeout > 0)
{
# ifdef RT_OS_DARWIN
/** @todo check how Ctrl-C works on darwin. */
rc = waitForEventsOnDarwin(cMsTimeout);
if (rc == VERR_TIMEOUT)
rc = processPendingEvents(mEventQ);
# else
rc = waitForEventsOnUnix(mEventQ, cMsTimeout);
if ( RT_SUCCESS(rc)
|| rc == VERR_TIMEOUT)
rc = processPendingEvents(mEventQ);
# endif
}
#else /* !VBOX_WITH_XPCOM */
if (cMsTimeout == RT_INDEFINITE_WAIT)
{
Event *aEvent = NULL;
BOOL fHasEvent = waitForEvent(&aEvent);
if (fHasEvent)
{
handleEvent(aEvent);
rc = processPendingEvents();
if (rc == VERR_TIMEOUT)
rc = VINF_SUCCESS;
}
else
rc = VERR_INTERRUPTED;
}
else
{
rc = processPendingEvents();
if ( rc == VERR_TIMEOUT
|| cMsTimeout == 0)
{
DWORD rcW = MsgWaitForMultipleObjects(1,
&mhThread,
TRUE /*fWaitAll*/,
cMsTimeout,
QS_ALLINPUT);
AssertMsgReturn(rcW == WAIT_TIMEOUT || rcW == WAIT_OBJECT_0,
("%d\n", rcW),
VERR_INTERNAL_ERROR_4);
rc = processPendingEvents();
}
}
#endif /* !VBOX_WITH_XPCOM */
return rc;
}
/**
* Interrupt thread waiting on event queue processing.
*
* Can be called on any thread.
*/
int EventQueue::interruptEventQueueProcessing()
{
/** @todo: rethink me! */
postEvent(NULL);
return VINF_SUCCESS;
}
/**
* Posts an event to this event loop asynchronously.
*
* @param event the event to post, must be allocated using |new|
* @return TRUE if successful and false otherwise
*/
BOOL EventQueue::postEvent (Event *event)
{
#if defined (RT_OS_WINDOWS)
return PostThreadMessage (mThreadId, WM_USER, (WPARAM) event, NULL);
#else
if (!mEventQ)
return FALSE;
MyPLEvent *ev = new MyPLEvent (event);
mEventQ->InitEvent (ev, this, plEventHandler, plEventDestructor);
HRESULT rc = mEventQ->PostEvent (ev);
return NS_SUCCEEDED(rc);
#endif
}
/**
* Waits for a single event.
* This method must be called on the same thread where this event queue
* is created.
*
* After this method returns TRUE and non-NULL event, the caller should call
* #handleEvent() in order to process the returned event (otherwise the event
* is just removed from the queue, but not processed).
*
* There is a special case when the returned event is NULL (and the method
* returns TRUE), meaning that this event queue must finish its execution
* (i.e., quit the event loop),
*
* @param event next event removed from the queue
* @return TRUE if successful and false otherwise
*/
BOOL EventQueue::waitForEvent (Event **event)
{
Assert(event);
if (!event)
return FALSE;
*event = NULL;
CHECK_THREAD_RET (FALSE);
#if defined (RT_OS_WINDOWS)
MSG msg;
BOOL rc = GetMessage (&msg, NULL, WM_USER, WM_USER);
// check for error
if (rc == -1)
return FALSE;
// check for WM_QUIT
if (!rc)
return TRUE;
// retrieve our event
*event = (Event *) msg.wParam;
#else
PLEvent *ev = NULL;
HRESULT rc;
mGotEvent = FALSE;
do
{
rc = mEventQ->WaitForEvent (&ev);
// check for error
if (FAILED(rc))
return FALSE;
// check for EINTR signal
if (!ev)
return TRUE;
// run PLEvent handler. This will just set mLastEvent if it is an
// MyPLEvent instance, and then delete ev.
mEventQ->HandleEvent (ev);
}
while (!mGotEvent);
// retrieve our event
*event = mLastEvent;
#endif
return TRUE;
}
/**
* Handles the given event and |delete|s it.
* This method must be called on the same thread where this event queue
* is created.
*/
BOOL EventQueue::handleEvent (Event *event)
{
Assert(event);
if (!event)
return FALSE;
CHECK_THREAD_RET (FALSE);
event->handler();
delete event;
return TRUE;
}
/**
* Get select()'able selector for this event queue.
* This will return -1 on platforms and queue variants not supporting such
* functionality.
*/
int EventQueue::getSelectFD()
{
#ifdef VBOX_WITH_XPCOM
return mEventQ->GetEventQueueSelectFD();
#else
return -1;
#endif
}
}
/* namespace com */