VirtualBox

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

Last change on this file since 25414 was 23128, checked in by vboxsync, 15 years ago

COM/XPCOM glue: Bug fixes around the EventQueue init/uninit calls from com::Initialize and com::Shutdown. As com::Initialize() clearly states, this function will be called by all client threads doing COM/XPCOM work. Calling it more than once on the same thread is also documented as normal behavior (this is also evident from the counting done in the XPCOM version of the code). However it seems like the media checker is one of the few use cases for this and it doesn't kick in on all systems it seems... So, I hope this actually works as I cannot reproduce it here.

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

© 2023 Oracle
ContactPrivacy policyTerms of Use