diff --git a/example/macOS/main.go b/example/macOS/main.go index d9504db..20117cb 100644 --- a/example/macOS/main.go +++ b/example/macOS/main.go @@ -6,9 +6,8 @@ import ( "fmt" "log" "os" - "os/signal" "runtime" - "syscall" + "time" "github.com/Code-Hex/vz/v2" ) @@ -48,9 +47,6 @@ func runVM(ctx context.Context) error { } vm := vz.NewVirtualMachine(config) - signalCh := make(chan os.Signal, 1) - signal.Notify(signalCh, syscall.SIGTERM) - errCh := make(chan error, 1) vm.Start(func(err error) { @@ -59,28 +55,50 @@ func runVM(ctx context.Context) error { } }) - vm.StartGraphicApplication(960, 600) + go func() { + for { + select { + case newState := <-vm.StateChangedNotify(): + if newState == vz.VirtualMachineStateRunning { + log.Println("start VM is running") + } + if newState == vz.VirtualMachineStateStopped || newState == vz.VirtualMachineStateStopping { + log.Println("stopped state") + errCh <- nil + return + } + case err := <-errCh: + errCh <- fmt.Errorf("failed to start vm: %w", err) + return + } + } + }() - for { - select { - case <-signalCh: + // cleanup is this function is useful when finished graphic application. + cleanup := func() { + for i := 1; vm.CanRequestStop(); i++ { result, err := vm.RequestStop() - if err != nil { - return err - } - log.Println("recieved signal", result) - case newState := <-vm.StateChangedNotify(): - if newState == vz.VirtualMachineStateRunning { - log.Println("start VM is running") - } - if newState == vz.VirtualMachineStateStopped { - log.Println("stopped successfully") - return nil + log.Printf("sent stop request(%d): %t, %v", i, result, err) + time.Sleep(time.Second * 3) + if i > 3 { + log.Println("call stop") + vm.Stop(func(err error) { + if err != nil { + log.Println("stop with error", err) + } + }) } - case err := <-errCh: - return fmt.Errorf("failed to start vm: %w", err) } + log.Println("finished cleanup") } + + runtime.LockOSThread() + vm.StartGraphicApplication(960, 600) + runtime.UnlockOSThread() + + cleanup() + + return <-errCh } func computeCPUCount() uint { diff --git a/virtualization.go b/virtualization.go index 18c5666..b94b044 100644 --- a/virtualization.go +++ b/virtualization.go @@ -8,12 +8,17 @@ package vz import "C" import ( "runtime" + "runtime/cgo" "sync" "unsafe" "github.com/rs/xid" ) +func init() { + C.sharedApplication() +} + // VirtualMachineState represents execution state of the virtual machine. type VirtualMachineState int @@ -41,6 +46,10 @@ const ( // VirtualMachineStateResuming The virtual machine is being resumed. // This is the intermediate state between VirtualMachineStatePaused and VirtualMachineStateRunning. VirtualMachineStateResuming + + // VZVirtualMachineStateStopping The virtual machine is being stopped. + // This is the intermediate state between VZVirtualMachineStateRunning and VZVirtualMachineStateStop. + VirtualMachineStateStopping ) // VirtualMachine represents the entire state of a single virtual machine. @@ -77,17 +86,9 @@ type ( mu sync.RWMutex } - machineHandlers struct { - start func(error) - pause func(error) - resume func(error) - } ) -var ( - handlers = map[string]*machineHandlers{} - statuses = map[string]*machineStatus{} -) +var statuses = map[string]*machineStatus{} // NewVirtualMachine creates a new VirtualMachine with VirtualMachineConfiguration. // @@ -104,11 +105,6 @@ func NewVirtualMachine(config *VirtualMachineConfiguration) *VirtualMachine { state: VirtualMachineState(0), stateNotify: make(chan VirtualMachineState), } - handlers[id] = &machineHandlers{ - start: func(error) {}, - pause: func(error) {}, - resume: func(error) {}, - } dispatchQueue := C.makeDispatchQueue(cs.CString()) v := &VirtualMachine{ id: id, @@ -203,37 +199,21 @@ func (v *VirtualMachine) CanRequestStop() bool { return (bool)(C.vmCanRequestStop(v.Ptr(), v.dispatchQueue)) } -//export startHandler -func startHandler(errPtr unsafe.Pointer, cid *C.char) { - id := (*char)(cid).String() - // If returns nil in the cgo world, the nil will not be treated as nil in the Go world - // so this is temporarily handled (Go 1.17) - if err := newNSError(errPtr); err != nil { - handlers[id].start(err) - } else { - handlers[id].start(nil) - } +// CanStop returns whether the machine is in a state that can be stopped. +func (v *VirtualMachine) CanStop() bool { + return (bool)(C.vmCanStop(v.Ptr(), v.dispatchQueue)) } -//export pauseHandler -func pauseHandler(errPtr unsafe.Pointer, cid *C.char) { - id := (*char)(cid).String() - // see: startHandler - if err := newNSError(errPtr); err != nil { - handlers[id].pause(err) - } else { - handlers[id].pause(nil) - } -} +//export virtualMachineCompletionHandler +func virtualMachineCompletionHandler(cgoHandlerPtr, errPtr unsafe.Pointer) { + cgoHandler := *(*cgo.Handle)(cgoHandlerPtr) + + handler := cgoHandler.Value().(func(error)) -//export resumeHandler -func resumeHandler(errPtr unsafe.Pointer, cid *C.char) { - id := (*char)(cid).String() - // see: startHandler if err := newNSError(errPtr); err != nil { - handlers[id].resume(err) + handler(err) } else { - handlers[id].resume(nil) + handler(nil) } } @@ -251,10 +231,9 @@ func makeHandler(fn func(error)) (func(error), chan struct{}) { // The error parameter passed to the block is null if the start was successful. func (v *VirtualMachine) Start(fn func(error)) { h, done := makeHandler(fn) - handlers[v.id].start = h - cid := charWithGoString(v.id) - defer cid.Free() - C.startWithCompletionHandler(v.Ptr(), v.dispatchQueue, cid.CString()) + handler := cgo.NewHandle(h) + defer handler.Delete() + C.startWithCompletionHandler(v.Ptr(), v.dispatchQueue, unsafe.Pointer(&handler)) <-done } @@ -264,10 +243,9 @@ func (v *VirtualMachine) Start(fn func(error)) { // The error parameter passed to the block is null if the start was successful. func (v *VirtualMachine) Pause(fn func(error)) { h, done := makeHandler(fn) - handlers[v.id].pause = h - cid := charWithGoString(v.id) - defer cid.Free() - C.pauseWithCompletionHandler(v.Ptr(), v.dispatchQueue, cid.CString()) + handler := cgo.NewHandle(h) + defer handler.Delete() + C.pauseWithCompletionHandler(v.Ptr(), v.dispatchQueue, unsafe.Pointer(&handler)) <-done } @@ -277,10 +255,9 @@ func (v *VirtualMachine) Pause(fn func(error)) { // The error parameter passed to the block is null if the resumption was successful. func (v *VirtualMachine) Resume(fn func(error)) { h, done := makeHandler(fn) - handlers[v.id].resume = h - cid := charWithGoString(v.id) - defer cid.Free() - C.resumeWithCompletionHandler(v.Ptr(), v.dispatchQueue, cid.CString()) + handler := cgo.NewHandle(h) + defer handler.Delete() + C.resumeWithCompletionHandler(v.Ptr(), v.dispatchQueue, unsafe.Pointer(&handler)) <-done } @@ -298,6 +275,21 @@ func (v *VirtualMachine) RequestStop() (bool, error) { return ret, nil } +// Stop stops a VM that’s in either a running or paused state. +// +// The completion handler returns an error object when the VM fails to stop, +// or nil if the stop was successful. +// +// Warning: This is a destructive operation. It stops the VM without +// giving the guest a chance to stop cleanly. +func (v *VirtualMachine) Stop(fn func(error)) { + h, done := makeHandler(fn) + handler := cgo.NewHandle(h) + defer handler.Delete() + C.stopWithCompletionHandler(v.Ptr(), v.dispatchQueue, unsafe.Pointer(&handler)) + <-done +} + // StartGraphicApplication starts an application to display graphics of the VM. // // You must to call runtime.LockOSThread before calling this method. diff --git a/virtualization.h b/virtualization.h index 196e409..c0ee359 100644 --- a/virtualization.h +++ b/virtualization.h @@ -10,9 +10,7 @@ #import /* exported from cgo */ -void startHandler(void *err, char *id); -void pauseHandler(void *err, char *id); -void resumeHandler(void *err, char *id); +void virtualMachineCompletionHandler(void *cgoHandler, void *errPtr); void connectionHandler(void *connection, void *err, char *id); void changeStateOnObserver(int state, char *id); bool shouldAcceptNewConnectionHandler(void *listener, void *connection, void *socketDevice); @@ -104,13 +102,15 @@ void *newVZGenericPlatformConfiguration(); /* VirtualMachine */ void *newVZVirtualMachineWithDispatchQueue(void *config, void *queue, const char *vmid); bool requestStopVirtualMachine(void *machine, void *queue, void **error); -void startWithCompletionHandler(void *machine, void *queue, const char *vmid); -void pauseWithCompletionHandler(void *machine, void *queue, const char *vmid); -void resumeWithCompletionHandler(void *machine, void *queue, const char *vmid); +void startWithCompletionHandler(void *machine, void *queue, void *completionHandler); +void pauseWithCompletionHandler(void *machine, void *queue, void *completionHandler); +void resumeWithCompletionHandler(void *machine, void *queue, void *completionHandler); +void stopWithCompletionHandler(void *machine, void *queue, void *completionHandler); bool vmCanStart(void *machine, void *queue); bool vmCanPause(void *machine, void *queue); bool vmCanResume(void *machine, void *queue); bool vmCanRequestStop(void *machine, void *queue); +bool vmCanStop(void *machine, void *queue); void *makeDispatchQueue(const char *label); @@ -124,4 +124,5 @@ typedef struct VZVirtioSocketConnectionFlat VZVirtioSocketConnectionFlat convertVZVirtioSocketConnection2Flat(void *connection); +void sharedApplication(); void startVirtualMachineWindow(void *machine, double width, double height); \ No newline at end of file diff --git a/virtualization.m b/virtualization.m index fa04677..9c8fc7f 100644 --- a/virtualization.m +++ b/virtualization.m @@ -824,45 +824,40 @@ bool requestStopVirtualMachine(void *machine, void *queue, void **error) return queue; } -typedef void (^handler_t)(NSError *); - -handler_t generateHandler(const char *vmid, void handler(void *, char *)) +void startWithCompletionHandler(void *machine, void *queue, void *completionHandler) { - handler_t ret; - @autoreleasepool { - NSString *str = [NSString stringWithUTF8String:vmid]; - ret = Block_copy(^(NSError *err){ - handler(err, copyCString(str)); - }); - } - return ret; + dispatch_sync((dispatch_queue_t)queue, ^{ + [(VZVirtualMachine *)machine startWithCompletionHandler:^(NSError *err) { + virtualMachineCompletionHandler(completionHandler, err); + }]; + }); } -void startWithCompletionHandler(void *machine, void *queue, const char *vmid) +void pauseWithCompletionHandler(void *machine, void *queue, void *completionHandler) { - handler_t handler = generateHandler(vmid, startHandler); dispatch_sync((dispatch_queue_t)queue, ^{ - [(VZVirtualMachine *)machine startWithCompletionHandler:handler]; + [(VZVirtualMachine *)machine pauseWithCompletionHandler:^(NSError *err) { + virtualMachineCompletionHandler(completionHandler, err); + }]; }); - Block_release(handler); } -void pauseWithCompletionHandler(void *machine, void *queue, const char *vmid) +void resumeWithCompletionHandler(void *machine, void *queue, void *completionHandler) { - handler_t handler = generateHandler(vmid, pauseHandler); dispatch_sync((dispatch_queue_t)queue, ^{ - [(VZVirtualMachine *)machine pauseWithCompletionHandler:handler]; + [(VZVirtualMachine *)machine resumeWithCompletionHandler:^(NSError *err) { + virtualMachineCompletionHandler(completionHandler, err); + }]; }); - Block_release(handler); } -void resumeWithCompletionHandler(void *machine, void *queue, const char *vmid) +void stopWithCompletionHandler(void *machine, void *queue, void *completionHandler) { - handler_t handler = generateHandler(vmid, pauseHandler); dispatch_sync((dispatch_queue_t)queue, ^{ - [(VZVirtualMachine *)machine resumeWithCompletionHandler:handler]; + [(VZVirtualMachine *)machine stopWithCompletionHandler:^(NSError *err) { + virtualMachineCompletionHandler(completionHandler, err); + }]; }); - Block_release(handler); } // TODO(codehex): use KVO @@ -901,8 +896,25 @@ bool vmCanRequestStop(void *machine, void *queue) }); return (bool)result; } + +bool vmCanStop(void *machine, void *queue) +{ + __block BOOL result; + dispatch_sync((dispatch_queue_t)queue, ^{ + result = ((VZVirtualMachine *)machine).canStop; + }); + return (bool)result; +} // --- TODO end +void sharedApplication() +{ + // Create a shared app instance. + // This will initialize the global variable + // 'NSApp' with the application instance. + [VZApplication sharedApplication]; +} + void startVirtualMachineWindow(void *machine, double width, double height) { @autoreleasepool { @@ -911,10 +923,6 @@ void startVirtualMachineWindow(void *machine, double width, double height) windowWidth:(CGFloat)width windowHeight:(CGFloat)height] autorelease]; - // Create a shared app instance. - // This will initialize the global variable - // 'NSApp' with the application instance. - [NSApplication sharedApplication]; NSApp.delegate = appDelegate; [NSApp run]; } diff --git a/virtualization_view.h b/virtualization_view.h index d5732b7..36e16c4 100644 --- a/virtualization_view.h +++ b/virtualization_view.h @@ -10,6 +10,16 @@ #import #import +@interface VZApplication : NSApplication +{ + bool shouldKeepRunning; +} + +- (void)run; +- (void)terminate:(id)sender; + +@end + @interface AboutViewController : NSViewController - (instancetype)init; @end diff --git a/virtualization_view.m b/virtualization_view.m index 4168e57..423f704 100644 --- a/virtualization_view.m +++ b/virtualization_view.m @@ -6,6 +6,32 @@ #import "virtualization_view.h" +@implementation VZApplication + +- (void)run +{ + [self finishLaunching]; + + shouldKeepRunning = YES; + do + { + NSEvent *event = [self + nextEventMatchingMask:NSEventMaskAny + untilDate:[NSDate distantFuture] + inMode:NSDefaultRunLoopMode + dequeue:YES]; + // NSLog(@"event: %@", event); + [self sendEvent:event]; + [self updateWindows]; + } while (shouldKeepRunning); +} + +- (void)terminate:(id)sender +{ + shouldKeepRunning = NO; +} +@end + @implementation AboutViewController {} - (instancetype)init @@ -151,7 +177,6 @@ - (instancetype)initWithVirtualMachine:(VZVirtualMachine *)virtualMachine /* IMPORTANT: delegate methods are called from VM's queue */ - (void)guestDidStopVirtualMachine:(VZVirtualMachine *)virtualMachine { - NSLog(@"VM %@ guest stopped", virtualMachine); [NSApp performSelectorOnMainThread:@selector(terminate:) withObject:self waitUntilDone:NO]; } @@ -171,9 +196,8 @@ - (void)applicationDidFinishLaunching:(NSNotification *)notification { [NSApp activateIgnoringOtherApps:YES]; } -- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender -{ - return YES; +- (void)windowWillClose:(NSNotification *)notification { + [NSApp performSelectorOnMainThread:@selector(terminate:) withObject:self waitUntilDone:NO]; } - (void)setupGraphicWindow @@ -185,10 +209,10 @@ - (void)setupGraphicWindow [window setOpaque:NO]; [window setContentView:_virtualMachineView]; - // [window setDelegate:self]; [window setTitleVisibility:NSWindowTitleHidden]; [window center]; + [window setDelegate:self]; [window makeKeyAndOrderFront:nil]; // This code to prevent crash when called applicationShouldTerminateAfterLastWindowClosed.