summaryrefslogtreecommitdiffstats
path: root/src/VBox/Main/glue/EventQueue.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Main/glue/EventQueue.cpp')
-rw-r--r--src/VBox/Main/glue/EventQueue.cpp257
1 files changed, 257 insertions, 0 deletions
diff --git a/src/VBox/Main/glue/EventQueue.cpp b/src/VBox/Main/glue/EventQueue.cpp
new file mode 100644
index 00000000..4f5a4a36
--- /dev/null
+++ b/src/VBox/Main/glue/EventQueue.cpp
@@ -0,0 +1,257 @@
+/* $Id: EventQueue.cpp $ */
+/** @file
+ * Event queue class declaration.
+ */
+
+/*
+ * Copyright (C) 2013-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
+ */
+
+/** @todo Adapt / update documentation! */
+
+#include "VBox/com/EventQueue.h"
+
+#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>
+
+namespace com
+{
+
+// EventQueue class
+////////////////////////////////////////////////////////////////////////////////
+
+EventQueue::EventQueue(void)
+ : mUserCnt(0),
+ mShutdown(false)
+{
+ int rc = RTCritSectInit(&mCritSect);
+ AssertRC(rc);
+
+ rc = RTSemEventCreate(&mSemEvent);
+ AssertRC(rc);
+}
+
+EventQueue::~EventQueue(void)
+{
+ int rc = RTCritSectDelete(&mCritSect);
+ AssertRC(rc);
+
+ rc = RTSemEventDestroy(mSemEvent);
+ AssertRC(rc);
+
+ EventQueueListIterator it = mEvents.begin();
+ while (it != mEvents.end())
+ {
+ (*it)->Release();
+ it = mEvents.erase(it);
+ }
+}
+
+/**
+ * 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 if one or more messages was processed.
+ * @retval VERR_TIMEOUT if cMsTimeout expired.
+ * @retval VERR_INVALID_CONTEXT if called on the wrong thread.
+ * @retval VERR_INTERRUPTED if interruptEventQueueProcessing was called.
+ * On Windows will also be returned when WM_QUIT is encountered.
+ * On Darwin this may also be returned when the native queue is
+ * stopped or destroyed/finished.
+ * @retval VINF_INTERRUPTED if the native system call was interrupted by a
+ * an asynchronous event delivery (signal) or just felt like returning
+ * out of bounds. On darwin it will also be returned if the queue is
+ * stopped.
+ */
+int EventQueue::processEventQueue(RTMSINTERVAL cMsTimeout)
+{
+ size_t cNumEvents;
+ int rc = RTCritSectEnter(&mCritSect);
+ if (RT_SUCCESS(rc))
+ {
+ if (mUserCnt == 0) /* No concurrent access allowed. */
+ {
+ mUserCnt++;
+
+ cNumEvents = mEvents.size();
+ if (!cNumEvents)
+ {
+ int rc2 = RTCritSectLeave(&mCritSect);
+ AssertRC(rc2);
+
+ 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;
+ }
+
+ 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++)
+ {
+ 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)
+ {
+ rc = VERR_INTERRUPTED;
+ break;
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * Interrupt thread waiting on event queue processing.
+ *
+ * Can be called on any thread.
+ *
+ * @returns VBox status code.
+ */
+int EventQueue::interruptEventQueueProcessing(void)
+{
+ ASMAtomicWriteBool(&mShutdown, true);
+
+ return RTSemEventSignal(mSemEvent);
+}
+
+/**
+ * Posts an event to this event loop asynchronously.
+ *
+ * @param pEvent the event to post, must be allocated using |new|
+ * @return TRUE if successful and false otherwise
+ */
+BOOL EventQueue::postEvent(Event *pEvent)
+{
+ 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;
+
+ 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));
+ }
+
+ /* 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);
+ }
+ }
+
+ return RT_SUCCESS(rc) ? TRUE : FALSE;
+}
+
+}
+/* namespace com */