Skip to content

Commit 73695c3

Browse files
authored
Merge pull request #122 from Code-Hex/fix/119
fixed #119
2 parents 601437e + 3a1111d commit 73695c3

8 files changed

+213
-70
lines changed

internal/objc/objc.go

+19-3
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,12 @@ void insertNSMutableDictionary(void *dict, char *key, void *val)
2828
2929
void releaseNSObject(void* o)
3030
{
31-
@autoreleasepool {
32-
[(NSObject*)o release];
33-
}
31+
[(NSObject*)o release];
32+
}
33+
34+
void retainNSObject(void* o)
35+
{
36+
[(NSObject*)o retain];
3437
}
3538
3639
static inline void releaseDispatch(void *queue)
@@ -77,11 +80,18 @@ func NewPointer(p unsafe.Pointer) *Pointer {
7780
}
7881

7982
// release releases allocated resources in objective-c world.
83+
// decrements reference count.
8084
func (p *Pointer) release() {
8185
C.releaseNSObject(p._ptr)
8286
runtime.KeepAlive(p)
8387
}
8488

89+
// retain increments reference count in objective-c world.
90+
func (p *Pointer) retain() {
91+
C.retainNSObject(p._ptr)
92+
runtime.KeepAlive(p)
93+
}
94+
8595
// Ptr returns raw pointer.
8696
func (o *Pointer) ptr() unsafe.Pointer {
8797
if o == nil {
@@ -94,13 +104,19 @@ func (o *Pointer) ptr() unsafe.Pointer {
94104
type NSObject interface {
95105
ptr() unsafe.Pointer
96106
release()
107+
retain()
97108
}
98109

99110
// Release releases allocated resources in objective-c world.
100111
func Release(o NSObject) {
101112
o.release()
102113
}
103114

115+
// Retain increments reference count in objective-c world.
116+
func Retain(o NSObject) {
117+
o.retain()
118+
}
119+
104120
// Ptr returns unsafe.Pointer of the NSObject
105121
func Ptr(o NSObject) unsafe.Pointer {
106122
return o.ptr()

internal/testhelper/ssh.go

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package testhelper
2+
3+
import (
4+
"io"
5+
"net"
6+
"testing"
7+
"time"
8+
9+
"golang.org/x/crypto/ssh"
10+
)
11+
12+
func NewSshConfig(username, password string) *ssh.ClientConfig {
13+
return &ssh.ClientConfig{
14+
User: username,
15+
Auth: []ssh.AuthMethod{ssh.Password(password)},
16+
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
17+
}
18+
}
19+
20+
func NewSshClient(conn net.Conn, addr string, config *ssh.ClientConfig) (*ssh.Client, error) {
21+
c, chans, reqs, err := ssh.NewClientConn(conn, addr, config)
22+
if err != nil {
23+
return nil, err
24+
}
25+
return ssh.NewClient(c, chans, reqs), nil
26+
}
27+
28+
func SetKeepAlive(t *testing.T, session *ssh.Session) {
29+
t.Helper()
30+
go func() {
31+
for range time.Tick(5 * time.Second) {
32+
_, err := session.SendRequest("[email protected]", true, nil)
33+
if err != nil && err != io.EOF {
34+
t.Logf("failed to send keep-alive request: %v", err)
35+
return
36+
}
37+
}
38+
}()
39+
}

issues_test.go

+81
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@ package vz
22

33
import (
44
"errors"
5+
"fmt"
56
"net"
67
"os"
78
"path/filepath"
89
"strings"
910
"testing"
11+
12+
"github.com/Code-Hex/vz/v3/internal/objc"
1013
)
1114

1215
func newTestConfig(t *testing.T) *VirtualMachineConfiguration {
@@ -256,3 +259,81 @@ func TestIssue98(t *testing.T) {
256259
t.Fatal(err)
257260
}
258261
}
262+
263+
func TestIssue119(t *testing.T) {
264+
vmlinuz := "./testdata/Image"
265+
initramfs := "./testdata/initramfs.cpio.gz"
266+
bootLoader, err := NewLinuxBootLoader(
267+
vmlinuz,
268+
WithCommandLine("console=hvc0"),
269+
WithInitrd(initramfs),
270+
)
271+
if err != nil {
272+
t.Fatal(err)
273+
}
274+
275+
config, err := setupIssue119Config(bootLoader)
276+
if err != nil {
277+
t.Fatal(err)
278+
}
279+
280+
vm, err := NewVirtualMachine(config)
281+
if err != nil {
282+
t.Fatal(err)
283+
}
284+
285+
if canStart := vm.CanStart(); !canStart {
286+
t.Fatal("want CanStart is true")
287+
}
288+
289+
if err := vm.Start(); err != nil {
290+
t.Fatal(err)
291+
}
292+
293+
if got := vm.State(); VirtualMachineStateRunning != got {
294+
t.Fatalf("want state %v but got %v", VirtualMachineStateRunning, got)
295+
}
296+
297+
// Simulates Go's VirtualMachine struct has been destructured but
298+
// Objective-C VZVirtualMachine object has not been destructured.
299+
objc.Retain(vm.pointer)
300+
vm.finalize()
301+
302+
// sshSession.Run("poweroff")
303+
vm.Pause()
304+
}
305+
306+
func setupIssue119Config(bootLoader *LinuxBootLoader) (*VirtualMachineConfiguration, error) {
307+
config, err := NewVirtualMachineConfiguration(
308+
bootLoader,
309+
1,
310+
512*1024*1024,
311+
)
312+
if err != nil {
313+
return nil, fmt.Errorf("failed to create a new virtual machine config: %w", err)
314+
}
315+
316+
// entropy device
317+
entropyConfig, err := NewVirtioEntropyDeviceConfiguration()
318+
if err != nil {
319+
return nil, fmt.Errorf("failed to create entropy device config: %w", err)
320+
}
321+
config.SetEntropyDevicesVirtualMachineConfiguration([]*VirtioEntropyDeviceConfiguration{
322+
entropyConfig,
323+
})
324+
325+
// memory balloon device
326+
memoryBalloonDevice, err := NewVirtioTraditionalMemoryBalloonDeviceConfiguration()
327+
if err != nil {
328+
return nil, fmt.Errorf("failed to create memory balloon device config: %w", err)
329+
}
330+
config.SetMemoryBalloonDevicesVirtualMachineConfiguration([]MemoryBalloonDeviceConfiguration{
331+
memoryBalloonDevice,
332+
})
333+
334+
if _, err := config.Validate(); err != nil {
335+
return nil, err
336+
}
337+
338+
return config, nil
339+
}

virtualization.go

+27-15
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,13 @@ type VirtualMachine struct {
7474

7575
*pointer
7676
dispatchQueue unsafe.Pointer
77-
status cgo.Handle
77+
stateHandle cgo.Handle
7878

79-
mu sync.Mutex
79+
mu *sync.Mutex
80+
finalizeOnce sync.Once
8081
}
8182

82-
type machineStatus struct {
83+
type machineState struct {
8384
state VirtualMachineState
8485
stateNotify chan VirtualMachineState
8586

@@ -102,7 +103,7 @@ func NewVirtualMachine(config *VirtualMachineConfiguration) (*VirtualMachine, er
102103
cs := (*char)(objc.GetUUID())
103104
dispatchQueue := C.makeDispatchQueue(cs.CString())
104105

105-
status := cgo.NewHandle(&machineStatus{
106+
stateHandle := cgo.NewHandle(&machineState{
106107
state: VirtualMachineState(0),
107108
stateNotify: make(chan VirtualMachineState),
108109
})
@@ -113,21 +114,26 @@ func NewVirtualMachine(config *VirtualMachineConfiguration) (*VirtualMachine, er
113114
C.newVZVirtualMachineWithDispatchQueue(
114115
objc.Ptr(config),
115116
dispatchQueue,
116-
unsafe.Pointer(&status),
117+
unsafe.Pointer(&stateHandle),
117118
),
118119
),
119120
dispatchQueue: dispatchQueue,
120-
status: status,
121+
stateHandle: stateHandle,
121122
}
122123

123124
objc.SetFinalizer(v, func(self *VirtualMachine) {
124-
self.status.Delete()
125-
objc.ReleaseDispatch(self.dispatchQueue)
126-
objc.Release(self)
125+
self.finalize()
127126
})
128127
return v, nil
129128
}
130129

130+
func (v *VirtualMachine) finalize() {
131+
v.finalizeOnce.Do(func() {
132+
objc.ReleaseDispatch(v.dispatchQueue)
133+
objc.Release(v)
134+
})
135+
}
136+
131137
// SocketDevices return the list of socket devices configured on this virtual machine.
132138
// Return an empty array if no socket device is configured.
133139
//
@@ -147,24 +153,30 @@ func (v *VirtualMachine) SocketDevices() []*VirtioSocketDevice {
147153
}
148154

149155
//export changeStateOnObserver
150-
func changeStateOnObserver(state C.int, cgoHandlerPtr unsafe.Pointer) {
151-
status := *(*cgo.Handle)(cgoHandlerPtr)
156+
func changeStateOnObserver(newStateRaw C.int, cgoHandlerPtr unsafe.Pointer) {
157+
stateHandler := *(*cgo.Handle)(cgoHandlerPtr)
152158
// I expected it will not cause panic.
153159
// if caused panic, that's unexpected behavior.
154-
v, _ := status.Value().(*machineStatus)
160+
v, _ := stateHandler.Value().(*machineState)
155161
v.mu.Lock()
156-
newState := VirtualMachineState(state)
162+
newState := VirtualMachineState(newStateRaw)
157163
v.state = newState
158164
// for non-blocking
159165
go func() { v.stateNotify <- newState }()
160166
v.mu.Unlock()
161167
}
162168

169+
//export deleteStateHandler
170+
func deleteStateHandler(cgoHandlerPtr unsafe.Pointer) {
171+
stateHandler := *(*cgo.Handle)(cgoHandlerPtr)
172+
stateHandler.Delete()
173+
}
174+
163175
// State represents execution state of the virtual machine.
164176
func (v *VirtualMachine) State() VirtualMachineState {
165177
// I expected it will not cause panic.
166178
// if caused panic, that's unexpected behavior.
167-
val, _ := v.status.Value().(*machineStatus)
179+
val, _ := v.stateHandle.Value().(*machineState)
168180
val.mu.RLock()
169181
defer val.mu.RUnlock()
170182
return val.state
@@ -174,7 +186,7 @@ func (v *VirtualMachine) State() VirtualMachineState {
174186
func (v *VirtualMachine) StateChangedNotify() <-chan VirtualMachineState {
175187
// I expected it will not cause panic.
176188
// if caused panic, that's unexpected behavior.
177-
val, _ := v.status.Value().(*machineStatus)
189+
val, _ := v.stateHandle.Value().(*machineState)
178190
val.mu.RLock()
179191
defer val.mu.RUnlock()
180192
return val.stateNotify

virtualization_11.h

+8
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,19 @@
1313
void connectionHandler(void *connection, void *err, void *cgoHandlerPtr);
1414
void changeStateOnObserver(int state, void *cgoHandler);
1515
bool shouldAcceptNewConnectionHandler(void *cgoHandler, void *connection, void *socketDevice);
16+
void deleteStateHandler(void *cgoHandlerPtr);
1617

1718
@interface Observer : NSObject
1819
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
1920
@end
2021

22+
@interface ObservableVZVirtualMachine : VZVirtualMachine
23+
- (instancetype)initWithConfiguration:(VZVirtualMachineConfiguration *)configuration
24+
queue:(dispatch_queue_t)queue
25+
statusHandler:(void *)statusHandler;
26+
- (void)dealloc;
27+
@end
28+
2129
/* VZVirtioSocketListener */
2230
@interface VZVirtioSocketListenerDelegateImpl : NSObject <VZVirtioSocketListenerDelegate>
2331
- (instancetype)initWithHandler:(void *)cgoHandler;

virtualization_11.m

+29-17
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,6 @@
66

77
#import "virtualization_11.h"
88

9-
char *copyCString(NSString *nss)
10-
{
11-
const char *cc = [nss UTF8String];
12-
char *c = calloc([nss length] + 1, 1);
13-
strncpy(c, cc, [nss length]);
14-
return c;
15-
}
16-
179
@implementation Observer
1810
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
1911
{
@@ -34,6 +26,32 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N
3426
}
3527
@end
3628

29+
@implementation ObservableVZVirtualMachine {
30+
Observer *_observer;
31+
void *_stateHandler;
32+
};
33+
- (instancetype)initWithConfiguration:(VZVirtualMachineConfiguration *)configuration
34+
queue:(dispatch_queue_t)queue
35+
statusHandler:(void *)statusHandler
36+
{
37+
self = [super initWithConfiguration:configuration queue:queue];
38+
_observer = [[Observer alloc] init];
39+
[self addObserver:_observer
40+
forKeyPath:@"state"
41+
options:NSKeyValueObservingOptionNew
42+
context:statusHandler];
43+
return self;
44+
}
45+
46+
- (void)dealloc
47+
{
48+
[self removeObserver:_observer forKeyPath:@"state"];
49+
deleteStateHandler(_stateHandler);
50+
[_observer release];
51+
[super dealloc];
52+
}
53+
@end
54+
3755
@implementation VZVirtioSocketListenerDelegateImpl {
3856
void *_cgoHandler;
3957
}
@@ -671,16 +689,10 @@ VZVirtioSocketConnectionFlat convertVZVirtioSocketConnection2Flat(void *connecti
671689
void *newVZVirtualMachineWithDispatchQueue(void *config, void *queue, void *statusHandler)
672690
{
673691
if (@available(macOS 11, *)) {
674-
VZVirtualMachine *vm = [[VZVirtualMachine alloc]
692+
ObservableVZVirtualMachine *vm = [[ObservableVZVirtualMachine alloc]
675693
initWithConfiguration:(VZVirtualMachineConfiguration *)config
676-
queue:(dispatch_queue_t)queue];
677-
@autoreleasepool {
678-
Observer *o = [[Observer alloc] init];
679-
[vm addObserver:o
680-
forKeyPath:@"state"
681-
options:NSKeyValueObservingOptionNew
682-
context:statusHandler];
683-
}
694+
queue:(dispatch_queue_t)queue
695+
statusHandler:statusHandler];
684696
return vm;
685697
}
686698

0 commit comments

Comments
 (0)