VirtualBox

source: vbox/trunk/src/VBox/Main/glue/EventQueue.cpp@ 43943

Last change on this file since 43943 was 43943, checked in by vboxsync, 12 years ago

Main/glue: bad_alloc handling for event queue, todos.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 18.6 KB
Line 
1/* $Id: EventQueue.cpp 43943 2012-11-22 17:00:42Z vboxsync $ */
2/** @file
3 * MS COM / XPCOM Abstraction Layer:
4 * Event and EventQueue class declaration
5 */
6
7/*
8 * Copyright (C) 2006-2012 Oracle Corporation
9 *
10 * This file is part of VirtualBox Open Source Edition (OSE), as
11 * available from http://www.virtualbox.org. This file is free software;
12 * you can redistribute it and/or modify it under the terms of the GNU
13 * General Public License (GPL) as published by the Free Software
14 * Foundation, in version 2 as it comes in the "COPYING" file of the
15 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
16 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
17 */
18
19#include "VBox/com/EventQueue.h"
20
21#ifdef RT_OS_DARWIN
22# include <CoreFoundation/CFRunLoop.h>
23#endif
24
25#if defined(VBOX_WITH_XPCOM) && !defined(RT_OS_DARWIN) && !defined(RT_OS_OS2)
26# define USE_XPCOM_QUEUE
27#endif
28
29#include <iprt/err.h>
30#include <iprt/time.h>
31#include <iprt/thread.h>
32#include <iprt/log.h>
33#ifdef USE_XPCOM_QUEUE
34# include <errno.h>
35#endif
36
37namespace com
38{
39
40// EventQueue class
41////////////////////////////////////////////////////////////////////////////////
42
43#ifndef VBOX_WITH_XPCOM
44
45# define CHECK_THREAD_RET(ret) \
46 do { \
47 AssertMsg(GetCurrentThreadId() == mThreadId, ("Must be on event queue thread!")); \
48 if (GetCurrentThreadId() != mThreadId) \
49 return ret; \
50 } while (0)
51
52/** Magic LPARAM value for the WM_USER messages that we're posting.
53 * @remarks This magic value is duplicated in
54 * vboxapi/PlatformMSCOM::interruptWaitEvents(). */
55#define EVENTQUEUE_WIN_LPARAM_MAGIC UINT32_C(0xf241b819)
56
57
58#else // VBOX_WITH_XPCOM
59
60# define CHECK_THREAD_RET(ret) \
61 do { \
62 if (!mEventQ) \
63 return ret; \
64 BOOL isOnCurrentThread = FALSE; \
65 mEventQ->IsOnCurrentThread(&isOnCurrentThread); \
66 AssertMsg(isOnCurrentThread, ("Must be on event queue thread!")); \
67 if (!isOnCurrentThread) \
68 return ret; \
69 } while (0)
70
71#endif // VBOX_WITH_XPCOM
72
73/** Pointer to the main event queue. */
74EventQueue *EventQueue::sMainQueue = NULL;
75
76
77#ifdef VBOX_WITH_XPCOM
78
79struct MyPLEvent : public PLEvent
80{
81 MyPLEvent(Event *e) : event(e) {}
82 Event *event;
83};
84
85/* static */
86void *PR_CALLBACK com::EventQueue::plEventHandler(PLEvent *self)
87{
88 Event *ev = ((MyPLEvent *)self)->event;
89 if (ev)
90 ev->handler();
91 else
92 {
93 EventQueue *eq = (EventQueue *)self->owner;
94 Assert(eq);
95 eq->mInterrupted = true;
96 }
97 return NULL;
98}
99
100/* static */
101void PR_CALLBACK com::EventQueue::plEventDestructor(PLEvent *self)
102{
103 Event *ev = ((MyPLEvent *)self)->event;
104 if (ev)
105 delete ev;
106 delete self;
107}
108
109#endif // VBOX_WITH_XPCOM
110
111/**
112 * Constructs an event queue for the current thread.
113 *
114 * Currently, there can be only one event queue per thread, so if an event
115 * queue for the current thread already exists, this object is simply attached
116 * to the existing event queue.
117 */
118EventQueue::EventQueue()
119{
120 /** @todo r=andy This constructor does way too much. In case of failure
121 * it's up to the caller to verify all sorts of stuff. Why
122 * isn't this done in init() and moving the main queue
123 * creation/deletion stuff to a dedicated function? */
124#ifndef VBOX_WITH_XPCOM
125
126 mThreadId = GetCurrentThreadId();
127 // force the system to create the message queue for the current thread
128 MSG msg;
129 PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
130
131 if (!DuplicateHandle(GetCurrentProcess(),
132 GetCurrentThread(),
133 GetCurrentProcess(),
134 &mhThread,
135 0 /*dwDesiredAccess*/,
136 FALSE /*bInheritHandle*/,
137 DUPLICATE_SAME_ACCESS))
138 mhThread = INVALID_HANDLE_VALUE;
139
140#else // VBOX_WITH_XPCOM
141
142 mEQCreated = false;
143 mInterrupted = false;
144
145 // Here we reference the global nsIEventQueueService instance and hold it
146 // until we're destroyed. This is necessary to keep NS_ShutdownXPCOM() away
147 // from calling StopAcceptingEvents() on all event queues upon destruction of
148 // nsIEventQueueService, and makes sense when, for some reason, this happens
149 // *before* we're able to send a NULL event to stop our event handler thread
150 // when doing unexpected cleanup caused indirectly by NS_ShutdownXPCOM()
151 // that is performing a global cleanup of everything. A good example of such
152 // situation is when NS_ShutdownXPCOM() is called while the VirtualBox component
153 // is still alive (because it is still referenced): eventually, it results in
154 // a VirtualBox::uninit() call from where it is already not possible to post
155 // NULL to the event thread (because it stopped accepting events).
156
157 nsresult rc = NS_GetEventQueueService(getter_AddRefs(mEventQService));
158
159 if (NS_SUCCEEDED(rc))
160 {
161 rc = mEventQService->GetThreadEventQueue(NS_CURRENT_THREAD,
162 getter_AddRefs(mEventQ));
163 if (rc == NS_ERROR_NOT_AVAILABLE)
164 {
165 rc = mEventQService->CreateThreadEventQueue();
166 if (NS_SUCCEEDED(rc))
167 {
168 mEQCreated = true;
169 rc = mEventQService->GetThreadEventQueue(NS_CURRENT_THREAD,
170 getter_AddRefs(mEventQ));
171 }
172 }
173 }
174 AssertComRC(rc);
175
176#endif // VBOX_WITH_XPCOM
177}
178
179EventQueue::~EventQueue()
180{
181#ifndef VBOX_WITH_XPCOM
182 if (mhThread != INVALID_HANDLE_VALUE)
183 {
184 CloseHandle(mhThread);
185 mhThread = INVALID_HANDLE_VALUE;
186 }
187#else // VBOX_WITH_XPCOM
188 // process all pending events before destruction
189 if (mEventQ)
190 {
191 if (mEQCreated)
192 {
193 mEventQ->StopAcceptingEvents();
194 mEventQ->ProcessPendingEvents();
195 mEventQService->DestroyThreadEventQueue();
196 }
197 mEventQ = nsnull;
198 mEventQService = nsnull;
199 }
200#endif // VBOX_WITH_XPCOM
201}
202
203/**
204 * Initializes the main event queue instance.
205 * @returns VBox status code.
206 *
207 * @remarks If you're using the rest of the COM/XPCOM glue library,
208 * com::Initialize() will take care of initializing and uninitializing
209 * the EventQueue class. If you don't call com::Initialize, you must
210 * make sure to call this method on the same thread that did the
211 * XPCOM initialization or we'll end up using the wrong main queue.
212 */
213/* static */
214int EventQueue::init()
215{
216 Assert(sMainQueue == NULL);
217 Assert(RTThreadIsMain(RTThreadSelf()));
218
219 try
220 {
221 sMainQueue = new EventQueue();
222
223#ifdef VBOX_WITH_XPCOM
224 /* Check that it actually is the main event queue, i.e. that
225 we're called on the right thread. */
226 nsCOMPtr<nsIEventQueue> q;
227 nsresult rv = NS_GetMainEventQ(getter_AddRefs(q));
228 Assert(NS_SUCCEEDED(rv));
229 Assert(q == sMainQueue->mEventQ);
230
231 /* Check that it's a native queue. */
232 PRBool fIsNative = PR_FALSE;
233 rv = sMainQueue->mEventQ->IsQueueNative(&fIsNative);
234 Assert(NS_SUCCEEDED(rv) && fIsNative);
235#endif // VBOX_WITH_XPCOM
236 }
237 catch (bad_alloc &ba)
238 {
239 return VERR_NO_MEMORY;
240 }
241
242 return VINF_SUCCESS;
243}
244
245/**
246 * Uninitialize the global resources (i.e. the main event queue instance).
247 * @returns VINF_SUCCESS
248 */
249/* static */
250int EventQueue::uninit()
251{
252 if (sMainQueue)
253 {
254 /* Must process all events to make sure that no NULL event is left
255 * after this point. It would need to modify the state of sMainQueue. */
256#ifdef RT_OS_DARWIN /* Do not process the native runloop, the toolkit may not be ready for it. */
257 sMainQueue->mEventQ->ProcessPendingEvents();
258#else
259 sMainQueue->processEventQueue(0);
260#endif
261 delete sMainQueue;
262 sMainQueue = NULL;
263 }
264 return VINF_SUCCESS;
265}
266
267/**
268 * Get main event queue instance.
269 *
270 * Depends on init() being called first.
271 */
272/* static */
273EventQueue* EventQueue::getMainEventQueue()
274{
275 return sMainQueue;
276}
277
278#ifdef VBOX_WITH_XPCOM
279# ifdef RT_OS_DARWIN
280/**
281 * Wait for events and process them (Darwin).
282 *
283 * @retval VINF_SUCCESS
284 * @retval VERR_TIMEOUT
285 * @retval VERR_INTERRUPTED
286 *
287 * @param cMsTimeout How long to wait, or RT_INDEFINITE_WAIT.
288 */
289static int waitForEventsOnDarwin(RTMSINTERVAL cMsTimeout)
290{
291 /*
292 * Wait for the requested time, if we get a hit we do a poll to process
293 * any other pending messages.
294 *
295 * Note! About 1.0e10: According to the sources anything above 3.1556952e+9
296 * means indefinite wait and 1.0e10 is what CFRunLoopRun() uses.
297 */
298 CFTimeInterval rdTimeout = cMsTimeout == RT_INDEFINITE_WAIT ? 1e10 : (double)cMsTimeout / 1000;
299 OSStatus orc = CFRunLoopRunInMode(kCFRunLoopDefaultMode, rdTimeout, true /*returnAfterSourceHandled*/);
300 if (orc == kCFRunLoopRunHandledSource)
301 {
302 OSStatus orc2 = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, false /*returnAfterSourceHandled*/);
303 if ( orc2 == kCFRunLoopRunStopped
304 || orc2 == kCFRunLoopRunFinished)
305 orc = orc2;
306 }
307 if ( orc == 0 /*???*/
308 || orc == kCFRunLoopRunHandledSource)
309 return VINF_SUCCESS;
310 if ( orc == kCFRunLoopRunStopped
311 || orc == kCFRunLoopRunFinished)
312 return VERR_INTERRUPTED;
313 AssertMsg(orc == kCFRunLoopRunTimedOut, ("Unexpected status code from CFRunLoopRunInMode: %#x", orc));
314 return VERR_TIMEOUT;
315}
316# else // !RT_OS_DARWIN
317
318/**
319 * Wait for events (generic XPCOM).
320 *
321 * @retval VINF_SUCCESS
322 * @retval VERR_TIMEOUT
323 * @retval VINF_INTERRUPTED
324 * @retval VERR_INTERNAL_ERROR_4
325 *
326 * @param pQueue The queue to wait on.
327 * @param cMsTimeout How long to wait, or RT_INDEFINITE_WAIT.
328 */
329static int waitForEventsOnXPCOM(nsIEventQueue *pQueue, RTMSINTERVAL cMsTimeout)
330{
331 int fd = pQueue->GetEventQueueSelectFD();
332 fd_set fdsetR;
333 FD_ZERO(&fdsetR);
334 FD_SET(fd, &fdsetR);
335
336 fd_set fdsetE = fdsetR;
337
338 struct timeval tv = {0,0};
339 struct timeval *ptv;
340 if (cMsTimeout == RT_INDEFINITE_WAIT)
341 ptv = NULL;
342 else
343 {
344 tv.tv_sec = cMsTimeout / 1000;
345 tv.tv_usec = (cMsTimeout % 1000) * 1000;
346 ptv = &tv;
347 }
348
349 int rc = select(fd + 1, &fdsetR, NULL, &fdsetE, ptv);
350 if (rc > 0)
351 rc = VINF_SUCCESS;
352 else if (rc == 0)
353 rc = VERR_TIMEOUT;
354 else if (errno == EINTR)
355 rc = VINF_INTERRUPTED;
356 else
357 {
358 static uint32_t s_ErrorCount = 0;
359 if (s_ErrorCount < 500)
360 {
361 LogRel(("waitForEventsOnXPCOM rc=%d errno=%d\n", rc, errno));
362 ++s_ErrorCount;
363 }
364
365 AssertMsgFailed(("rc=%d errno=%d\n", rc, errno));
366 rc = VERR_INTERNAL_ERROR_4;
367 }
368 return rc;
369}
370
371# endif // !RT_OS_DARWIN
372#endif // VBOX_WITH_XPCOM
373
374#ifndef VBOX_WITH_XPCOM
375
376/**
377 * Dispatch a message on Windows.
378 *
379 * This will pick out our events and handle them specially.
380 *
381 * @returns @a rc or VERR_INTERRUPTED (WM_QUIT or NULL msg).
382 * @param pMsg The message to dispatch.
383 * @param rc The current status code.
384 */
385/*static*/
386int EventQueue::dispatchMessageOnWindows(MSG const *pMsg, int rc)
387{
388 /*
389 * Check for and dispatch our events.
390 */
391 if ( pMsg->hwnd == NULL
392 && pMsg->message == WM_USER)
393 {
394 if (pMsg->lParam == EVENTQUEUE_WIN_LPARAM_MAGIC)
395 {
396 Event *pEvent = (Event *)pMsg->wParam;
397 if (pEvent)
398 {
399 pEvent->handler();
400 delete pEvent;
401 }
402 else
403 rc = VERR_INTERRUPTED;
404 return rc;
405 }
406 AssertMsgFailed(("lParam=%p wParam=%p\n", pMsg->lParam, pMsg->wParam));
407 }
408
409 /*
410 * Check for the quit message and dispatch the message the normal way.
411 */
412 if (pMsg->message == WM_QUIT)
413 rc = VERR_INTERRUPTED;
414 TranslateMessage(pMsg);
415 DispatchMessage(pMsg);
416
417 return rc;
418}
419
420
421/**
422 * Process pending events (Windows).
423 *
424 * @retval VINF_SUCCESS
425 * @retval VERR_TIMEOUT
426 * @retval VERR_INTERRUPTED.
427 */
428static int processPendingEvents(void)
429{
430 int rc = VERR_TIMEOUT;
431 MSG Msg;
432 if (PeekMessage(&Msg, NULL /*hWnd*/, 0 /*wMsgFilterMin*/, 0 /*wMsgFilterMax*/, PM_REMOVE))
433 {
434 rc = VINF_SUCCESS;
435 do
436 rc = EventQueue::dispatchMessageOnWindows(&Msg, rc);
437 while (PeekMessage(&Msg, NULL /*hWnd*/, 0 /*wMsgFilterMin*/, 0 /*wMsgFilterMax*/, PM_REMOVE));
438 }
439 return rc;
440}
441
442#else // VBOX_WITH_XPCOM
443
444/**
445 * Process pending XPCOM events.
446 * @param pQueue The queue to process events on.
447 * @retval VINF_SUCCESS
448 * @retval VERR_TIMEOUT
449 * @retval VERR_INTERRUPTED (darwin only)
450 * @retval VERR_INTERNAL_ERROR_2
451 */
452static int processPendingEvents(nsIEventQueue *pQueue)
453{
454 /* ProcessPendingEvents doesn't report back what it did, so check here. */
455 PRBool fHasEvents = PR_FALSE;
456 nsresult hr = pQueue->PendingEvents(&fHasEvents);
457 if (NS_FAILED(hr))
458 return VERR_INTERNAL_ERROR_2;
459
460 /* Process pending events. */
461 int rc = VINF_SUCCESS;
462 if (fHasEvents)
463 pQueue->ProcessPendingEvents();
464 else
465 rc = VERR_TIMEOUT;
466
467# ifdef RT_OS_DARWIN
468 /* Process pending native events. */
469 int rc2 = waitForEventsOnDarwin(0);
470 if (rc == VERR_TIMEOUT || rc2 == VERR_INTERRUPTED)
471 rc = rc2;
472# endif
473
474 return rc;
475}
476
477#endif // VBOX_WITH_XPCOM
478
479/**
480 * Process events pending on this event queue, and wait up to given timeout, if
481 * nothing is available.
482 *
483 * Must be called on same thread this event queue was created on.
484 *
485 * @param cMsTimeout The timeout specified as milliseconds. Use
486 * RT_INDEFINITE_WAIT to wait till an event is posted on the
487 * queue.
488 *
489 * @returns VBox status code
490 * @retval VINF_SUCCESS if one or more messages was processed.
491 * @retval VERR_TIMEOUT if cMsTimeout expired.
492 * @retval VERR_INVALID_CONTEXT if called on the wrong thread.
493 * @retval VERR_INTERRUPTED if interruptEventQueueProcessing was called.
494 * On Windows will also be returned when WM_QUIT is encountered.
495 * On Darwin this may also be returned when the native queue is
496 * stopped or destroyed/finished.
497 * @retval VINF_INTERRUPTED if the native system call was interrupted by a
498 * an asynchronous event delivery (signal) or just felt like returning
499 * out of bounds. On darwin it will also be returned if the queue is
500 * stopped.
501 */
502int EventQueue::processEventQueue(RTMSINTERVAL cMsTimeout)
503{
504 int rc;
505 CHECK_THREAD_RET(VERR_INVALID_CONTEXT);
506
507#ifdef VBOX_WITH_XPCOM
508 /*
509 * Process pending events, if none are available and we're not in a
510 * poll call, wait for some to appear. (We have to be a little bit
511 * careful after waiting for the events since Darwin will process
512 * them as part of the wait, while the XPCOM case will not.)
513 *
514 * Note! Unfortunately, WaitForEvent isn't interruptible with Ctrl-C,
515 * while select() is. So we cannot use it for indefinite waits.
516 */
517 rc = processPendingEvents(mEventQ);
518 if ( rc == VERR_TIMEOUT
519 && cMsTimeout > 0)
520 {
521# ifdef RT_OS_DARWIN
522 /** @todo check how Ctrl-C works on Darwin. */
523 rc = waitForEventsOnDarwin(cMsTimeout);
524 if (rc == VERR_TIMEOUT)
525 rc = processPendingEvents(mEventQ);
526# else // !RT_OS_DARWIN
527 rc = waitForEventsOnXPCOM(mEventQ, cMsTimeout);
528 if ( RT_SUCCESS(rc)
529 || rc == VERR_TIMEOUT)
530 rc = processPendingEvents(mEventQ);
531# endif // !RT_OS_DARWIN
532 }
533
534 if ( ( RT_SUCCESS(rc)
535 || rc == VERR_INTERRUPTED
536 || rc == VERR_TIMEOUT)
537 && mInterrupted)
538 {
539 mInterrupted = false;
540 rc = VERR_INTERRUPTED;
541 }
542
543#else // !VBOX_WITH_XPCOM
544 if (cMsTimeout == RT_INDEFINITE_WAIT)
545 {
546 BOOL fRet;
547 MSG Msg;
548 rc = VINF_SUCCESS;
549 while ( rc != VERR_INTERRUPTED
550 && (fRet = GetMessage(&Msg, NULL /*hWnd*/, WM_USER, WM_USER))
551 && fRet != -1)
552 rc = EventQueue::dispatchMessageOnWindows(&Msg, rc);
553 if (fRet == 0)
554 rc = VERR_INTERRUPTED;
555 else if (fRet == -1)
556 rc = RTErrConvertFromWin32(GetLastError());
557 }
558 else
559 {
560 rc = processPendingEvents();
561 if ( rc == VERR_TIMEOUT
562 && cMsTimeout != 0)
563 {
564 DWORD rcW = MsgWaitForMultipleObjects(1,
565 &mhThread,
566 TRUE /*fWaitAll*/,
567 cMsTimeout,
568 QS_ALLINPUT);
569 AssertMsgReturn(rcW == WAIT_TIMEOUT || rcW == WAIT_OBJECT_0,
570 ("%d\n", rcW),
571 VERR_INTERNAL_ERROR_4);
572 rc = processPendingEvents();
573 }
574 }
575#endif // !VBOX_WITH_XPCOM
576
577 Assert(rc != VERR_TIMEOUT || cMsTimeout != RT_INDEFINITE_WAIT);
578 return rc;
579}
580
581/**
582 * Interrupt thread waiting on event queue processing.
583 *
584 * Can be called on any thread.
585 *
586 * @returns VBox status code.
587 */
588int EventQueue::interruptEventQueueProcessing()
589{
590 /* Send a NULL event. This event will be picked up and handled specially
591 * both for XPCOM and Windows. It is the responsibility of the caller to
592 * take care of not running the loop again in a way which will hang. */
593 postEvent(NULL);
594 return VINF_SUCCESS;
595}
596
597/**
598 * Posts an event to this event loop asynchronously.
599 *
600 * @param event the event to post, must be allocated using |new|
601 * @return TRUE if successful and false otherwise
602 */
603BOOL EventQueue::postEvent(Event *pEvent)
604{
605#ifndef VBOX_WITH_XPCOM
606 /* Note! The event == NULL case is duplicated in vboxapi/PlatformMSCOM::interruptWaitEvents(). */
607 return PostThreadMessage(mThreadId, WM_USER, (WPARAM)pEvent, EVENTQUEUE_WIN_LPARAM_MAGIC);
608
609#else // VBOX_WITH_XPCOM
610
611 if (!mEventQ)
612 return FALSE;
613
614 try
615 {
616 MyPLEvent *pMyEvent = new MyPLEvent(pEvent);
617 mEventQ->InitEvent(pMyEvent, this, com::EventQueue::plEventHandler,
618 com::EventQueue::plEventDestructor);
619 HRESULT rc = mEventQ->PostEvent(pMyEvent);
620 return NS_SUCCEEDED(rc);
621 }
622 catch (bad_alloc &ba)
623 {
624 AssertMsgFailed(("Out of memory while allocating memory for event=%p: %s\n",
625 pEvent, ba.what()));
626 }
627
628 return FALSE;
629#endif // VBOX_WITH_XPCOM
630}
631
632
633/**
634 * Get select()'able selector for this event queue.
635 * This will return -1 on platforms and queue variants not supporting such
636 * functionality.
637 */
638int EventQueue::getSelectFD()
639{
640#ifdef VBOX_WITH_XPCOM
641 return mEventQ->GetEventQueueSelectFD();
642#else
643 return -1;
644#endif
645}
646
647}
648/* namespace com */
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette