/* $Id: EventQueue.cpp 98297 2023-01-25 01:59:25Z vboxsync $ */ /** @file * Event queue class declaration. */ /* * Copyright (C) 2013-2023 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 . * * SPDX-License-Identifier: GPL-3.0-only */ /** @todo Adapt / update documentation! */ #include "VBox/com/EventQueue.h" #include #include /* For bad_alloc. */ #include #include #include #include #include namespace com { // EventQueue class //////////////////////////////////////////////////////////////////////////////// EventQueue::EventQueue(void) : mUserCnt(0), mShutdown(false) { int vrc = RTCritSectInit(&mCritSect); AssertRC(vrc); vrc = RTSemEventCreate(&mSemEvent); AssertRC(vrc); } EventQueue::~EventQueue(void) { int vrc = RTCritSectDelete(&mCritSect); AssertRC(vrc); vrc = RTSemEventDestroy(mSemEvent); AssertRC(vrc); 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 vrc = RTCritSectEnter(&mCritSect); if (RT_SUCCESS(vrc)) { if (mUserCnt == 0) /* No concurrent access allowed. */ { mUserCnt++; cNumEvents = mEvents.size(); if (!cNumEvents) { int vrc2 = RTCritSectLeave(&mCritSect); AssertRC(vrc2); vrc = RTSemEventWaitNoResume(mSemEvent, cMsTimeout); vrc2 = RTCritSectEnter(&mCritSect); AssertRC(vrc2); if (RT_SUCCESS(vrc)) { if (mShutdown) vrc = VERR_INTERRUPTED; cNumEvents = mEvents.size(); } } if (RT_SUCCESS(vrc)) vrc = processPendingEvents(cNumEvents); Assert(mUserCnt); mUserCnt--; } else vrc = VERR_WRONG_ORDER; int vrc2 = RTCritSectLeave(&mCritSect); if (RT_SUCCESS(vrc)) vrc = vrc2; } Assert(vrc != VERR_TIMEOUT || cMsTimeout != RT_INDEFINITE_WAIT); return vrc; } /** * 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 vrc = 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 vrc2 = RTCritSectLeave(&mCritSect); AssertRC(vrc2); pEvent->handler(); pEvent->Release(); vrc2 = RTCritSectEnter(&mCritSect); AssertRC(vrc2); it = mEvents.begin(); if (mShutdown) { vrc = VERR_INTERRUPTED; break; } } return vrc; } /** * 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 vrc = RTCritSectEnter(&mCritSect); if (RT_SUCCESS(vrc)) { 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. */ vrc = RTCritSectLeave(&mCritSect); if (RT_SUCCESS(vrc)) { int vrc2 = RTSemEventSignal(mSemEvent); AssertRC(vrc2); } } catch (std::bad_alloc &ba) { NOREF(ba); vrc = VERR_NO_MEMORY; } if (RT_FAILURE(vrc)) { int vrc2 = RTCritSectLeave(&mCritSect); AssertRC(vrc2); } } return RT_SUCCESS(vrc) ? TRUE : FALSE; } } /* namespace com */