VirtualBox

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

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

Main: coding style fixes

  • 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 26186 2010-02-03 13:07:12Z 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.

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