From 6cfd2d4f191d444c822adfe6355c973a580ffe17 Mon Sep 17 00:00:00 2001 From: Christophe Fergeau Date: Fri, 10 Nov 2023 12:12:59 +0100 Subject: [PATCH 1/2] gui-linux: Add reproducer for hardstop issue When calling vm.Stop() while using the GUI, the VM stops, but the GUI is not closed and the application keeps running. Using vm.RequestStop() does not have this issue. This commit modifies example/gui-linux to exhibit the issue. --- example/gui-linux/main.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/example/gui-linux/main.go b/example/gui-linux/main.go index 5db3f99a..b98bdd71 100644 --- a/example/gui-linux/main.go +++ b/example/gui-linux/main.go @@ -77,6 +77,17 @@ func run(ctx context.Context) error { } }() + go func() { + if !vm.CanStop() { + log.Println("cannot stop vm forcefully") + return + } + time.Sleep(10 * time.Second) + log.Println("calling vm.Stop()") + + vm.Stop() + }() + // cleanup is this function is useful when finished graphic application. cleanup := func() { for i := 1; vm.CanRequestStop(); i++ { From 999f00aafaa5df2910082e45aefd15a34864bb1a Mon Sep 17 00:00:00 2001 From: Christophe Fergeau Date: Fri, 10 Nov 2023 11:15:11 +0100 Subject: [PATCH 2/2] gui: Watch vm state to terminate when it's stopped The GUI code in virtualization_view.m is notified when there was a guest initiated shutdown ('guestDidStopVirtualMachine') and when there was a virtualization error ('didStopWithError'). When calling Stop(), the VM is forcefully stopped (similar to pulling the plug on real hardware). This action is neither a guest initiated shutdown, nor a virtualization error, so the GUI code does not catch it. This means after calling vm.Stop(), the GUI main loop will keep running, and the application using Code-Hex/vz will never exit. This commit fixes this by adding an observer for VM state changes, and by calling 'terminate' when the VM state becomes 'stopped' or 'error'. This fixes https://github.com/Code-Hex/vz/issues/150 --- virtualization_view.m | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/virtualization_view.m b/virtualization_view.m index 9fbf6d18..7e20fafd 100644 --- a/virtualization_view.m +++ b/virtualization_view.m @@ -165,11 +165,30 @@ - (instancetype)init @end +API_AVAILABLE(macos(12.0)) +@interface VMStateObserver : NSObject +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context; +@end + +@implementation VMStateObserver +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context; +{ + if ([keyPath isEqualToString:@"state"]) { + int newState = (int)[change[NSKeyValueChangeNewKey] integerValue]; + if (newState == VZVirtualMachineStateStopped || newState == VZVirtualMachineStateError) { + [NSApp performSelectorOnMainThread:@selector(terminate:) withObject:context waitUntilDone:NO]; + [object removeObserver:self forKeyPath:@"state"]; + } + } +} +@end + @implementation AppDelegate { VZVirtualMachine *_virtualMachine; VZVirtualMachineView *_virtualMachineView; CGFloat _windowWidth; CGFloat _windowHeight; + VMStateObserver *_observer; } - (instancetype)initWithVirtualMachine:(VZVirtualMachine *)virtualMachine @@ -179,6 +198,11 @@ - (instancetype)initWithVirtualMachine:(VZVirtualMachine *)virtualMachine self = [super init]; _virtualMachine = virtualMachine; [_virtualMachine setDelegate:self]; + _observer = [[VMStateObserver alloc] init]; + [virtualMachine addObserver:_observer + forKeyPath:@"state" + options:NSKeyValueObservingOptionNew + context:(void *)self]; // Setup virtual machine view configs VZVirtualMachineView *view = [[[VZVirtualMachineView alloc] init] autorelease];