VirtualBox

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

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

Automated rebranding to Oracle copyright/license strings via filemuncher

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