ui/cocoa: Run qemu_init in the main thread
This work is based on: https://patchew.org/QEMU/20220317125534.38706-1-philippe.mathieu.daude@gmail.com/ Simplify the initialization dance by running qemu_init() in the main thread before the Cocoa event loop starts. The secondary thread only runs only qemu_main_loop() and qemu_cleanup(). This fixes a case where addRemovableDevicesMenuItems() calls qmp_query_block() while expecting the main thread to still hold the BQL. Overriding the code after calling qemu_init() is done by dynamically replacing a function pointer variable, qemu_main when initializing ui/cocoa, which unifies the static implementation of main() for builds with ui/cocoa and ones without ui/cocoa. Signed-off-by: Akihiko Odaki <akihiko.odaki@gmail.com> Message-Id: <20220819132756.74641-2-akihiko.odaki@gmail.com> Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
This commit is contained in:
parent
df6322a897
commit
bab6a301c5
@ -287,8 +287,8 @@ select the fuzz target. Then, the qtest client is initialized. If the target
|
||||
requires qos, qgraph is set up and the QOM/LIBQOS modules are initialized.
|
||||
Then the QGraph is walked and the QEMU cmd_line is determined and saved.
|
||||
|
||||
After this, the ``vl.c:qemu_main`` is called to set up the guest. There are
|
||||
target-specific hooks that can be called before and after qemu_main, for
|
||||
After this, the ``vl.c:main`` is called to set up the guest. There are
|
||||
target-specific hooks that can be called before and after main, for
|
||||
additional setup(e.g. PCI setup, or VM snapshotting).
|
||||
|
||||
``LLVMFuzzerTestOneInput``: Uses qtest/qos functions to act based on the fuzz
|
||||
|
@ -5,6 +5,7 @@
|
||||
#ifndef QEMU_MAIN_H
|
||||
#define QEMU_MAIN_H
|
||||
|
||||
int qemu_main(int argc, char **argv, char **envp);
|
||||
int qemu_default_main(void);
|
||||
extern int (*qemu_main)(void);
|
||||
|
||||
#endif /* QEMU_MAIN_H */
|
||||
|
@ -102,7 +102,7 @@ void qemu_boot_set(const char *boot_order, Error **errp);
|
||||
|
||||
bool defaults_enabled(void);
|
||||
|
||||
void qemu_init(int argc, char **argv, char **envp);
|
||||
void qemu_init(int argc, char **argv);
|
||||
int qemu_main_loop(void);
|
||||
void qemu_cleanup(void);
|
||||
|
||||
|
@ -30,20 +30,20 @@
|
||||
#include <SDL.h>
|
||||
#endif
|
||||
|
||||
int qemu_main(int argc, char **argv, char **envp)
|
||||
int qemu_default_main(void)
|
||||
{
|
||||
int status;
|
||||
|
||||
qemu_init(argc, argv, envp);
|
||||
status = qemu_main_loop();
|
||||
qemu_cleanup();
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
#ifndef CONFIG_COCOA
|
||||
int (*qemu_main)(void) = qemu_default_main;
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
return qemu_main(argc, argv, NULL);
|
||||
qemu_init(argc, argv);
|
||||
return qemu_main();
|
||||
}
|
||||
#endif
|
||||
|
@ -2605,7 +2605,7 @@ void qmp_x_exit_preconfig(Error **errp)
|
||||
}
|
||||
}
|
||||
|
||||
void qemu_init(int argc, char **argv, char **envp)
|
||||
void qemu_init(int argc, char **argv)
|
||||
{
|
||||
QemuOpts *opts;
|
||||
QemuOpts *icount_opts = NULL, *accel_opts = NULL;
|
||||
|
@ -218,7 +218,7 @@ int LLVMFuzzerInitialize(int *argc, char ***argv, char ***envp)
|
||||
g_free(pretty_cmd_line);
|
||||
}
|
||||
|
||||
qemu_init(result.we_wordc, result.we_wordv, NULL);
|
||||
qemu_init(result.we_wordc, result.we_wordv);
|
||||
|
||||
/* re-enable the rcu atfork, which was previously disabled in qemu_init */
|
||||
rcu_enable_atfork();
|
||||
|
132
ui/cocoa.m
132
ui/cocoa.m
@ -100,13 +100,9 @@ static int cursor_hide = 1;
|
||||
static int left_command_key_enabled = 1;
|
||||
static bool swap_opt_cmd;
|
||||
|
||||
static int gArgc;
|
||||
static char **gArgv;
|
||||
static bool stretch_video;
|
||||
static NSTextField *pauseLabel;
|
||||
|
||||
static QemuSemaphore display_init_sem;
|
||||
static QemuSemaphore app_started_sem;
|
||||
static bool allow_events;
|
||||
|
||||
static NSInteger cbchangecount = -1;
|
||||
@ -597,7 +593,7 @@ static CGEventRef handleTapEvent(CGEventTapProxy proxy, CGEventType type, CGEven
|
||||
/*
|
||||
* Don't try to tell QEMU about UI information in the application
|
||||
* startup phase -- we haven't yet registered dcl with the QEMU UI
|
||||
* layer, and also trying to take the iothread lock would deadlock.
|
||||
* layer.
|
||||
* When cocoa_display_init() does register the dcl, the UI layer
|
||||
* will call cocoa_switch(), which will call updateUIInfo, so
|
||||
* we don't lose any information here.
|
||||
@ -790,16 +786,6 @@ static CGEventRef handleTapEvent(CGEventTapProxy proxy, CGEventType type, CGEven
|
||||
|
||||
- (bool) handleEvent:(NSEvent *)event
|
||||
{
|
||||
if(!allow_events) {
|
||||
/*
|
||||
* Just let OSX have all events that arrive before
|
||||
* applicationDidFinishLaunching.
|
||||
* This avoids a deadlock on the iothread lock, which cocoa_display_init()
|
||||
* will not drop until after the app_started_sem is posted. (In theory
|
||||
* there should not be any such events, but OSX Catalina now emits some.)
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
return bool_with_iothread_lock(^{
|
||||
return [self handleEventLocked:event];
|
||||
});
|
||||
@ -1287,8 +1273,6 @@ static CGEventRef handleTapEvent(CGEventTapProxy proxy, CGEventType type, CGEven
|
||||
{
|
||||
COCOA_DEBUG("QemuCocoaAppController: applicationDidFinishLaunching\n");
|
||||
allow_events = true;
|
||||
/* Tell cocoa_display_init to proceed */
|
||||
qemu_sem_post(&app_started_sem);
|
||||
}
|
||||
|
||||
- (void)applicationWillTerminate:(NSNotification *)aNotification
|
||||
@ -1919,92 +1903,45 @@ static void cocoa_clipboard_request(QemuClipboardInfo *info,
|
||||
/*
|
||||
* The startup process for the OSX/Cocoa UI is complicated, because
|
||||
* OSX insists that the UI runs on the initial main thread, and so we
|
||||
* need to start a second thread which runs the vl.c qemu_main():
|
||||
*
|
||||
* Initial thread: 2nd thread:
|
||||
* need to start a second thread which runs the qemu_default_main():
|
||||
* in main():
|
||||
* create qemu-main thread
|
||||
* wait on display_init semaphore
|
||||
* call qemu_main()
|
||||
* ...
|
||||
* in cocoa_display_init():
|
||||
* post the display_init semaphore
|
||||
* wait on app_started semaphore
|
||||
* assign cocoa_main to qemu_main
|
||||
* create application, menus, etc
|
||||
* in cocoa_main():
|
||||
* create qemu-main thread
|
||||
* enter OSX run loop
|
||||
* in applicationDidFinishLaunching:
|
||||
* post app_started semaphore
|
||||
* tell main thread to fullscreen if needed
|
||||
* [...]
|
||||
* run qemu main-loop
|
||||
*
|
||||
* We do this in two stages so that we don't do the creation of the
|
||||
* GUI application menus and so on for command line options like --help
|
||||
* where we want to just print text to stdout and exit immediately.
|
||||
*/
|
||||
|
||||
static void *call_qemu_main(void *opaque)
|
||||
{
|
||||
int status;
|
||||
|
||||
COCOA_DEBUG("Second thread: calling qemu_main()\n");
|
||||
status = qemu_main(gArgc, gArgv, *_NSGetEnviron());
|
||||
COCOA_DEBUG("Second thread: qemu_main() returned, exiting\n");
|
||||
COCOA_DEBUG("Second thread: calling qemu_default_main()\n");
|
||||
qemu_mutex_lock_iothread();
|
||||
status = qemu_default_main();
|
||||
qemu_mutex_unlock_iothread();
|
||||
COCOA_DEBUG("Second thread: qemu_default_main() returned, exiting\n");
|
||||
[cbowner release];
|
||||
exit(status);
|
||||
}
|
||||
|
||||
int main (int argc, char **argv) {
|
||||
static int cocoa_main()
|
||||
{
|
||||
QemuThread thread;
|
||||
|
||||
COCOA_DEBUG("Entered main()\n");
|
||||
gArgc = argc;
|
||||
gArgv = argv;
|
||||
|
||||
qemu_sem_init(&display_init_sem, 0);
|
||||
qemu_sem_init(&app_started_sem, 0);
|
||||
COCOA_DEBUG("Entered %s()\n", __func__);
|
||||
|
||||
qemu_mutex_unlock_iothread();
|
||||
qemu_thread_create(&thread, "qemu_main", call_qemu_main,
|
||||
NULL, QEMU_THREAD_DETACHED);
|
||||
|
||||
COCOA_DEBUG("Main thread: waiting for display_init_sem\n");
|
||||
qemu_sem_wait(&display_init_sem);
|
||||
COCOA_DEBUG("Main thread: initializing app\n");
|
||||
|
||||
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
// Pull this console process up to being a fully-fledged graphical
|
||||
// app with a menubar and Dock icon
|
||||
ProcessSerialNumber psn = { 0, kCurrentProcess };
|
||||
TransformProcessType(&psn, kProcessTransformToForegroundApplication);
|
||||
|
||||
[QemuApplication sharedApplication];
|
||||
|
||||
create_initial_menus();
|
||||
|
||||
/*
|
||||
* Create the menu entries which depend on QEMU state (for consoles
|
||||
* and removeable devices). These make calls back into QEMU functions,
|
||||
* which is OK because at this point we know that the second thread
|
||||
* holds the iothread lock and is synchronously waiting for us to
|
||||
* finish.
|
||||
*/
|
||||
add_console_menu_entries();
|
||||
addRemovableDevicesMenuItems();
|
||||
|
||||
// Create an Application controller
|
||||
QemuCocoaAppController *appController = [[QemuCocoaAppController alloc] init];
|
||||
[NSApp setDelegate:appController];
|
||||
|
||||
// Start the main event loop
|
||||
COCOA_DEBUG("Main thread: entering OSX run loop\n");
|
||||
[NSApp run];
|
||||
COCOA_DEBUG("Main thread: left OSX run loop, exiting\n");
|
||||
COCOA_DEBUG("Main thread: left OSX run loop, which should never happen\n");
|
||||
|
||||
[appController release];
|
||||
[pool release];
|
||||
|
||||
return 0;
|
||||
abort();
|
||||
}
|
||||
|
||||
|
||||
@ -2083,25 +2020,42 @@ static void cocoa_refresh(DisplayChangeListener *dcl)
|
||||
|
||||
static void cocoa_display_init(DisplayState *ds, DisplayOptions *opts)
|
||||
{
|
||||
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
COCOA_DEBUG("qemu_cocoa: cocoa_display_init\n");
|
||||
|
||||
/* Tell main thread to go ahead and create the app and enter the run loop */
|
||||
qemu_sem_post(&display_init_sem);
|
||||
qemu_sem_wait(&app_started_sem);
|
||||
COCOA_DEBUG("cocoa_display_init: app start completed\n");
|
||||
qemu_main = cocoa_main;
|
||||
|
||||
// Pull this console process up to being a fully-fledged graphical
|
||||
// app with a menubar and Dock icon
|
||||
ProcessSerialNumber psn = { 0, kCurrentProcess };
|
||||
TransformProcessType(&psn, kProcessTransformToForegroundApplication);
|
||||
|
||||
[QemuApplication sharedApplication];
|
||||
|
||||
create_initial_menus();
|
||||
|
||||
/*
|
||||
* Create the menu entries which depend on QEMU state (for consoles
|
||||
* and removeable devices). These make calls back into QEMU functions,
|
||||
* which is OK because at this point we know that the second thread
|
||||
* holds the iothread lock and is synchronously waiting for us to
|
||||
* finish.
|
||||
*/
|
||||
add_console_menu_entries();
|
||||
addRemovableDevicesMenuItems();
|
||||
|
||||
// Create an Application controller
|
||||
QemuCocoaAppController *controller = [[QemuCocoaAppController alloc] init];
|
||||
[NSApp setDelegate:controller];
|
||||
|
||||
QemuCocoaAppController *controller = (QemuCocoaAppController *)[[NSApplication sharedApplication] delegate];
|
||||
/* if fullscreen mode is to be used */
|
||||
if (opts->has_full_screen && opts->full_screen) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[NSApp activateIgnoringOtherApps: YES];
|
||||
[controller toggleFullScreen: nil];
|
||||
});
|
||||
}
|
||||
if (opts->u.cocoa.has_full_grab && opts->u.cocoa.full_grab) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[controller setFullGrab: nil];
|
||||
});
|
||||
}
|
||||
|
||||
if (opts->has_show_cursor && opts->show_cursor) {
|
||||
@ -2121,6 +2075,8 @@ static void cocoa_display_init(DisplayState *ds, DisplayOptions *opts)
|
||||
qemu_event_init(&cbevent, false);
|
||||
cbowner = [[QemuCocoaPasteboardTypeOwner alloc] init];
|
||||
qemu_clipboard_peer_register(&cbpeer);
|
||||
|
||||
[pool release];
|
||||
}
|
||||
|
||||
static QemuDisplay qemu_display_cocoa = {
|
||||
|
Loading…
Reference in New Issue
Block a user