Skip to content

Commit 10b5980

Browse files
authored
Merge pull request #36 from Code-Hex/fix/back-to-go-when-event-loop
Implemented around stop API
2 parents 8182bcf + 51727eb commit 10b5980

File tree

6 files changed

+165
-112
lines changed

6 files changed

+165
-112
lines changed

example/macOS/main.go

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@ import (
66
"fmt"
77
"log"
88
"os"
9-
"os/signal"
109
"runtime"
11-
"syscall"
10+
"time"
1211

1312
"github.com/Code-Hex/vz/v2"
1413
)
@@ -48,9 +47,6 @@ func runVM(ctx context.Context) error {
4847
}
4948
vm := vz.NewVirtualMachine(config)
5049

51-
signalCh := make(chan os.Signal, 1)
52-
signal.Notify(signalCh, syscall.SIGTERM)
53-
5450
errCh := make(chan error, 1)
5551

5652
vm.Start(func(err error) {
@@ -59,28 +55,50 @@ func runVM(ctx context.Context) error {
5955
}
6056
})
6157

62-
vm.StartGraphicApplication(960, 600)
58+
go func() {
59+
for {
60+
select {
61+
case newState := <-vm.StateChangedNotify():
62+
if newState == vz.VirtualMachineStateRunning {
63+
log.Println("start VM is running")
64+
}
65+
if newState == vz.VirtualMachineStateStopped || newState == vz.VirtualMachineStateStopping {
66+
log.Println("stopped state")
67+
errCh <- nil
68+
return
69+
}
70+
case err := <-errCh:
71+
errCh <- fmt.Errorf("failed to start vm: %w", err)
72+
return
73+
}
74+
}
75+
}()
6376

64-
for {
65-
select {
66-
case <-signalCh:
77+
// cleanup is this function is useful when finished graphic application.
78+
cleanup := func() {
79+
for i := 1; vm.CanRequestStop(); i++ {
6780
result, err := vm.RequestStop()
68-
if err != nil {
69-
return err
70-
}
71-
log.Println("recieved signal", result)
72-
case newState := <-vm.StateChangedNotify():
73-
if newState == vz.VirtualMachineStateRunning {
74-
log.Println("start VM is running")
75-
}
76-
if newState == vz.VirtualMachineStateStopped {
77-
log.Println("stopped successfully")
78-
return nil
81+
log.Printf("sent stop request(%d): %t, %v", i, result, err)
82+
time.Sleep(time.Second * 3)
83+
if i > 3 {
84+
log.Println("call stop")
85+
vm.Stop(func(err error) {
86+
if err != nil {
87+
log.Println("stop with error", err)
88+
}
89+
})
7990
}
80-
case err := <-errCh:
81-
return fmt.Errorf("failed to start vm: %w", err)
8291
}
92+
log.Println("finished cleanup")
8393
}
94+
95+
runtime.LockOSThread()
96+
vm.StartGraphicApplication(960, 600)
97+
runtime.UnlockOSThread()
98+
99+
cleanup()
100+
101+
return <-errCh
84102
}
85103

86104
func computeCPUCount() uint {

virtualization.go

Lines changed: 44 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,17 @@ package vz
88
import "C"
99
import (
1010
"runtime"
11+
"runtime/cgo"
1112
"sync"
1213
"unsafe"
1314

1415
"github.com/rs/xid"
1516
)
1617

18+
func init() {
19+
C.sharedApplication()
20+
}
21+
1722
// VirtualMachineState represents execution state of the virtual machine.
1823
type VirtualMachineState int
1924

@@ -41,6 +46,10 @@ const (
4146
// VirtualMachineStateResuming The virtual machine is being resumed.
4247
// This is the intermediate state between VirtualMachineStatePaused and VirtualMachineStateRunning.
4348
VirtualMachineStateResuming
49+
50+
// VZVirtualMachineStateStopping The virtual machine is being stopped.
51+
// This is the intermediate state between VZVirtualMachineStateRunning and VZVirtualMachineStateStop.
52+
VirtualMachineStateStopping
4453
)
4554

4655
// VirtualMachine represents the entire state of a single virtual machine.
@@ -77,17 +86,9 @@ type (
7786

7887
mu sync.RWMutex
7988
}
80-
machineHandlers struct {
81-
start func(error)
82-
pause func(error)
83-
resume func(error)
84-
}
8589
)
8690

87-
var (
88-
handlers = map[string]*machineHandlers{}
89-
statuses = map[string]*machineStatus{}
90-
)
91+
var statuses = map[string]*machineStatus{}
9192

9293
// NewVirtualMachine creates a new VirtualMachine with VirtualMachineConfiguration.
9394
//
@@ -104,11 +105,6 @@ func NewVirtualMachine(config *VirtualMachineConfiguration) *VirtualMachine {
104105
state: VirtualMachineState(0),
105106
stateNotify: make(chan VirtualMachineState),
106107
}
107-
handlers[id] = &machineHandlers{
108-
start: func(error) {},
109-
pause: func(error) {},
110-
resume: func(error) {},
111-
}
112108
dispatchQueue := C.makeDispatchQueue(cs.CString())
113109
v := &VirtualMachine{
114110
id: id,
@@ -203,37 +199,21 @@ func (v *VirtualMachine) CanRequestStop() bool {
203199
return (bool)(C.vmCanRequestStop(v.Ptr(), v.dispatchQueue))
204200
}
205201

206-
//export startHandler
207-
func startHandler(errPtr unsafe.Pointer, cid *C.char) {
208-
id := (*char)(cid).String()
209-
// If returns nil in the cgo world, the nil will not be treated as nil in the Go world
210-
// so this is temporarily handled (Go 1.17)
211-
if err := newNSError(errPtr); err != nil {
212-
handlers[id].start(err)
213-
} else {
214-
handlers[id].start(nil)
215-
}
202+
// CanStop returns whether the machine is in a state that can be stopped.
203+
func (v *VirtualMachine) CanStop() bool {
204+
return (bool)(C.vmCanStop(v.Ptr(), v.dispatchQueue))
216205
}
217206

218-
//export pauseHandler
219-
func pauseHandler(errPtr unsafe.Pointer, cid *C.char) {
220-
id := (*char)(cid).String()
221-
// see: startHandler
222-
if err := newNSError(errPtr); err != nil {
223-
handlers[id].pause(err)
224-
} else {
225-
handlers[id].pause(nil)
226-
}
227-
}
207+
//export virtualMachineCompletionHandler
208+
func virtualMachineCompletionHandler(cgoHandlerPtr, errPtr unsafe.Pointer) {
209+
cgoHandler := *(*cgo.Handle)(cgoHandlerPtr)
210+
211+
handler := cgoHandler.Value().(func(error))
228212

229-
//export resumeHandler
230-
func resumeHandler(errPtr unsafe.Pointer, cid *C.char) {
231-
id := (*char)(cid).String()
232-
// see: startHandler
233213
if err := newNSError(errPtr); err != nil {
234-
handlers[id].resume(err)
214+
handler(err)
235215
} else {
236-
handlers[id].resume(nil)
216+
handler(nil)
237217
}
238218
}
239219

@@ -251,10 +231,9 @@ func makeHandler(fn func(error)) (func(error), chan struct{}) {
251231
// The error parameter passed to the block is null if the start was successful.
252232
func (v *VirtualMachine) Start(fn func(error)) {
253233
h, done := makeHandler(fn)
254-
handlers[v.id].start = h
255-
cid := charWithGoString(v.id)
256-
defer cid.Free()
257-
C.startWithCompletionHandler(v.Ptr(), v.dispatchQueue, cid.CString())
234+
handler := cgo.NewHandle(h)
235+
defer handler.Delete()
236+
C.startWithCompletionHandler(v.Ptr(), v.dispatchQueue, unsafe.Pointer(&handler))
258237
<-done
259238
}
260239

@@ -264,10 +243,9 @@ func (v *VirtualMachine) Start(fn func(error)) {
264243
// The error parameter passed to the block is null if the start was successful.
265244
func (v *VirtualMachine) Pause(fn func(error)) {
266245
h, done := makeHandler(fn)
267-
handlers[v.id].pause = h
268-
cid := charWithGoString(v.id)
269-
defer cid.Free()
270-
C.pauseWithCompletionHandler(v.Ptr(), v.dispatchQueue, cid.CString())
246+
handler := cgo.NewHandle(h)
247+
defer handler.Delete()
248+
C.pauseWithCompletionHandler(v.Ptr(), v.dispatchQueue, unsafe.Pointer(&handler))
271249
<-done
272250
}
273251

@@ -277,10 +255,9 @@ func (v *VirtualMachine) Pause(fn func(error)) {
277255
// The error parameter passed to the block is null if the resumption was successful.
278256
func (v *VirtualMachine) Resume(fn func(error)) {
279257
h, done := makeHandler(fn)
280-
handlers[v.id].resume = h
281-
cid := charWithGoString(v.id)
282-
defer cid.Free()
283-
C.resumeWithCompletionHandler(v.Ptr(), v.dispatchQueue, cid.CString())
258+
handler := cgo.NewHandle(h)
259+
defer handler.Delete()
260+
C.resumeWithCompletionHandler(v.Ptr(), v.dispatchQueue, unsafe.Pointer(&handler))
284261
<-done
285262
}
286263

@@ -298,6 +275,21 @@ func (v *VirtualMachine) RequestStop() (bool, error) {
298275
return ret, nil
299276
}
300277

278+
// Stop stops a VM that’s in either a running or paused state.
279+
//
280+
// The completion handler returns an error object when the VM fails to stop,
281+
// or nil if the stop was successful.
282+
//
283+
// Warning: This is a destructive operation. It stops the VM without
284+
// giving the guest a chance to stop cleanly.
285+
func (v *VirtualMachine) Stop(fn func(error)) {
286+
h, done := makeHandler(fn)
287+
handler := cgo.NewHandle(h)
288+
defer handler.Delete()
289+
C.stopWithCompletionHandler(v.Ptr(), v.dispatchQueue, unsafe.Pointer(&handler))
290+
<-done
291+
}
292+
301293
// StartGraphicApplication starts an application to display graphics of the VM.
302294
//
303295
// You must to call runtime.LockOSThread before calling this method.

virtualization.h

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@
1010
#import <Virtualization/Virtualization.h>
1111

1212
/* exported from cgo */
13-
void startHandler(void *err, char *id);
14-
void pauseHandler(void *err, char *id);
15-
void resumeHandler(void *err, char *id);
13+
void virtualMachineCompletionHandler(void *cgoHandler, void *errPtr);
1614
void connectionHandler(void *connection, void *err, char *id);
1715
void changeStateOnObserver(int state, char *id);
1816
bool shouldAcceptNewConnectionHandler(void *listener, void *connection, void *socketDevice);
@@ -104,13 +102,15 @@ void *newVZGenericPlatformConfiguration();
104102
/* VirtualMachine */
105103
void *newVZVirtualMachineWithDispatchQueue(void *config, void *queue, const char *vmid);
106104
bool requestStopVirtualMachine(void *machine, void *queue, void **error);
107-
void startWithCompletionHandler(void *machine, void *queue, const char *vmid);
108-
void pauseWithCompletionHandler(void *machine, void *queue, const char *vmid);
109-
void resumeWithCompletionHandler(void *machine, void *queue, const char *vmid);
105+
void startWithCompletionHandler(void *machine, void *queue, void *completionHandler);
106+
void pauseWithCompletionHandler(void *machine, void *queue, void *completionHandler);
107+
void resumeWithCompletionHandler(void *machine, void *queue, void *completionHandler);
108+
void stopWithCompletionHandler(void *machine, void *queue, void *completionHandler);
110109
bool vmCanStart(void *machine, void *queue);
111110
bool vmCanPause(void *machine, void *queue);
112111
bool vmCanResume(void *machine, void *queue);
113112
bool vmCanRequestStop(void *machine, void *queue);
113+
bool vmCanStop(void *machine, void *queue);
114114

115115
void *makeDispatchQueue(const char *label);
116116

@@ -124,4 +124,5 @@ typedef struct VZVirtioSocketConnectionFlat
124124

125125
VZVirtioSocketConnectionFlat convertVZVirtioSocketConnection2Flat(void *connection);
126126

127+
void sharedApplication();
127128
void startVirtualMachineWindow(void *machine, double width, double height);

virtualization.m

Lines changed: 35 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -824,45 +824,40 @@ bool requestStopVirtualMachine(void *machine, void *queue, void **error)
824824
return queue;
825825
}
826826

827-
typedef void (^handler_t)(NSError *);
828-
829-
handler_t generateHandler(const char *vmid, void handler(void *, char *))
827+
void startWithCompletionHandler(void *machine, void *queue, void *completionHandler)
830828
{
831-
handler_t ret;
832-
@autoreleasepool {
833-
NSString *str = [NSString stringWithUTF8String:vmid];
834-
ret = Block_copy(^(NSError *err){
835-
handler(err, copyCString(str));
836-
});
837-
}
838-
return ret;
829+
dispatch_sync((dispatch_queue_t)queue, ^{
830+
[(VZVirtualMachine *)machine startWithCompletionHandler:^(NSError *err) {
831+
virtualMachineCompletionHandler(completionHandler, err);
832+
}];
833+
});
839834
}
840835

841-
void startWithCompletionHandler(void *machine, void *queue, const char *vmid)
836+
void pauseWithCompletionHandler(void *machine, void *queue, void *completionHandler)
842837
{
843-
handler_t handler = generateHandler(vmid, startHandler);
844838
dispatch_sync((dispatch_queue_t)queue, ^{
845-
[(VZVirtualMachine *)machine startWithCompletionHandler:handler];
839+
[(VZVirtualMachine *)machine pauseWithCompletionHandler:^(NSError *err) {
840+
virtualMachineCompletionHandler(completionHandler, err);
841+
}];
846842
});
847-
Block_release(handler);
848843
}
849844

850-
void pauseWithCompletionHandler(void *machine, void *queue, const char *vmid)
845+
void resumeWithCompletionHandler(void *machine, void *queue, void *completionHandler)
851846
{
852-
handler_t handler = generateHandler(vmid, pauseHandler);
853847
dispatch_sync((dispatch_queue_t)queue, ^{
854-
[(VZVirtualMachine *)machine pauseWithCompletionHandler:handler];
848+
[(VZVirtualMachine *)machine resumeWithCompletionHandler:^(NSError *err) {
849+
virtualMachineCompletionHandler(completionHandler, err);
850+
}];
855851
});
856-
Block_release(handler);
857852
}
858853

859-
void resumeWithCompletionHandler(void *machine, void *queue, const char *vmid)
854+
void stopWithCompletionHandler(void *machine, void *queue, void *completionHandler)
860855
{
861-
handler_t handler = generateHandler(vmid, pauseHandler);
862856
dispatch_sync((dispatch_queue_t)queue, ^{
863-
[(VZVirtualMachine *)machine resumeWithCompletionHandler:handler];
857+
[(VZVirtualMachine *)machine stopWithCompletionHandler:^(NSError *err) {
858+
virtualMachineCompletionHandler(completionHandler, err);
859+
}];
864860
});
865-
Block_release(handler);
866861
}
867862

868863
// TODO(codehex): use KVO
@@ -901,8 +896,25 @@ bool vmCanRequestStop(void *machine, void *queue)
901896
});
902897
return (bool)result;
903898
}
899+
900+
bool vmCanStop(void *machine, void *queue)
901+
{
902+
__block BOOL result;
903+
dispatch_sync((dispatch_queue_t)queue, ^{
904+
result = ((VZVirtualMachine *)machine).canStop;
905+
});
906+
return (bool)result;
907+
}
904908
// --- TODO end
905909

910+
void sharedApplication()
911+
{
912+
// Create a shared app instance.
913+
// This will initialize the global variable
914+
// 'NSApp' with the application instance.
915+
[VZApplication sharedApplication];
916+
}
917+
906918
void startVirtualMachineWindow(void *machine, double width, double height)
907919
{
908920
@autoreleasepool {
@@ -911,10 +923,6 @@ void startVirtualMachineWindow(void *machine, double width, double height)
911923
windowWidth:(CGFloat)width
912924
windowHeight:(CGFloat)height] autorelease];
913925

914-
// Create a shared app instance.
915-
// This will initialize the global variable
916-
// 'NSApp' with the application instance.
917-
[NSApplication sharedApplication];
918926
NSApp.delegate = appDelegate;
919927
[NSApp run];
920928
}

0 commit comments

Comments
 (0)