Skip to content

Commit f5ab12c

Browse files
pmjphilmd
authored andcommitted
ui & main loop: Redesign of system-specific main thread event handling
macOS's Cocoa event handling must be done on the initial (main) thread of the process. Furthermore, if library or application code uses libdispatch, the main dispatch queue must be handling events on the main thread as well. So far, this has affected Qemu in both the Cocoa and SDL UIs, although in different ways: the Cocoa UI replaces the default qemu_main function with one that spins Qemu's internal main event loop off onto a background thread. SDL (which uses Cocoa internally) on the other hand uses a polling approach within Qemu's main event loop. Events are polled during the SDL UI's dpy_refresh callback, which happens to run on the main thread by default. As UIs are mutually exclusive, this works OK as long as nothing else needs platform-native event handling. In the next patch, a new device is introduced based on the ParavirtualizedGraphics.framework in macOS. This uses libdispatch internally, and only works when events are being handled on the main runloop. With the current system, it works when using either the Cocoa or the SDL UI. However, it does not when running headless. Moreover, any attempt to install a similar scheme to the Cocoa UI's main thread replacement fails when combined with the SDL UI. This change tidies up main thread management to be more flexible. * The qemu_main global function pointer is a custom function for the main thread, and it may now be NULL. When it is, the main thread runs the main Qemu loop. This represents the traditional setup. * When non-null, spawning the main Qemu event loop on a separate thread is now done centrally rather than inside the Cocoa UI code. * For most platforms, qemu_main is indeed NULL by default, but on Darwin, it defaults to a function that runs the CFRunLoop. * The Cocoa UI sets qemu_main to a function which runs the NSApplication event handling runloop, as is usual for a Cocoa app. * The SDL UI overrides the qemu_main function to NULL, thus specifying that Qemu's main loop must run on the main thread. * The GTK UI also overrides the qemu_main function to NULL. * For other UIs, or in the absence of UIs, the platform's default behaviour is followed. This means that on macOS, the platform's runloop events are always handled, regardless of chosen UI. The new PV graphics device will thus work in all configurations. There is no functional change on other operating systems. Implementing this via a global function pointer variable is a bit ugly, but it's probably worth investigating the existing UI thread rule violations in the SDL (e.g. #2537) and GTK+ back-ends. Fixing those issues might precipitate requirements similar but not identical to those of the Cocoa UI; hopefully we'll see some kind of pattern emerge, which can then be used as a basis for an overhaul. (In fact, it may turn out to be simplest to split the UI/native platform event thread from the QEMU main event loop on all platforms, with any UI or even none at all.) Signed-off-by: Phil Dennis-Jordan <[email protected]> Reviewed-by: Akihiko Odaki <[email protected]> Tested-by: Akihiko Odaki <[email protected]> Message-ID: <[email protected]> [PMD: Declare 'qemu_main' symbol in tests/qtest/fuzz/fuzz.c, add missing g_assert_not_reached() call in main()] Signed-off-by: Philippe Mathieu-Daudé <[email protected]>
1 parent 916bf7f commit f5ab12c

File tree

6 files changed

+69
-46
lines changed

6 files changed

+69
-46
lines changed

include/qemu-main.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,19 @@
55
#ifndef QEMU_MAIN_H
66
#define QEMU_MAIN_H
77

8-
int qemu_default_main(void);
8+
/*
9+
* The function to run on the main (initial) thread of the process.
10+
* NULL means QEMU's main event loop.
11+
* When non-NULL, QEMU's main event loop will run on a purposely created
12+
* thread, after which the provided function pointer will be invoked on
13+
* the initial thread.
14+
* This is useful on platforms which treat the main thread as special
15+
* (macOS/Darwin) and/or require all UI API calls to occur from the main
16+
* thread. Those platforms can initialise it to a specific function,
17+
* while UI implementations may reset it to NULL during their init if they
18+
* will handle system and UI events on the main thread via QEMU's own main
19+
* event loop.
20+
*/
921
extern int (*qemu_main)(void);
1022

1123
#endif /* QEMU_MAIN_H */

system/main.c

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,26 +24,56 @@
2424

2525
#include "qemu/osdep.h"
2626
#include "qemu-main.h"
27+
#include "qemu/main-loop.h"
2728
#include "system/system.h"
2829

2930
#ifdef CONFIG_SDL
31+
/*
32+
* SDL insists on wrapping the main() function with its own implementation on
33+
* some platforms; it does so via a macro that renames our main function, so
34+
* <SDL.h> must be #included here even with no SDL code called from this file.
35+
*/
3036
#include <SDL.h>
3137
#endif
3238

33-
int qemu_default_main(void)
39+
#ifdef CONFIG_DARWIN
40+
#include <CoreFoundation/CoreFoundation.h>
41+
#endif
42+
43+
static void *qemu_default_main(void *opaque)
3444
{
3545
int status;
3646

47+
bql_lock();
3748
status = qemu_main_loop();
3849
qemu_cleanup(status);
50+
bql_unlock();
3951

40-
return status;
52+
exit(status);
4153
}
4254

43-
int (*qemu_main)(void) = qemu_default_main;
55+
int (*qemu_main)(void);
56+
57+
#ifdef CONFIG_DARWIN
58+
static int os_darwin_cfrunloop_main(void)
59+
{
60+
CFRunLoopRun();
61+
g_assert_not_reached();
62+
}
63+
int (*qemu_main)(void) = os_darwin_cfrunloop_main;
64+
#endif
4465

4566
int main(int argc, char **argv)
4667
{
4768
qemu_init(argc, argv);
48-
return qemu_main();
69+
bql_unlock();
70+
if (qemu_main) {
71+
QemuThread main_loop_thread;
72+
qemu_thread_create(&main_loop_thread, "qemu_main",
73+
qemu_default_main, NULL, QEMU_THREAD_DETACHED);
74+
return qemu_main();
75+
} else {
76+
qemu_default_main(NULL);
77+
g_assert_not_reached();
78+
}
4979
}

tests/qtest/fuzz/fuzz.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ static FuzzTargetList *fuzz_target_list;
4141
static FuzzTarget *fuzz_target;
4242
static QTestState *fuzz_qts;
4343

44+
int (*qemu_main)(void);
4445

4546

4647
void flush_events(QTestState *s)

ui/cocoa.m

Lines changed: 13 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@
7373
int height;
7474
} QEMUScreen;
7575

76+
@class QemuCocoaPasteboardTypeOwner;
77+
7678
static void cocoa_update(DisplayChangeListener *dcl,
7779
int x, int y, int w, int h);
7880

@@ -107,6 +109,7 @@ static void cocoa_switch(DisplayChangeListener *dcl,
107109
static NSInteger cbchangecount = -1;
108110
static QemuClipboardInfo *cbinfo;
109111
static QemuEvent cbevent;
112+
static QemuCocoaPasteboardTypeOwner *cbowner;
110113

111114
// Utility functions to run specified code block with the BQL held
112115
typedef void (^CodeBlock)(void);
@@ -1326,8 +1329,10 @@ - (void) dealloc
13261329
{
13271330
COCOA_DEBUG("QemuCocoaAppController: dealloc\n");
13281331

1329-
if (cocoaView)
1330-
[cocoaView release];
1332+
[cocoaView release];
1333+
[cbowner release];
1334+
cbowner = nil;
1335+
13311336
[super dealloc];
13321337
}
13331338

@@ -1943,8 +1948,6 @@ - (void)pasteboard:(NSPasteboard *)sender provideDataForType:(NSPasteboardType)t
19431948

19441949
@end
19451950

1946-
static QemuCocoaPasteboardTypeOwner *cbowner;
1947-
19481951
static void cocoa_clipboard_notify(Notifier *notifier, void *data);
19491952
static void cocoa_clipboard_request(QemuClipboardInfo *info,
19501953
QemuClipboardType type);
@@ -2007,43 +2010,8 @@ static void cocoa_clipboard_request(QemuClipboardInfo *info,
20072010
}
20082011
}
20092012

2010-
/*
2011-
* The startup process for the OSX/Cocoa UI is complicated, because
2012-
* OSX insists that the UI runs on the initial main thread, and so we
2013-
* need to start a second thread which runs the qemu_default_main():
2014-
* in main():
2015-
* in cocoa_display_init():
2016-
* assign cocoa_main to qemu_main
2017-
* create application, menus, etc
2018-
* in cocoa_main():
2019-
* create qemu-main thread
2020-
* enter OSX run loop
2021-
*/
2022-
2023-
static void *call_qemu_main(void *opaque)
2024-
{
2025-
int status;
2026-
2027-
COCOA_DEBUG("Second thread: calling qemu_default_main()\n");
2028-
bql_lock();
2029-
status = qemu_default_main();
2030-
bql_unlock();
2031-
COCOA_DEBUG("Second thread: qemu_default_main() returned, exiting\n");
2032-
[cbowner release];
2033-
exit(status);
2034-
}
2035-
20362013
static int cocoa_main(void)
20372014
{
2038-
QemuThread thread;
2039-
2040-
COCOA_DEBUG("Entered %s()\n", __func__);
2041-
2042-
bql_unlock();
2043-
qemu_thread_create(&thread, "qemu_main", call_qemu_main,
2044-
NULL, QEMU_THREAD_DETACHED);
2045-
2046-
// Start the main event loop
20472015
COCOA_DEBUG("Main thread: entering OSX run loop\n");
20482016
[NSApp run];
20492017
COCOA_DEBUG("Main thread: left OSX run loop, which should never happen\n");
@@ -2125,8 +2093,6 @@ static void cocoa_display_init(DisplayState *ds, DisplayOptions *opts)
21252093

21262094
COCOA_DEBUG("qemu_cocoa: cocoa_display_init\n");
21272095

2128-
qemu_main = cocoa_main;
2129-
21302096
// Pull this console process up to being a fully-fledged graphical
21312097
// app with a menubar and Dock icon
21322098
ProcessSerialNumber psn = { 0, kCurrentProcess };
@@ -2190,6 +2156,12 @@ static void cocoa_display_init(DisplayState *ds, DisplayOptions *opts)
21902156
qemu_clipboard_peer_register(&cbpeer);
21912157

21922158
[pool release];
2159+
2160+
/*
2161+
* The Cocoa UI will run the NSApplication runloop on the main thread
2162+
* rather than the default Core Foundation one.
2163+
*/
2164+
qemu_main = cocoa_main;
21932165
}
21942166

21952167
static QemuDisplay qemu_display_cocoa = {

ui/gtk.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
#include "qemu/cutils.h"
3939
#include "qemu/error-report.h"
4040
#include "qemu/main-loop.h"
41+
#include "qemu-main.h"
4142

4243
#include "ui/console.h"
4344
#include "ui/gtk.h"
@@ -2485,6 +2486,9 @@ static void gtk_display_init(DisplayState *ds, DisplayOptions *opts)
24852486
#ifdef CONFIG_GTK_CLIPBOARD
24862487
gd_clipboard_init(s);
24872488
#endif /* CONFIG_GTK_CLIPBOARD */
2489+
2490+
/* GTK's event polling must happen on the main thread. */
2491+
qemu_main = NULL;
24882492
}
24892493

24902494
static void early_gtk_display_init(DisplayOptions *opts)

ui/sdl2.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
#include "system/system.h"
3535
#include "ui/win32-kbd-hook.h"
3636
#include "qemu/log.h"
37+
#include "qemu-main.h"
3738

3839
static int sdl2_num_outputs;
3940
static struct sdl2_console *sdl2_console;
@@ -965,6 +966,9 @@ static void sdl2_display_init(DisplayState *ds, DisplayOptions *o)
965966
}
966967

967968
atexit(sdl_cleanup);
969+
970+
/* SDL's event polling (in dpy_refresh) must happen on the main thread. */
971+
qemu_main = NULL;
968972
}
969973

970974
static QemuDisplay qemu_display_sdl2 = {

0 commit comments

Comments
 (0)