VirtualBox

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

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

Main/glue: EventQueue bugfixes and code style adjustments and cleanups. Windows was polling instead of waiting and processEventQueue didn't return what it should on all platforms (nobody uses it currently, so that didn't break anything). Tested on 64-bit linux, 64-bit windows and 32-bit darwin.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 15.9 KB
Line 
1/* $Id: EventQueue.cpp 23092 2009-09-17 13:20:18Z 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 mMainQueue = new EventQueue();
179
180#if defined (VBOX_WITH_XPCOM)
181 /* Check that it actually is the main event queue, i.e. that
182 we're called on the right thread. */
183 nsCOMPtr<nsIEventQueue> q;
184 nsresult rv = NS_GetMainEventQ(getter_AddRefs(q));
185 Assert(NS_SUCCEEDED(rv));
186 Assert(q == mMainQueue->mEventQ);
187
188 /* Check that it's a native queue. */
189 PRBool fIsNative = PR_FALSE;
190 rv = mMainQueue->mEventQ->IsQueueNative(&fIsNative);
191 Assert(NS_SUCCEEDED(rv) && fIsNative);
192#endif
193
194 return VINF_SUCCESS;
195}
196
197/**
198 * Uninitialize the global resources (i.e. the main event queue instance).
199 * @returns VINF_SUCCESS
200 */
201/* static */ int
202EventQueue::uninit()
203{
204 delete mMainQueue;
205 mMainQueue = NULL;
206 return VINF_SUCCESS;
207}
208
209/**
210 * Get main event queue instance.
211 *
212 * Depends on init() being called first.
213 */
214/* static */ EventQueue *
215EventQueue::getMainEventQueue()
216{
217 return mMainQueue;
218}
219
220#ifdef RT_OS_DARWIN
221/**
222 * Wait for events and process them (Darwin).
223 *
224 * @returns VINF_SUCCESS or VERR_TIMEOUT.
225 *
226 * @param cMsTimeout How long to wait, or RT_INDEFINITE_WAIT.
227 */
228static int
229waitForEventsOnDarwin(unsigned cMsTimeout)
230{
231 /*
232 * Wait for the requested time, if we get a hit we do a poll to process
233 * any other pending messages.
234 *
235 * Note! About 1.0e10: According to the sources anything above 3.1556952e+9
236 * means indefinite wait and 1.0e10 is what CFRunLoopRun() uses.
237 */
238 CFTimeInterval rdTimeout = cMsTimeout == RT_INDEFINITE_WAIT ? 1e10 : (double)cMsTimeout / 1000;
239 OSStatus orc = CFRunLoopRunInMode(kCFRunLoopDefaultMode, rdTimeout, true /*returnAfterSourceHandled*/);
240 /** @todo Not entire sure if the poll actually processes more than one message.
241 * Feel free to check the sources anyone. */
242 if (orc == kCFRunLoopRunHandledSource)
243 orc = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, false /*returnAfterSourceHandled*/);
244 if ( orc == 0
245 || orc == kCFRunLoopRunHandledSource)
246 return VINF_SUCCESS;
247 if ( orc == kCFRunLoopRunStopped
248 || orc == kCFRunLoopRunFinished)
249 return VERR_INTERRUPTED;
250 AssertMsg(orc == kCFRunLoopRunTimedOut, ("Unexpected status code from CFRunLoopRunInMode: %#x", orc));
251 return VERR_TIMEOUT;
252}
253#elif !defined(RT_OS_WINDOWS)
254
255/**
256 * Wait for events (Unix).
257 *
258 * @returns VINF_SUCCESS or VERR_TIMEOUT.
259 *
260 * @param pQueue The queue to wait on.
261 * @param cMsTimeout How long to wait, or RT_INDEFINITE_WAIT.
262 */
263static int
264waitForEventsOnUnix(nsIEventQueue *pQueue, unsigned cMsTimeout)
265{
266 int fd = pQueue->GetEventQueueSelectFD();
267 fd_set fdsetR;
268 FD_ZERO(&fdsetR);
269 FD_SET(fd, &fdsetR);
270
271 fd_set fdsetE = fdsetR;
272
273 struct timeval tv = {0,0};
274 struct timeval *ptv;
275 if (cMsTimeout == RT_INDEFINITE_WAIT)
276 ptv = NULL;
277 else
278 {
279 tv.tv_sec = cMsTimeout / 1000;
280 tv.tv_usec = (cMsTimeout % 1000) * 1000;
281 ptv = &tv;
282 }
283
284 int rc = select(fd + 1, &fdsetR, NULL, &fdsetE, ptv);
285 if (rc > 0)
286 rc = VINF_SUCCESS;
287 else if (rc == 0)
288 rc = VERR_TIMEOUT;
289 else if (errno == EINTR)
290 rc = VERR_INTERRUPTED;
291 else
292 {
293 AssertMsgFailed(("rc=%d errno=%d\n", rc, errno));
294 rc = VERR_INTERNAL_ERROR_4;
295 }
296 return rc;
297}
298
299#endif
300
301#ifdef RT_OS_WINDOWS
302/**
303 * Process pending events (Windows).
304 * @returns VINF_SUCCESS, VERR_TIMEOUT or VERR_INTERRUPTED.
305 */
306static int
307processPendingEvents(void)
308{
309 MSG Msg;
310 int rc = VERR_TIMEOUT;
311 while (PeekMessage(&Msg, NULL /*hWnd*/, 0 /*wMsgFilterMin*/, 0 /*wMsgFilterMax*/, PM_REMOVE))
312 {
313 if (Msg.message == WM_QUIT)
314 rc = VERR_INTERRUPTED;
315 DispatchMessage(&Msg);
316 if (rc == VERR_INTERRUPTED)
317 break;
318 rc = VINF_SUCCESS;
319 }
320 return rc;
321}
322#else /* !RT_OS_WINDOWS */
323/**
324 * Process pending XPCOM events.
325 * @param pQueue The queue to process events on.
326 * @returns VINF_SUCCESS or VERR_TIMEOUT.
327 */
328static int
329processPendingEvents(nsIEventQueue *pQueue)
330{
331 /* Check for timeout condition so the caller can be a bit more lazy. */
332 PRBool fHasEvents = PR_FALSE;
333 nsresult hr = pQueue->PendingEvents(&fHasEvents);
334 if (NS_FAILED(hr))
335 return VERR_INTERNAL_ERROR_2;
336 if (!fHasEvents)
337 return VERR_TIMEOUT;
338
339 /** @todo: rethink interruption events, current NULL event approach is bad */
340 pQueue->ProcessPendingEvents();
341 return VINF_SUCCESS;
342}
343#endif /* !RT_OS_WINDOWS */
344
345
346/**
347 * Process events pending on this event queue, and wait up to given timeout, if
348 * nothing is available.
349 *
350 * Must be called on same thread this event queue was created on.
351 *
352 * @param cMsTimeout The timeout specified as milliseconds. Use
353 * RT_INDEFINITE_WAIT to wait till an event is posted on the
354 * queue.
355 *
356 * @returns VBox status code
357 * @retval VINF_SUCCESS
358 * @retval VERR_TIMEOUT
359 * @retval VERR_INVALID_CONTEXT
360 */
361int EventQueue::processEventQueue(uint32_t cMsTimeout)
362{
363 int rc;
364 CHECK_THREAD_RET(VERR_INVALID_CONTEXT);
365
366#if defined (VBOX_WITH_XPCOM)
367 /*
368 * Process pending events, if none are available and we're not in a
369 * poll call, wait for some to appear. (We have to be a little bit
370 * careful after waiting for the events since darwin will process
371 * them as part of the wait, while the unix case will not.)
372 *
373 * Note! Unfortunately, WaitForEvent isn't interruptible with Ctrl-C,
374 * while select() is. So we cannot use it for indefinite waits.
375 */
376 rc = processPendingEvents(mEventQ);
377 if ( rc == VERR_TIMEOUT
378 && cMsTimeout > 0)
379 {
380# ifdef RT_OS_DARWIN
381 /** @todo check how Ctrl-C works on darwin. */
382 rc = waitForEventsOnDarwin(cMsTimeout);
383 if (rc == VERR_TIMEOUT)
384 rc = processPendingEvents(mEventQ);
385# else
386 rc = waitForEventsOnUnix(mEventQ, cMsTimeout);
387 if ( RT_SUCCESS(rc)
388 || rc == VERR_TIMEOUT)
389 rc = processPendingEvents(mEventQ);
390# endif
391 }
392
393#else /* !VBOX_WITH_XPCOM */
394 if (cMsTimeout == RT_INDEFINITE_WAIT)
395 {
396 Event *aEvent = NULL;
397
398 BOOL fHasEvent = waitForEvent(&aEvent);
399 if (fHasEvent)
400 {
401 handleEvent(aEvent);
402 rc = processPendingEvents();
403 if (rc == VERR_TIMEOUT)
404 rc = VINF_SUCCESS;
405 }
406 else
407 rc = VERR_INTERRUPTED;
408 }
409 else
410 {
411 uint64_t const StartTS = RTTimeMilliTS();
412 for (;;)
413 {
414 rc = processPendingEvents();
415 if ( rc != VERR_TIMEOUT
416 || cMsTimeout == 0)
417 break;
418
419 uint64_t cMsElapsed = RTTimeMilliTS() - StartTS;
420 if (cMsElapsed >= cMsTimeout)
421 {
422 rc = VERR_TIMEOUT;
423 break;
424 }
425 uint32_t cMsLeft = cMsTimeout - (unsigned)cMsElapsed;
426 DWORD rcW = MsgWaitForMultipleObjects(1,
427 &mhThread,
428 TRUE /*fWaitAll*/,
429 cMsLeft,
430 QS_ALLINPUT);
431 AssertMsgBreakStmt(rcW == WAIT_TIMEOUT || rcW == WAIT_OBJECT_0,
432 ("%d\n", rcW),
433 rc = VERR_INTERNAL_ERROR_4);
434 }
435 }
436#endif /* !VBOX_WITH_XPCOM */
437 return rc;
438}
439
440/**
441 * Interrupt thread waiting on event queue processing.
442 *
443 * Can be called on any thread.
444 */
445int EventQueue::interruptEventQueueProcessing()
446{
447 /** @todo: rethink me! */
448 postEvent(NULL);
449 return VINF_SUCCESS;
450}
451
452/**
453 * Posts an event to this event loop asynchronously.
454 *
455 * @param event the event to post, must be allocated using |new|
456 * @return TRUE if successful and false otherwise
457 */
458BOOL EventQueue::postEvent (Event *event)
459{
460#if defined (RT_OS_WINDOWS)
461
462 return PostThreadMessage (mThreadId, WM_USER, (WPARAM) event, NULL);
463
464#else
465
466 if (!mEventQ)
467 return FALSE;
468
469 MyPLEvent *ev = new MyPLEvent (event);
470 mEventQ->InitEvent (ev, this, plEventHandler, plEventDestructor);
471 HRESULT rc = mEventQ->PostEvent (ev);
472 return NS_SUCCEEDED(rc);
473
474#endif
475}
476
477/**
478 * Waits for a single event.
479 * This method must be called on the same thread where this event queue
480 * is created.
481 *
482 * After this method returns TRUE and non-NULL event, the caller should call
483 * #handleEvent() in order to process the returned event (otherwise the event
484 * is just removed from the queue, but not processed).
485 *
486 * There is a special case when the returned event is NULL (and the method
487 * returns TRUE), meaning that this event queue must finish its execution
488 * (i.e., quit the event loop),
489 *
490 * @param event next event removed from the queue
491 * @return TRUE if successful and false otherwise
492 */
493BOOL EventQueue::waitForEvent (Event **event)
494{
495 Assert (event);
496 if (!event)
497 return FALSE;
498
499 *event = NULL;
500
501 CHECK_THREAD_RET (FALSE);
502
503#if defined (RT_OS_WINDOWS)
504
505 MSG msg;
506 BOOL rc = GetMessage (&msg, NULL, WM_USER, WM_USER);
507 // check for error
508 if (rc == -1)
509 return FALSE;
510 // check for WM_QUIT
511 if (!rc)
512 return TRUE;
513
514 // retrieve our event
515 *event = (Event *) msg.wParam;
516
517#else
518
519 PLEvent *ev = NULL;
520 HRESULT rc;
521
522 mGotEvent = FALSE;
523
524 do
525 {
526 rc = mEventQ->WaitForEvent (&ev);
527 // check for error
528 if (FAILED (rc))
529 return FALSE;
530 // check for EINTR signal
531 if (!ev)
532 return TRUE;
533
534 // run PLEvent handler. This will just set mLastEvent if it is an
535 // MyPLEvent instance, and then delete ev.
536 mEventQ->HandleEvent (ev);
537 }
538 while (!mGotEvent);
539
540 // retrieve our event
541 *event = mLastEvent;
542
543#endif
544
545 return TRUE;
546}
547
548/**
549 * Handles the given event and |delete|s it.
550 * This method must be called on the same thread where this event queue
551 * is created.
552 */
553BOOL EventQueue::handleEvent (Event *event)
554{
555 Assert (event);
556 if (!event)
557 return FALSE;
558
559 CHECK_THREAD_RET (FALSE);
560
561 event->handler();
562 delete event;
563
564 return TRUE;
565}
566
567/**
568 * Get select()'able selector for this event queue.
569 * This will return -1 on platforms and queue variants not supporting such
570 * functionality.
571 */
572int EventQueue::getSelectFD()
573{
574#ifdef VBOX_WITH_XPCOM
575 return mEventQ->GetEventQueueSelectFD();
576#else
577 return -1;
578#endif
579}
580}
581/* 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