VirtualBox

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

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

EventQueue.cpp: check interrupted status before waiting for the next message

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