VirtualBox

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

Last change on this file since 42566 was 41015, checked in by vboxsync, 13 years ago

VBoxCOM: proper uninitialization even if com::Initialize failed very early

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

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