EventQueue.cpp revision 8bc83e5cee630bad71985c8ddf175f6eab0bfb7a
/* $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;
* 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
# define USE_XPCOM_QUEUE
#endif
#ifdef USE_XPCOM_QUEUE
# include <errno.h>
#endif
namespace com
{
// EventQueue class
////////////////////////////////////////////////////////////////////////////////
#if defined (RT_OS_WINDOWS)
#define CHECK_THREAD_RET(ret) \
do { \
if (GetCurrentThreadId() != mThreadId) \
return ret; \
} while (0)
#else // !defined (RT_OS_WINDOWS)
#define CHECK_THREAD_RET(ret) \
do { \
if (!mEventQ) \
return ret; \
if (!isOnCurrentThread) \
return ret; \
} while (0)
#endif // !defined (RT_OS_WINDOWS)
/**
* 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.
*/
{
#if defined (RT_OS_WINDOWS)
// force the system to create the message queue for the current thread
if (!DuplicateHandle (GetCurrentProcess(),
&mhThread,
0 /*dwDesiredAccess*/,
FALSE /*bInheritHandle*/,
#else
mEQCreated = FALSE;
mLastEvent = NULL;
// 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).
if (NS_SUCCEEDED(rc))
{
if (rc == NS_ERROR_NOT_AVAILABLE)
{
if (NS_SUCCEEDED(rc))
{
mEQCreated = TRUE;
}
}
}
AssertComRC (rc);
#endif
}
EventQueue::~EventQueue()
{
#if defined (RT_OS_WINDOWS)
if (mhThread != INVALID_HANDLE_VALUE)
{
}
#else
// process all pending events before destruction
if (mEventQ)
{
if (mEQCreated)
{
}
}
#endif
}
/**
* Initializes the main event queue instance.
* @returns VBox status code.
*
* 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()
{
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. */
/* Check that it's a native queue. */
#endif
return VINF_SUCCESS;
}
/**
* Uninitialize the global resources (i.e. the main event queue instance).
* @returns VINF_SUCCESS
*/
/* static */ int
EventQueue::uninit()
{
delete mMainQueue;
mMainQueue = NULL;
return VINF_SUCCESS;
}
/**
* Get main event queue instance.
*
* Depends on init() being called first.
*/
/* static */ EventQueue *
{
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.
*/
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)
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
{
if (cMsTimeout == RT_INDEFINITE_WAIT)
else
{
}
if (rc > 0)
rc = VINF_SUCCESS;
else if (rc == 0)
rc = VERR_TIMEOUT;
else
{
}
return rc;
}
#endif
#ifdef RT_OS_WINDOWS
/**
* Process pending events (Windows).
* @returns VINF_SUCCESS, VERR_TIMEOUT or VERR_INTERRUPTED.
*/
static int
processPendingEvents(void)
{
int rc = VERR_TIMEOUT;
{
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
{
/* Check for timeout condition so the caller can be a bit more lazy. */
return VERR_INTERNAL_ERROR_2;
if (!fHasEvents)
return VERR_TIMEOUT;
/** @todo: rethink interruption events, current NULL event approach is bad */
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 rc;
#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.
*/
if ( rc == VERR_TIMEOUT
&& cMsTimeout > 0)
{
# ifdef RT_OS_DARWIN
/** @todo check how Ctrl-C works on darwin. */
if (rc == VERR_TIMEOUT)
# else
if ( RT_SUCCESS(rc)
|| rc == VERR_TIMEOUT)
# endif
}
#else /* !VBOX_WITH_XPCOM */
if (cMsTimeout == RT_INDEFINITE_WAIT)
{
if (fHasEvent)
{
rc = processPendingEvents();
if (rc == VERR_TIMEOUT)
rc = VINF_SUCCESS;
}
else
}
else
{
rc = processPendingEvents();
if ( rc == VERR_TIMEOUT
|| cMsTimeout == 0)
{
&mhThread,
TRUE /*fWaitAll*/,
("%d\n", rcW),
rc = processPendingEvents();
}
}
#endif /* !VBOX_WITH_XPCOM */
return rc;
}
/**
* Interrupt thread waiting on event queue processing.
*
* Can be called on any thread.
*/
{
/** @todo: rethink me! */
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
*/
{
#if defined (RT_OS_WINDOWS)
#else
if (!mEventQ)
return FALSE;
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
*/
{
if (!event)
return FALSE;
#if defined (RT_OS_WINDOWS)
// check for error
if (rc == -1)
return FALSE;
// check for WM_QUIT
if (!rc)
return TRUE;
// retrieve our event
#else
do
{
// check for error
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.
}
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.
*/
{
if (!event)
return FALSE;
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 */