Skip to content

Commit

Permalink
Merge pull request #36 from Code-Hex/fix/back-to-go-when-event-loop
Browse files Browse the repository at this point in the history
Implemented around stop API
  • Loading branch information
Code-Hex authored Aug 29, 2022
2 parents 8182bcf + 51727eb commit 10b5980
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 112 deletions.
62 changes: 40 additions & 22 deletions example/macOS/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@ import (
"fmt"
"log"
"os"
"os/signal"
"runtime"
"syscall"
"time"

"github.com/Code-Hex/vz/v2"
)
Expand Down Expand Up @@ -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) {
Expand All @@ -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 {
Expand Down
96 changes: 44 additions & 52 deletions virtualization.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
//
Expand All @@ -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,
Expand Down Expand Up @@ -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)
}
}

Expand All @@ -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
}

Expand All @@ -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
}

Expand All @@ -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
}

Expand All @@ -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.
Expand Down
13 changes: 7 additions & 6 deletions virtualization.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@
#import <Virtualization/Virtualization.h>

/* 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);
Expand Down Expand Up @@ -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);

Expand All @@ -124,4 +124,5 @@ typedef struct VZVirtioSocketConnectionFlat

VZVirtioSocketConnectionFlat convertVZVirtioSocketConnection2Flat(void *connection);

void sharedApplication();
void startVirtualMachineWindow(void *machine, double width, double height);
62 changes: 35 additions & 27 deletions virtualization.m
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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];
}
Expand Down
Loading

0 comments on commit 10b5980

Please sign in to comment.