Skip to content

Commit f70b693

Browse files
committed
implemented Go API for attachmentWasDisconnectedWithError
1 parent 9f38783 commit f70b693

8 files changed

+233
-64
lines changed

configuration.go

+3-9
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ type VirtualMachineConfiguration struct {
3838
memorySize uint64
3939
*pointer
4040

41+
networkDeviceConfiguration []*VirtioNetworkDeviceConfiguration
4142
storageDeviceConfiguration []StorageDeviceConfiguration
4243
}
4344

@@ -116,20 +117,13 @@ func (v *VirtualMachineConfiguration) SetNetworkDevicesVirtualMachineConfigurati
116117
}
117118
array := objc.ConvertToNSMutableArray(ptrs)
118119
C.setNetworkDevicesVZVirtualMachineConfiguration(objc.Ptr(v), objc.Ptr(array))
120+
v.networkDeviceConfiguration = cs
119121
}
120122

121123
// NetworkDevices return the list of network device configuration set in this virtual machine configuration.
122124
// Return an empty array if no network device configuration is set.
123125
func (v *VirtualMachineConfiguration) NetworkDevices() []*VirtioNetworkDeviceConfiguration {
124-
nsArray := objc.NewNSArray(
125-
C.networkDevicesVZVirtualMachineConfiguration(objc.Ptr(v)),
126-
)
127-
ptrs := nsArray.ToPointerSlice()
128-
networkDevices := make([]*VirtioNetworkDeviceConfiguration, len(ptrs))
129-
for i, ptr := range ptrs {
130-
networkDevices[i] = newVirtioNetworkDeviceConfiguration(ptr)
131-
}
132-
return networkDevices
126+
return v.networkDeviceConfiguration
133127
}
134128

135129
// SetSerialPortsVirtualMachineConfiguration sets list of serial ports. Empty by default.

internal/sliceutil/sliceutil.go

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package sliceutil
2+
3+
// FindValueByIndex returns the value of the index in s,
4+
// or -1 if not present.
5+
func FindValueByIndex[S ~[]E, E any](s S, idx int) (v E) {
6+
for i := range s {
7+
if i == idx {
8+
return s[i]
9+
}
10+
}
11+
return v
12+
}

internal/sliceutil/sliceutil_test.go

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package sliceutil_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/Code-Hex/vz/v3/internal/sliceutil"
7+
)
8+
9+
func TestFindValueByIndex(t *testing.T) {
10+
tests := []struct {
11+
name string
12+
slice []int
13+
index int
14+
expected int
15+
}{
16+
{
17+
name: "Index within range",
18+
slice: []int{1, 2, 3, 4, 5},
19+
index: 2,
20+
expected: 3,
21+
},
22+
{
23+
name: "Index out of range",
24+
slice: []int{1, 2, 3, 4, 5},
25+
index: 10,
26+
expected: 0, // default value of int
27+
},
28+
{
29+
name: "Negative index",
30+
slice: []int{1, 2, 3, 4, 5},
31+
index: -1,
32+
expected: 0, // default value of int
33+
},
34+
{
35+
name: "Empty slice",
36+
slice: []int{},
37+
index: 0,
38+
expected: 0, // default value of int
39+
},
40+
}
41+
42+
for _, tt := range tests {
43+
t.Run(tt.name, func(t *testing.T) {
44+
result := sliceutil.FindValueByIndex(tt.slice, tt.index)
45+
if result != tt.expected {
46+
t.Errorf("FindValueByIndex(%v, %d) = %v; want %v", tt.slice, tt.index, result, tt.expected)
47+
}
48+
})
49+
}
50+
}

network.go

+26-9
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import (
1212
"net"
1313
"os"
1414
"syscall"
15-
"unsafe"
1615

1716
"github.com/Code-Hex/vz/v3/internal/objc"
1817
)
@@ -91,6 +90,10 @@ type NATNetworkDeviceAttachment struct {
9190
*baseNetworkDeviceAttachment
9291
}
9392

93+
func (*NATNetworkDeviceAttachment) String() string {
94+
return "NATNetworkDeviceAttachment"
95+
}
96+
9497
var _ NetworkDeviceAttachment = (*NATNetworkDeviceAttachment)(nil)
9598

9699
// NewNATNetworkDeviceAttachment creates a new NATNetworkDeviceAttachment.
@@ -127,6 +130,10 @@ type BridgedNetworkDeviceAttachment struct {
127130
*baseNetworkDeviceAttachment
128131
}
129132

133+
func (*BridgedNetworkDeviceAttachment) String() string {
134+
return "BridgedNetworkDeviceAttachment"
135+
}
136+
130137
var _ NetworkDeviceAttachment = (*BridgedNetworkDeviceAttachment)(nil)
131138

132139
// NewBridgedNetworkDeviceAttachment creates a new BridgedNetworkDeviceAttachment with networkInterface.
@@ -164,6 +171,10 @@ type FileHandleNetworkDeviceAttachment struct {
164171
mtu int
165172
}
166173

174+
func (*FileHandleNetworkDeviceAttachment) String() string {
175+
return "FileHandleNetworkDeviceAttachment"
176+
}
177+
167178
var _ NetworkDeviceAttachment = (*FileHandleNetworkDeviceAttachment)(nil)
168179

169180
// NewFileHandleNetworkDeviceAttachment initialize the attachment with a file handle.
@@ -253,7 +264,7 @@ func (f *FileHandleNetworkDeviceAttachment) MaximumTransmissionUnit() int {
253264
// see: https://developer.apple.com/documentation/virtualization/vznetworkdeviceattachment?language=objc
254265
type NetworkDeviceAttachment interface {
255266
objc.NSObject
256-
267+
fmt.Stringer
257268
networkDeviceAttachment()
258269
}
259270

@@ -271,6 +282,8 @@ func (*baseNetworkDeviceAttachment) networkDeviceAttachment() {}
271282
// see: https://developer.apple.com/documentation/virtualization/vzvirtionetworkdeviceconfiguration?language=objc
272283
type VirtioNetworkDeviceConfiguration struct {
273284
*pointer
285+
286+
attachment NetworkDeviceAttachment
274287
}
275288

276289
// NewVirtioNetworkDeviceConfiguration creates a new VirtioNetworkDeviceConfiguration with NetworkDeviceAttachment.
@@ -282,27 +295,31 @@ func NewVirtioNetworkDeviceConfiguration(attachment NetworkDeviceAttachment) (*V
282295
return nil, err
283296
}
284297

285-
config := newVirtioNetworkDeviceConfiguration(
286-
C.newVZVirtioNetworkDeviceConfiguration(
287-
objc.Ptr(attachment),
288-
),
289-
)
298+
config := newVirtioNetworkDeviceConfiguration(attachment)
290299
objc.SetFinalizer(config, func(self *VirtioNetworkDeviceConfiguration) {
291300
objc.Release(self)
292301
})
293302
return config, nil
294303
}
295304

296-
func newVirtioNetworkDeviceConfiguration(ptr unsafe.Pointer) *VirtioNetworkDeviceConfiguration {
305+
func newVirtioNetworkDeviceConfiguration(attachment NetworkDeviceAttachment) *VirtioNetworkDeviceConfiguration {
306+
ptr := C.newVZVirtioNetworkDeviceConfiguration(
307+
objc.Ptr(attachment),
308+
)
297309
return &VirtioNetworkDeviceConfiguration{
298-
pointer: objc.NewPointer(ptr),
310+
pointer: objc.NewPointer(ptr),
311+
attachment: attachment,
299312
}
300313
}
301314

302315
func (v *VirtioNetworkDeviceConfiguration) SetMACAddress(macAddress *MACAddress) {
303316
C.setNetworkDevicesVZMACAddress(objc.Ptr(v), objc.Ptr(macAddress))
304317
}
305318

319+
func (v *VirtioNetworkDeviceConfiguration) Attachment() NetworkDeviceAttachment {
320+
return v.attachment
321+
}
322+
306323
// MACAddress represents a media access control address (MAC address), the 48-bit ethernet address.
307324
// see: https://developer.apple.com/documentation/virtualization/vzmacaddress?language=objc
308325
type MACAddress struct {

virtualization.go

+99-4
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ package vz
99
*/
1010
import "C"
1111
import (
12+
"fmt"
1213
"runtime/cgo"
1314
"sync"
1415
"unsafe"
1516

1617
infinity "github.com/Code-Hex/go-infinity-channel"
1718
"github.com/Code-Hex/vz/v3/internal/objc"
19+
"github.com/Code-Hex/vz/v3/internal/sliceutil"
1820
)
1921

2022
// VirtualMachineState represents execution state of the virtual machine.
@@ -91,9 +93,15 @@ type VirtualMachine struct {
9193
dispatchQueue unsafe.Pointer
9294
machineState *machineState
9395

96+
disconnectedIn *infinity.Channel[*disconnected]
97+
disconnectedOut *infinity.Channel[*DisconnectedError]
98+
watchDisconnectedOnce sync.Once
99+
94100
finalizeOnce sync.Once
95101

96102
config *VirtualMachineConfiguration
103+
104+
mu sync.RWMutex
97105
}
98106

99107
type machineState struct {
@@ -123,20 +131,27 @@ func NewVirtualMachine(config *VirtualMachineConfiguration) (*VirtualMachine, er
123131
state: VirtualMachineState(0),
124132
stateNotify: infinity.NewChannel[VirtualMachineState](),
125133
}
126-
127134
stateHandle := cgo.NewHandle(machineState)
135+
136+
disconnectedIn := infinity.NewChannel[*disconnected]()
137+
disconnectedOut := infinity.NewChannel[*DisconnectedError]()
138+
disconnectedHandle := cgo.NewHandle(disconnectedIn)
139+
128140
v := &VirtualMachine{
129141
id: cs.String(),
130142
pointer: objc.NewPointer(
131143
C.newVZVirtualMachineWithDispatchQueue(
132144
objc.Ptr(config),
133145
dispatchQueue,
134146
C.uintptr_t(stateHandle),
147+
C.uintptr_t(disconnectedHandle),
135148
),
136149
),
137-
dispatchQueue: dispatchQueue,
138-
machineState: machineState,
139-
config: config,
150+
dispatchQueue: dispatchQueue,
151+
machineState: machineState,
152+
disconnectedIn: disconnectedIn,
153+
disconnectedOut: disconnectedOut,
154+
config: config,
140155
}
141156

142157
objc.SetFinalizer(v, func(self *VirtualMachine) {
@@ -357,3 +372,83 @@ func (v *VirtualMachine) StartGraphicApplication(width, height float64) error {
357372
C.startVirtualMachineWindow(objc.Ptr(v), C.double(width), C.double(height))
358373
return nil
359374
}
375+
376+
// DisconnectedError represents an error that occurs when a VM’s network attachment is disconnected
377+
// due to a network-related issue. This error is triggered by the framework when such a disconnection happens.
378+
type DisconnectedError struct {
379+
// Err is the underlying error that caused the disconnection, triggered by the framework.
380+
// This error provides information on why the network attachment was disconnected.
381+
Err error
382+
// The network device configuration associated with the disconnection event.
383+
// This configuration helps identify which network device experienced the disconnection.
384+
// If Config is nil, the specific configuration details are unavailable.
385+
Config *VirtioNetworkDeviceConfiguration
386+
}
387+
388+
func (e *DisconnectedError) Unwrap() error { return e.Err }
389+
func (e *DisconnectedError) Error() string {
390+
if e.Config == nil {
391+
return e.Err.Error()
392+
}
393+
return fmt.Sprintf("%s: %v", e.Config.attachment, e.Err)
394+
}
395+
396+
type disconnected struct {
397+
err error
398+
index int
399+
}
400+
401+
// NetworkDeviceAttachmentWasDisconnected returns a receive channel.
402+
// The channel emits an error message each time the network attachment is disconnected,
403+
// typically triggered by events such as failure to start, initial boot, device reset, or reboot.
404+
// As a result, this method may be invoked multiple times throughout the virtual machine's lifecycle.
405+
//
406+
// This is only supported on macOS 12 and newer, error will be returned on older versions.
407+
func (v *VirtualMachine) NetworkDeviceAttachmentWasDisconnected() (<-chan *DisconnectedError, error) {
408+
if err := macOSAvailable(12); err != nil {
409+
return nil, err
410+
}
411+
v.watchDisconnectedOnce.Do(func() {
412+
go v.watchDisconnected()
413+
})
414+
return v.disconnectedOut.Out(), nil
415+
}
416+
417+
// TODO(codehex): refactoring to leave using machineState's mutex lock.
418+
func (v *VirtualMachine) watchDisconnected() {
419+
for disconnected := range v.disconnectedIn.Out() {
420+
v.mu.RLock()
421+
config := sliceutil.FindValueByIndex(
422+
v.config.networkDeviceConfiguration,
423+
disconnected.index,
424+
)
425+
v.mu.RUnlock()
426+
v.disconnectedOut.In() <- &DisconnectedError{
427+
Err: disconnected.err,
428+
Config: config,
429+
}
430+
}
431+
v.disconnectedOut.Close()
432+
}
433+
434+
//export emitAttachmentWasDisconnected
435+
func emitAttachmentWasDisconnected(index C.int, errPtr unsafe.Pointer, cgoHandleUintptr C.uintptr_t) {
436+
handler := cgo.Handle(cgoHandleUintptr)
437+
err := newNSError(errPtr)
438+
// I expected it will not cause panic.
439+
// if caused panic, that's unexpected behavior.
440+
ch, _ := handler.Value().(*infinity.Channel[*disconnected])
441+
ch.In() <- &disconnected{
442+
err: err,
443+
index: int(index),
444+
}
445+
}
446+
447+
//export closeAttachmentWasDisconnectedChannel
448+
func closeAttachmentWasDisconnectedChannel(cgoHandleUintptr C.uintptr_t) {
449+
handler := cgo.Handle(cgoHandleUintptr)
450+
// I expected it will not cause panic.
451+
// if caused panic, that's unexpected behavior.
452+
ch, _ := handler.Value().(*infinity.Channel[*disconnected])
453+
ch.Close()
454+
}

virtualization_11.h

+7-3
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@
88

99
#import "virtualization_helper.h"
1010
#import <Virtualization/Virtualization.h>
11-
#import <sys/utsname.h>
1211

1312
/* exported from cgo */
1413
void connectionHandler(void *connection, void *err, uintptr_t cgoHandle);
1514
void changeStateOnObserver(int state, uintptr_t cgoHandle);
1615
bool shouldAcceptNewConnectionHandler(uintptr_t cgoHandle, void *connection, void *socketDevice);
16+
void emitAttachmentWasDisconnected(int index, void *err, uintptr_t cgoHandle);
17+
void closeAttachmentWasDisconnectedChannel(uintptr_t cgoHandle);
1718

1819
@interface Observer : NSObject
1920
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
@@ -38,11 +39,14 @@ bool shouldAcceptNewConnectionHandler(uintptr_t cgoHandle, void *connection, voi
3839
- (void)dealloc;
3940
@end
4041

41-
@interface VZVirtualMachineNetworkDeviceErrorHandler : NSObject <VZVirtualMachineDelegate>
42+
@interface NetworkDeviceDisconnectedHandler : NSObject <VZVirtualMachineDelegate>
4243
- (instancetype)initWithHandle:(uintptr_t)cgoHandle;
4344
- (void)virtualMachine:(VZVirtualMachine *)virtualMachine
4445
networkDevice:(VZNetworkDevice *)networkDevice
4546
attachmentWasDisconnectedWithError:(NSError *)error API_AVAILABLE(macos(12.0));
47+
- (int)networkDevices:(NSArray<VZNetworkDevice *> *)networkDevices
48+
indexOf:(VZNetworkDevice *)networkDevice;
49+
- (void)dealloc;
4650
@end
4751

4852
/* VZVirtioSocketListener */
@@ -108,7 +112,7 @@ void VZVirtioSocketDevice_removeSocketListenerForPort(void *socketDevice, void *
108112
void VZVirtioSocketDevice_connectToPort(void *socketDevice, void *vmQueue, uint32_t port, uintptr_t cgoHandle);
109113

110114
/* VirtualMachine */
111-
void *newVZVirtualMachineWithDispatchQueue(void *config, void *queue, uintptr_t statusUpdateCgoHandle);
115+
void *newVZVirtualMachineWithDispatchQueue(void *config, void *queue, uintptr_t statusUpdateCgoHandle, uintptr_t disconnectedCgoHandle);
112116
bool requestStopVirtualMachine(void *machine, void *queue, void **error);
113117
void startWithCompletionHandler(void *machine, void *queue, uintptr_t cgoHandle);
114118
void pauseWithCompletionHandler(void *machine, void *queue, uintptr_t cgoHandle);

0 commit comments

Comments
 (0)