VirtualBox

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

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

Burn fix.

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