VirtualBox

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

Last change on this file since 22722 was 22722, checked in by vboxsync, 16 years ago

VBoxCOM,VBoxManage,WebService: Event queue fun.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 16.0 KB
Line 
1/* $Id: EventQueue.cpp 22722 2009-09-02 15:05:57Z 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.215389.xyz. 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
72/**
73 * Constructs an event queue for the current thread.
74 *
75 * Currently, there can be only one event queue per thread, so if an event
76 * queue for the current thread already exists, this object is simply attached
77 * to the existing event queue.
78 */
79EventQueue::EventQueue()
80{
81#if defined (RT_OS_WINDOWS)
82
83 mThreadId = GetCurrentThreadId();
84 // force the system to create the message queue for the current thread
85 MSG msg;
86 PeekMessage (&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
87
88#else
89
90 mEQCreated = FALSE;
91
92 mLastEvent = NULL;
93 mGotEvent = FALSE;
94
95 // Here we reference the global nsIEventQueueService instance and hold it
96 // until we're destroyed. This is necessary to keep NS_ShutdownXPCOM() away
97 // from calling StopAcceptingEvents() on all event queues upon destruction of
98 // nsIEventQueueService, and makes sense when, for some reason, this happens
99 // *before* we're able to send a NULL event to stop our event handler thread
100 // when doing unexpected cleanup caused indirectly by NS_ShutdownXPCOM()
101 // that is performing a global cleanup of everything. A good example of such
102 // situation is when NS_ShutdownXPCOM() is called while the VirtualBox component
103 // is still alive (because it is still referenced): eventually, it results in
104 // a VirtualBox::uninit() call from where it is already not possible to post
105 // NULL to the event thread (because it stopped accepting events).
106
107 nsresult rc = NS_GetEventQueueService (getter_AddRefs (mEventQService));
108
109 if (NS_SUCCEEDED(rc))
110 {
111 rc = mEventQService->GetThreadEventQueue (NS_CURRENT_THREAD,
112 getter_AddRefs (mEventQ));
113 if (rc == NS_ERROR_NOT_AVAILABLE)
114 {
115 rc = mEventQService->CreateMonitoredThreadEventQueue();
116 if (NS_SUCCEEDED(rc))
117 {
118 mEQCreated = TRUE;
119 rc = mEventQService->GetThreadEventQueue (NS_CURRENT_THREAD,
120 getter_AddRefs (mEventQ));
121 }
122 }
123 }
124 AssertComRC (rc);
125
126#endif
127}
128
129EventQueue::~EventQueue()
130{
131#if defined (RT_OS_WINDOWS)
132#else
133 // process all pending events before destruction
134 if (mEventQ)
135 {
136 if (mEQCreated)
137 {
138 mEventQ->StopAcceptingEvents();
139 mEventQ->ProcessPendingEvents();
140 mEventQService->DestroyThreadEventQueue();
141 }
142 mEventQ = nsnull;
143 mEventQService = nsnull;
144 }
145#endif
146}
147
148/**
149 * Posts an event to this event loop asynchronously.
150 *
151 * @param event the event to post, must be allocated using |new|
152 * @return TRUE if successful and false otherwise
153 */
154BOOL EventQueue::postEvent (Event *event)
155{
156#if defined (RT_OS_WINDOWS)
157
158 return PostThreadMessage (mThreadId, WM_USER, (WPARAM) event, NULL);
159
160#else
161
162 if (!mEventQ)
163 return FALSE;
164
165 MyPLEvent *ev = new MyPLEvent (event);
166 mEventQ->InitEvent (ev, this, plEventHandler, plEventDestructor);
167 HRESULT rc = mEventQ->PostEvent (ev);
168 return NS_SUCCEEDED(rc);
169
170#endif
171}
172
173/**
174 * Waits for a single event.
175 * This method must be called on the same thread where this event queue
176 * is created.
177 *
178 * After this method returns TRUE and non-NULL event, the caller should call
179 * #handleEvent() in order to process the returned event (otherwise the event
180 * is just removed from the queue, but not processed).
181 *
182 * There is a special case when the returned event is NULL (and the method
183 * returns TRUE), meaning that this event queue must finish its execution
184 * (i.e., quit the event loop),
185 *
186 * @param event next event removed from the queue
187 * @return TRUE if successful and false otherwise
188 */
189BOOL EventQueue::waitForEvent (Event **event)
190{
191 Assert (event);
192 if (!event)
193 return FALSE;
194
195 *event = NULL;
196
197 CHECK_THREAD_RET (FALSE);
198
199#if defined (RT_OS_WINDOWS)
200
201 MSG msg;
202 BOOL rc = GetMessage (&msg, NULL, WM_USER, WM_USER);
203 // check for error
204 if (rc == -1)
205 return FALSE;
206 // check for WM_QUIT
207 if (!rc)
208 return TRUE;
209
210 // retrieve our event
211 *event = (Event *) msg.wParam;
212
213#else
214
215 PLEvent *ev = NULL;
216 HRESULT rc;
217
218 mGotEvent = FALSE;
219
220 do
221 {
222 rc = mEventQ->WaitForEvent (&ev);
223 // check for error
224 if (FAILED (rc))
225 return FALSE;
226 // check for EINTR signal
227 if (!ev)
228 return TRUE;
229
230 // run PLEvent handler. This will just set mLastEvent if it is an
231 // MyPLEvent instance, and then delete ev.
232 mEventQ->HandleEvent (ev);
233 }
234 while (!mGotEvent);
235
236 // retrieve our event
237 *event = mLastEvent;
238
239#endif
240
241 return TRUE;
242}
243
244/**
245 * Handles the given event and |delete|s it.
246 * This method must be called on the same thread where this event queue
247 * is created.
248 */
249BOOL EventQueue::handleEvent (Event *event)
250{
251 Assert (event);
252 if (!event)
253 return FALSE;
254
255 CHECK_THREAD_RET (FALSE);
256
257 event->handler();
258 delete event;
259
260 return TRUE;
261}
262
263
264#ifdef VBOX_WITH_XPCOM
265
266/** Wrapper around nsIEventQueue::PendingEvents. */
267DECLINLINE(bool) hasEventQueuePendingEvents(nsIEventQueue *pQueue)
268{
269 PRBool fHasEvents = PR_FALSE;
270 nsresult rc = pQueue->PendingEvents(&fHasEvents);
271 return NS_SUCCEEDED(rc) && fHasEvents ? true : false;
272}
273
274/** Wrapper around nsIEventQueue::IsQueueNative. */
275DECLINLINE(bool) isEventQueueNative(nsIEventQueue *pQueue)
276{
277 PRBool fIsNative = PR_FALSE;
278 nsresult rc = pQueue->IsQueueNative(&fIsNative);
279 return NS_SUCCEEDED(rc) && fIsNative ? true : false;
280}
281
282/** Wrapper around nsIEventQueue::ProcessPendingEvents. */
283DECLINLINE(void) processPendingEvents(nsIEventQueue *pQueue)
284{
285 pQueue->ProcessPendingEvents();
286}
287
288#else
289
290/** For automatic cleanup. */
291class MyThreadHandle
292{
293public:
294 HANDLE mh;
295
296 MyThreadHandle(HANDLE hThread)
297 {
298 if (!DuplicateHandle(GetCurrentProcess(), hThread, GetCurrentProcess(),
299 &mh, 0 /*dwDesiredAccess*/, FALSE /*bInheritHandle*/,
300 DUPLICATE_SAME_ACCESS))
301 mh = INVALID_HANDLE_VALUE;
302 }
303
304 ~MyThreadHandle()
305 {
306 CloseHandle(mh);
307 mh = INVALID_HANDLE_VALUE;
308 }
309};
310
311/** COM version of nsIEventQueue::PendingEvents. */
312DECLINLINE(bool) hasEventQueuePendingEvents(MyThreadHandle &Handle)
313{
314 DWORD rc = MsgWaitForMultipleObjects(1, &Handle.mh, TRUE /*fWaitAll*/, 0 /*ms*/, QS_ALLINPUT);
315 return rc == WAIT_OBJECT_0;
316}
317
318/** COM version of nsIEventQueue::IsQueueNative, the question doesn't make
319 * sense and we have to return false for the code below to work. */
320DECLINLINE(bool) isEventQueueNative(MyThreadHandle const &Handle)
321{
322 return false;
323}
324
325/** COM version of nsIEventQueue::ProcessPendingEvents. */
326static void processPendingEvents(MyThreadHandle const &Handle)
327{
328 /*
329 * Process pending thead messages.
330 */
331 MSG Msg;
332 while (PeekMessage(&Msg, NULL /*hWnd*/, 0 /*wMsgFilterMin*/, 0 /*wMsgFilterMax*/, PM_REMOVE))
333 {
334 if (Msg.message == WM_QUIT)
335 return /*VERR_INTERRUPTED*/;
336 DispatchMessage(&Msg);
337 }
338}
339
340#endif /* VBOX_WITH_XPCOM */
341
342/**
343 * Processes events for the current thread.
344 *
345 * @param cMsTimeout The timeout in milliseconds or RT_INDEFINITE_WAIT.
346 * @param pfnExitCheck Optional callback for checking for some exit condition
347 * while looping. Note that this may be called
348 * @param pvUser User argument for pfnExitCheck.
349 * @param cMsPollInterval The interval cMsTimeout should be called at. 0 means
350 * never default.
351 * @param fReturnOnEvent If true, return immediately after some events has
352 * been processed. If false, process events until we
353 * time out, pfnExitCheck returns true, interrupted or
354 * the queue receives some kind of quit message.
355 *
356 * @returns VBox status code.
357 * @retval VINF_SUCCESS if events were processed.
358 * @retval VERR_TIMEOUT if no events before cMsTimeout elapsed.
359 * @retval VERR_INTERRUPTED if the wait was interrupted by a signal or other
360 * async event.
361 * @retval VERR_NOT_FOUND if the thread has no event queue.
362 * @retval VERR_CALLBACK_RETURN if the callback indicates return.
363 *
364 * @todo This is just a quick approximation of what we need. Feel free to
365 * improve the interface and make it fit better in with the EventQueue
366 * class.
367 */
368/*static*/ int
369EventQueue::processThreadEventQueue(uint32_t cMsTimeout, bool (*pfnExitCheck)(void *pvUser) /*= 0*/,
370 void *pvUser /*= 0*/, uint32_t cMsPollInterval /*= 1000*/,
371 bool fReturnOnEvent /*= true*/)
372{
373 uint64_t const StartMsTS = RTTimeMilliTS();
374
375 /* set default. */
376 if (cMsPollInterval == 0)
377 cMsPollInterval = 1000;
378
379 /*
380 * Get the event queue / thread.
381 */
382#ifdef VBOX_WITH_XPCOM
383 nsCOMPtr<nsIEventQueue> q;
384 nsresult rv = NS_GetCurrentEventQ(getter_AddRefs(q));
385 if (NS_FAILED(rv))
386 return VERR_NOT_FOUND;
387#else
388 MyThreadHandle q(GetCurrentThread());
389#endif
390
391 /*
392 * Check for pending before setting up the wait.
393 */
394 if ( !hasEventQueuePendingEvents(q)
395 || !fReturnOnEvent)
396 {
397 bool fIsNative = isEventQueueNative(q);
398 if ( fIsNative
399 || cMsTimeout != RT_INDEFINITE_WAIT
400 || pfnExitCheck
401 || !fReturnOnEvent /** @todo !fReturnOnEvent and cMsTimeout RT_INDEFINITE_WAIT can be handled in else */)
402 {
403#ifdef USE_XPCOM_QUEUE
404 int const fdQueue = fIsNative ? q->GetEventQueueSelectFD() : -1;
405 if (fIsNative && fdQueue == -1)
406 return VERR_INTERNAL_ERROR_4;
407#endif
408 for (;;)
409 {
410 /*
411 * Check for events.
412 */
413 if (hasEventQueuePendingEvents(q))
414 {
415 if (fReturnOnEvent)
416 break;
417 processPendingEvents(q);
418 }
419
420 /*
421 * Check the user exit.
422 */
423 if ( pfnExitCheck
424 && pfnExitCheck(pvUser))
425 return VERR_CALLBACK_RETURN;
426
427 /*
428 * Figure out how much we have left to wait and if we've timed out already.
429 */
430 uint32_t cMsLeft;
431 if (cMsTimeout == RT_INDEFINITE_WAIT)
432 cMsLeft = RT_INDEFINITE_WAIT;
433 else
434 {
435 uint64_t cMsElapsed = RTTimeMilliTS() - StartMsTS;
436 if (cMsElapsed >= cMsTimeout)
437 break; /* timeout */
438 cMsLeft = cMsTimeout - (uint32_t)cMsElapsed;
439 }
440
441 /*
442 * Wait in a queue & platform specific manner.
443 */
444#ifdef VBOX_WITH_XPCOM
445 if (!fIsNative)
446 RTThreadSleep(250 /*ms*/);
447 else
448 {
449# ifdef USE_XPCOM_QUEUE
450 fd_set fdset;
451 FD_ZERO(&fdset);
452 FD_SET(fdQueue, &fdset);
453 struct timeval tv;
454 if ( cMsLeft == RT_INDEFINITE_WAIT
455 || cMsLeft >= cMsPollInterval)
456 {
457 tv.tv_sec = cMsPollInterval / 1000;
458 tv.tv_usec = (cMsPollInterval % 1000) * 1000;
459 }
460 else
461 {
462 tv.tv_sec = cMsLeft / 1000;
463 tv.tv_usec = (cMsLeft % 1000) * 1000;
464 }
465 int prc = select(fdQueue + 1, &fdset, NULL, NULL, &tv);
466 if (prc == -1)
467 return RTErrConvertFromErrno(errno);
468
469# elif defined(RT_OS_DARWIN)
470 CFTimeInterval rdTimeout = (double)RT_MIN(cMsLeft, cMsPollInterval) / 1000;
471 OSStatus orc = CFRunLoopRunInMode(kCFRunLoopDefaultMode, rdTimeout, true /*returnAfterSourceHandled*/);
472 if (orc == kCFRunLoopRunHandledSource)
473 orc = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, false /*returnAfterSourceHandled*/);
474 if ( orc != 0
475 && orc != kCFRunLoopRunHandledSource
476 && orc != kCFRunLoopRunTimedOut)
477 return orc == kCFRunLoopRunStopped || orc == kCFRunLoopRunFinished
478 ? VERR_INTERRUPTED
479 : RTErrConvertFromDarwin(orc);
480# else
481# warning "PORTME:"
482 RTThreadSleep(250);
483# endif
484 }
485
486#else /* !VBOX_WITH_XPCOM */
487 DWORD rc = MsgWaitForMultipleObjects(1, &q.mh, TRUE /*fWaitAll*/, RT_MIN(cMsLeft, cMsPollInterval), QS_ALLINPUT);
488 if (rc == WAIT_OBJECT_0)
489 {
490 if (fReturnOnEvent)
491 break;
492 processPendingEvents(q);
493 }
494 else if (rc == WAIT_FAILED)
495 return RTErrConvertFromWin32(GetLastError());
496 else if (rc != WAIT_TIMEOUT)
497 return VERR_INTERNAL_ERROR_4;
498#endif /* !VBOX_WITH_XPCOM */
499 } /* for (;;) */
500 }
501 else
502 {
503 /*
504 * Indefinite wait without any complications.
505 */
506#ifdef VBOX_WITH_XPCOM
507 PLEvent *pEvent = NULL;
508 rv = q->WaitForEvent(&pEvent);
509 if (NS_FAILED(rv))
510 return VERR_INTERRUPTED;
511 q->HandleEvent(pEvent);
512#else
513 DWORD rc = MsgWaitForMultipleObjects(1, &q.mh, TRUE /*fWaitAll*/, INFINITE, QS_ALLINPUT);
514 if (rc != WAIT_OBJECT_0)
515 {
516 if (rc == WAIT_FAILED)
517 return RTErrConvertFromWin32(GetLastError());
518 return VERR_INTERNAL_ERROR_3;
519 }
520#endif
521 }
522 }
523
524 /*
525 * We have/had events in the queue. Process pending events and
526 * return successfully.
527 */
528 processPendingEvents(q);
529
530 return VINF_SUCCESS;
531}
532
533}
534/* namespace com */
535
Note: See TracBrowser for help on using the repository browser.

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