VirtualBox

source: vbox/trunk/src/VBox/Main/glue/NativeEventQueue.cpp@ 46649

最後變更 在這個檔案從46649是 46649,由 vboxsync 提交於 12 年 前

Forward ported r85941 and required build fixes (Main: Implemented new event queue to separate system's native event queue and our own. Also, XPCOM is not needed for handling our own events. On Windows this also fixes the system's queue quota limitation).

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

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