VirtualBox

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

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

EventQueue: Don't return VERR_TIMEOUT if we processed a message in waitForEventsOnDarwin. return VINF_INTERRUPTED when encountering EINTR.

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