VirtualBox

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

Last change on this file since 31579 was 31579, checked in by vboxsync, 14 years ago

EventQueue: Fix losing messages, use the right queue type on XPCOM (the fact that event handling in VBoxSVC worked was mainly luck), big code cleanup. VBoxHeadless and VirtualBoxImpl now use the only remaining event processing style. Eliminated redundant custom StateChange event in VBoxHeadless.

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