diff options
Diffstat (limited to 'src/VBox/Main/glue/EventQueue.cpp')
-rw-r--r-- | src/VBox/Main/glue/EventQueue.cpp | 670 |
1 files changed, 145 insertions, 525 deletions
diff --git a/src/VBox/Main/glue/EventQueue.cpp b/src/VBox/Main/glue/EventQueue.cpp index d5a09c69..c918c993 100644 --- a/src/VBox/Main/glue/EventQueue.cpp +++ b/src/VBox/Main/glue/EventQueue.cpp @@ -1,11 +1,10 @@ /* $Id: EventQueue.cpp $ */ /** @file - * MS COM / XPCOM Abstraction Layer: - * Event and EventQueue class declaration + * Event queue class declaration. */ /* - * Copyright (C) 2006-2010 Oracle Corporation + * Copyright (C) 2013 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; @@ -16,23 +15,18 @@ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. */ -#include "VBox/com/EventQueue.h" +/** @todo Adapt / update documentation! */ -#ifdef RT_OS_DARWIN -# include <CoreFoundation/CFRunLoop.h> -#endif +#include "VBox/com/EventQueue.h" -#if defined(VBOX_WITH_XPCOM) && !defined(RT_OS_DARWIN) && !defined(RT_OS_OS2) -# define USE_XPCOM_QUEUE -#endif +#include <iprt/asm.h> +#include <new> /* For bad_alloc. */ #include <iprt/err.h> +#include <iprt/semaphore.h> #include <iprt/time.h> #include <iprt/thread.h> #include <iprt/log.h> -#ifdef USE_XPCOM_QUEUE -# include <errno.h> -#endif namespace com { @@ -40,430 +34,33 @@ namespace com // EventQueue class //////////////////////////////////////////////////////////////////////////////// -#ifndef VBOX_WITH_XPCOM - -# define CHECK_THREAD_RET(ret) \ - do { \ - AssertMsg(GetCurrentThreadId() == mThreadId, ("Must be on event queue thread!")); \ - if (GetCurrentThreadId() != mThreadId) \ - return ret; \ - } while (0) - -/** Magic LPARAM value for the WM_USER messages that we're posting. - * @remarks This magic value is duplicated in - * vboxapi/PlatformMSCOM::interruptWaitEvents(). */ -#define EVENTQUEUE_WIN_LPARAM_MAGIC UINT32_C(0xf241b819) - - -#else // VBOX_WITH_XPCOM - -# 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 // VBOX_WITH_XPCOM - -/** Pointer to the main event queue. */ -EventQueue *EventQueue::sMainQueue = NULL; - - -#ifdef VBOX_WITH_XPCOM - -struct MyPLEvent : public PLEvent -{ - MyPLEvent(Event *e) : event(e) {} - Event *event; -}; - -/* static */ -void *PR_CALLBACK com::EventQueue::plEventHandler(PLEvent *self) -{ - Event *ev = ((MyPLEvent *)self)->event; - if (ev) - ev->handler(); - else - { - EventQueue *eq = (EventQueue *)self->owner; - Assert(eq); - eq->mInterrupted = true; - } - return NULL; -} - -/* static */ -void PR_CALLBACK com::EventQueue::plEventDestructor(PLEvent *self) -{ - Event *ev = ((MyPLEvent *)self)->event; - if (ev) - delete ev; - delete self; -} - -#endif // VBOX_WITH_XPCOM - -/** - * 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() -{ -#ifndef VBOX_WITH_XPCOM - - 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 // VBOX_WITH_XPCOM - - mEQCreated = false; - mInterrupted = 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->CreateThreadEventQueue(); - if (NS_SUCCEEDED(rc)) - { - mEQCreated = true; - rc = mEventQService->GetThreadEventQueue(NS_CURRENT_THREAD, - getter_AddRefs(mEventQ)); - } - } - } - AssertComRC(rc); - -#endif // VBOX_WITH_XPCOM -} - -EventQueue::~EventQueue() -{ -#ifndef VBOX_WITH_XPCOM - if (mhThread != INVALID_HANDLE_VALUE) - { - CloseHandle(mhThread); - mhThread = INVALID_HANDLE_VALUE; - } -#else // VBOX_WITH_XPCOM - // process all pending events before destruction - if (mEventQ) - { - if (mEQCreated) - { - mEventQ->StopAcceptingEvents(); - mEventQ->ProcessPendingEvents(); - mEventQService->DestroyThreadEventQueue(); - } - mEventQ = nsnull; - mEventQService = nsnull; - } -#endif // VBOX_WITH_XPCOM -} - -/** - * 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() +EventQueue::EventQueue(void) + : mUserCnt(0), + mShutdown(false) { - Assert(sMainQueue == NULL); - Assert(RTThreadIsMain(RTThreadSelf())); - sMainQueue = new EventQueue(); - -#ifdef 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 == sMainQueue->mEventQ); - - /* Check that it's a native queue. */ - PRBool fIsNative = PR_FALSE; - rv = sMainQueue->mEventQ->IsQueueNative(&fIsNative); - Assert(NS_SUCCEEDED(rv) && fIsNative); -#endif // VBOX_WITH_XPCOM - - return VINF_SUCCESS; -} - -/** - * Uninitialize the global resources (i.e. the main event queue instance). - * @returns VINF_SUCCESS - */ -/* static */ -int EventQueue::uninit() -{ - if (sMainQueue) - { - /* Must process all events to make sure that no NULL event is left - * after this point. It would need to modify the state of sMainQueue. */ -#ifdef RT_OS_DARWIN /* Do not process the native runloop, the toolkit may not be ready for it. */ - sMainQueue->mEventQ->ProcessPendingEvents(); -#else - sMainQueue->processEventQueue(0); -#endif - delete sMainQueue; - sMainQueue = NULL; - } - return VINF_SUCCESS; -} + int rc = RTCritSectInit(&mCritSect); + AssertRC(rc); -/** - * Get main event queue instance. - * - * Depends on init() being called first. - */ -/* static */ -EventQueue* EventQueue::getMainEventQueue() -{ - return sMainQueue; + rc = RTSemEventCreate(&mSemEvent); + AssertRC(rc); } -#ifdef VBOX_WITH_XPCOM -# ifdef RT_OS_DARWIN -/** - * Wait for events and process them (Darwin). - * - * @retval VINF_SUCCESS - * @retval VERR_TIMEOUT - * @retval VERR_INTERRUPTED - * - * @param cMsTimeout How long to wait, or RT_INDEFINITE_WAIT. - */ -static int waitForEventsOnDarwin(RTMSINTERVAL cMsTimeout) +EventQueue::~EventQueue(void) { - /* - * 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*/); - if (orc == kCFRunLoopRunHandledSource) - { - OSStatus orc2 = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, false /*returnAfterSourceHandled*/); - if ( orc2 == kCFRunLoopRunStopped - || orc2 == kCFRunLoopRunFinished) - orc = orc2; - } - 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; -} -# else // !RT_OS_DARWIN + int rc = RTCritSectDelete(&mCritSect); + AssertRC(rc); -/** - * Wait for events (generic XPCOM). - * - * @retval VINF_SUCCESS - * @retval VERR_TIMEOUT - * @retval VINF_INTERRUPTED - * @retval VERR_INTERNAL_ERROR_4 - * - * @param pQueue The queue to wait on. - * @param cMsTimeout How long to wait, or RT_INDEFINITE_WAIT. - */ -static int waitForEventsOnXPCOM(nsIEventQueue *pQueue, RTMSINTERVAL 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; - } + rc = RTSemEventDestroy(mSemEvent); + AssertRC(rc); - 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 = VINF_INTERRUPTED; - else + EventQueueListIterator it = mEvents.begin(); + while (it != mEvents.end()) { - static uint32_t s_ErrorCount = 0; - if (s_ErrorCount < 500) - { - LogRel(("waitForEventsOnXPCOM rc=%d errno=%d\n", rc, errno)); - ++s_ErrorCount; - } - - AssertMsgFailed(("rc=%d errno=%d\n", rc, errno)); - rc = VERR_INTERNAL_ERROR_4; + (*it)->Release(); + it = mEvents.erase(it); } - return rc; } -# endif // !RT_OS_DARWIN -#endif // VBOX_WITH_XPCOM - -#ifndef VBOX_WITH_XPCOM - -/** - * Dispatch a message on Windows. - * - * This will pick out our events and handle them specially. - * - * @returns @a rc or VERR_INTERRUPTED (WM_QUIT or NULL msg). - * @param pMsg The message to dispatch. - * @param rc The current status code. - */ -/*static*/ -int EventQueue::dispatchMessageOnWindows(MSG const *pMsg, int rc) -{ - /* - * Check for and dispatch our events. - */ - if ( pMsg->hwnd == NULL - && pMsg->message == WM_USER) - { - if (pMsg->lParam == EVENTQUEUE_WIN_LPARAM_MAGIC) - { - Event *pEvent = (Event *)pMsg->wParam; - if (pEvent) - { - pEvent->handler(); - delete pEvent; - } - else - rc = VERR_INTERRUPTED; - return rc; - } - AssertMsgFailed(("lParam=%p wParam=%p\n", pMsg->lParam, pMsg->wParam)); - } - - /* - * Check for the quit message and dispatch the message the normal way. - */ - if (pMsg->message == WM_QUIT) - rc = VERR_INTERRUPTED; - TranslateMessage(pMsg); - DispatchMessage(pMsg); - - return rc; -} - - -/** - * Process pending events (Windows). - * - * @retval VINF_SUCCESS - * @retval VERR_TIMEOUT - * @retval VERR_INTERRUPTED. - */ -static int processPendingEvents(void) -{ - int rc = VERR_TIMEOUT; - MSG Msg; - if (PeekMessage(&Msg, NULL /*hWnd*/, 0 /*wMsgFilterMin*/, 0 /*wMsgFilterMax*/, PM_REMOVE)) - { - rc = VINF_SUCCESS; - do - rc = EventQueue::dispatchMessageOnWindows(&Msg, rc); - while (PeekMessage(&Msg, NULL /*hWnd*/, 0 /*wMsgFilterMin*/, 0 /*wMsgFilterMax*/, PM_REMOVE)); - } - return rc; -} - -#else // VBOX_WITH_XPCOM - -/** - * Process pending XPCOM events. - * @param pQueue The queue to process events on. - * @retval VINF_SUCCESS - * @retval VERR_TIMEOUT - * @retval VERR_INTERRUPTED (darwin only) - * @retval VERR_INTERNAL_ERROR_2 - */ -static int processPendingEvents(nsIEventQueue *pQueue) -{ - /* ProcessPendingEvents doesn't report back what it did, so check here. */ - PRBool fHasEvents = PR_FALSE; - nsresult hr = pQueue->PendingEvents(&fHasEvents); - if (NS_FAILED(hr)) - return VERR_INTERNAL_ERROR_2; - - /* Process pending events. */ - int rc = VINF_SUCCESS; - if (fHasEvents) - pQueue->ProcessPendingEvents(); - else - rc = VERR_TIMEOUT; - -# ifdef RT_OS_DARWIN - /* Process pending native events. */ - int rc2 = waitForEventsOnDarwin(0); - if (rc == VERR_TIMEOUT || rc2 == VERR_INTERRUPTED) - rc = rc2; -# endif - - return rc; -} - -#endif // VBOX_WITH_XPCOM - /** * Process events pending on this event queue, and wait up to given timeout, if * nothing is available. @@ -489,80 +86,92 @@ static int processPendingEvents(nsIEventQueue *pQueue) */ int EventQueue::processEventQueue(RTMSINTERVAL cMsTimeout) { - int rc; - CHECK_THREAD_RET(VERR_INVALID_CONTEXT); - -#ifdef 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 XPCOM 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) + size_t cNumEvents; + int rc = RTCritSectEnter(&mCritSect); + if (RT_SUCCESS(rc)) { -# ifdef RT_OS_DARWIN - /** @todo check how Ctrl-C works on Darwin. */ - rc = waitForEventsOnDarwin(cMsTimeout); - if (rc == VERR_TIMEOUT) - rc = processPendingEvents(mEventQ); -# else // !RT_OS_DARWIN - rc = waitForEventsOnXPCOM(mEventQ, cMsTimeout); - if ( RT_SUCCESS(rc) - || rc == VERR_TIMEOUT) - rc = processPendingEvents(mEventQ); -# endif // !RT_OS_DARWIN - } + if (mUserCnt == 0) /* No concurrent access allowed. */ + { + mUserCnt++; - if ( ( RT_SUCCESS(rc) - || rc == VERR_INTERRUPTED - || rc == VERR_TIMEOUT) - && mInterrupted) - { - mInterrupted = false; - rc = VERR_INTERRUPTED; - } + cNumEvents = mEvents.size(); + if (!cNumEvents) + { + int rc2 = RTCritSectLeave(&mCritSect); + AssertRC(rc2); -#else // !VBOX_WITH_XPCOM - if (cMsTimeout == RT_INDEFINITE_WAIT) - { - BOOL fRet; - MSG Msg; - rc = VINF_SUCCESS; - while ( rc != VERR_INTERRUPTED - && (fRet = GetMessage(&Msg, NULL /*hWnd*/, WM_USER, WM_USER)) - && fRet != -1) - rc = EventQueue::dispatchMessageOnWindows(&Msg, rc); - if (fRet == 0) - rc = VERR_INTERRUPTED; - else if (fRet == -1) - rc = RTErrConvertFromWin32(GetLastError()); + rc = RTSemEventWaitNoResume(mSemEvent, cMsTimeout); + + rc2 = RTCritSectEnter(&mCritSect); + AssertRC(rc2); + + if (RT_SUCCESS(rc)) + { + if (mShutdown) + rc = VERR_INTERRUPTED; + cNumEvents = mEvents.size(); + } + } + + if (RT_SUCCESS(rc)) + rc = processPendingEvents(cNumEvents); + + Assert(mUserCnt); + mUserCnt--; + } + else + rc = VERR_WRONG_ORDER; + + int rc2 = RTCritSectLeave(&mCritSect); + if (RT_SUCCESS(rc)) + rc = rc2; } - else + + Assert(rc != VERR_TIMEOUT || cMsTimeout != RT_INDEFINITE_WAIT); + return rc; +} + +/** + * Processes all pending events in the queue at the time of + * calling. Note: Does no initial locking, must be done by the + * caller! + * + * @return IPRT status code. + */ +int EventQueue::processPendingEvents(size_t cNumEvents) +{ + if (!cNumEvents) /* Nothing to process? Bail out early. */ + return VINF_SUCCESS; + + int rc = VINF_SUCCESS; + + EventQueueListIterator it = mEvents.begin(); + for (size_t i = 0; + i < cNumEvents + && it != mEvents.end(); i++) { - rc = processPendingEvents(); - if ( rc == VERR_TIMEOUT - && cMsTimeout != 0) + Event *pEvent = *it; + AssertPtr(pEvent); + + mEvents.erase(it); + + int rc2 = RTCritSectLeave(&mCritSect); + AssertRC(rc2); + + pEvent->handler(); + pEvent->Release(); + + rc2 = RTCritSectEnter(&mCritSect); + AssertRC(rc2); + + it = mEvents.begin(); + if (mShutdown) { - 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(); + rc = VERR_INTERRUPTED; + break; } } -#endif // !VBOX_WITH_XPCOM - Assert(rc != VERR_TIMEOUT || cMsTimeout != RT_INDEFINITE_WAIT); return rc; } @@ -573,13 +182,11 @@ int EventQueue::processEventQueue(RTMSINTERVAL cMsTimeout) * * @returns VBox status code. */ -int EventQueue::interruptEventQueueProcessing() +int EventQueue::interruptEventQueueProcessing(void) { - /* Send a NULL event. This event will be picked up and handled specially - * both for XPCOM and Windows. It is the responsibility of the caller to - * take care of not running the loop again in a way which will hang. */ - postEvent(NULL); - return VINF_SUCCESS; + ASMAtomicWriteBool(&mShutdown, true); + + return RTSemEventSignal(mSemEvent); } /** @@ -588,39 +195,52 @@ int EventQueue::interruptEventQueueProcessing() * @param event the event to post, must be allocated using |new| * @return TRUE if successful and false otherwise */ -BOOL EventQueue::postEvent(Event *event) +BOOL EventQueue::postEvent(Event *pEvent) { -#ifndef VBOX_WITH_XPCOM - /* Note! The event == NULL case is duplicated in vboxapi/PlatformMSCOM::interruptWaitEvents(). */ - return PostThreadMessage(mThreadId, WM_USER, (WPARAM)event, EVENTQUEUE_WIN_LPARAM_MAGIC); - -#else // VBOX_WITH_XPCOM - - if (!mEventQ) - return FALSE; + int rc = RTCritSectEnter(&mCritSect); + if (RT_SUCCESS(rc)) + { + try + { + if (pEvent) + { + pEvent->AddRef(); + mEvents.push_back(pEvent); + } + else /* No locking, since we're already in our crit sect. */ + mShutdown = true; - MyPLEvent *ev = new MyPLEvent(event); - mEventQ->InitEvent(ev, this, com::EventQueue::plEventHandler, - com::EventQueue::plEventDestructor); - HRESULT rc = mEventQ->PostEvent(ev); - return NS_SUCCEEDED(rc); + size_t cEvents = mEvents.size(); + if (cEvents > _1K) /** @todo Make value configurable? */ + { + static int s_cBitchedAboutLotEvents = 0; + if (s_cBitchedAboutLotEvents < 10) + LogRel(("Warning: Event queue received lots of events (%zu), expect delayed event handling (%d/10)\n", + cEvents, ++s_cBitchedAboutLotEvents)); + } -#endif // VBOX_WITH_XPCOM -} + /* Leave critical section before signalling event. */ + rc = RTCritSectLeave(&mCritSect); + if (RT_SUCCESS(rc)) + { + int rc2 = RTSemEventSignal(mSemEvent); + AssertRC(rc2); + } + } + catch (std::bad_alloc &ba) + { + NOREF(ba); + rc = VERR_NO_MEMORY; + } + if (RT_FAILURE(rc)) + { + int rc2 = RTCritSectLeave(&mCritSect); + AssertRC(rc2); + } + } -/** - * 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 + return RT_SUCCESS(rc) ? TRUE : FALSE; } } |