Skip to content

Commit 46a19ee

Browse files
authored
Merge pull request #168 from Code-Hex/fix/delegate-pr
2 parents 8aa9759 + 239cc8d commit 46a19ee

8 files changed

+206
-40
lines changed

configuration.go

+9
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ type VirtualMachineConfiguration struct {
3737
cpuCount uint
3838
memorySize uint64
3939
*pointer
40+
41+
storageDeviceConfiguration []StorageDeviceConfiguration
4042
}
4143

4244
// NewVirtualMachineConfiguration creates a new configuration.
@@ -172,6 +174,13 @@ func (v *VirtualMachineConfiguration) SetStorageDevicesVirtualMachineConfigurati
172174
}
173175
array := objc.ConvertToNSMutableArray(ptrs)
174176
C.setStorageDevicesVZVirtualMachineConfiguration(objc.Ptr(v), objc.Ptr(array))
177+
v.storageDeviceConfiguration = cs
178+
}
179+
180+
// StorageDevices return the list of storage device configuration configured in this virtual machine configuration.
181+
// Return an empty array if no storage device configuration is set.
182+
func (v *VirtualMachineConfiguration) StorageDevices() []StorageDeviceConfiguration {
183+
return v.storageDeviceConfiguration
175184
}
176185

177186
// SetDirectorySharingDevicesVirtualMachineConfiguration sets list of directory sharing devices. Empty by default.

example/macOS/main.go

+55-23
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,21 @@ func runVM(ctx context.Context) error {
7878
}
7979
}()
8080

81+
// it start listening to the NBD server, if any
82+
nbdAttachment := retrieveNetworkBlockDeviceStorageDeviceAttachment(config.StorageDevices())
83+
if nbdAttachment != nil {
84+
go func() {
85+
for {
86+
select {
87+
case err := <-nbdAttachment.DidEncounterError():
88+
log.Printf("NBD client has been encountered error: %v\n", err)
89+
case <-nbdAttachment.Connected():
90+
log.Println("NBD client connected with the server")
91+
}
92+
}
93+
}()
94+
}
95+
8196
// cleanup is this function is useful when finished graphic application.
8297
cleanup := func() {
8398
for i := 1; vm.CanRequestStop(); i++ {
@@ -102,9 +117,7 @@ func runVM(ctx context.Context) error {
102117
log.Println("finished cleanup")
103118
}
104119

105-
runtime.LockOSThread()
106120
vm.StartGraphicApplication(960, 600)
107-
runtime.UnlockOSThread()
108121

109122
cleanup()
110123

@@ -144,29 +157,30 @@ func computeMemorySize() uint64 {
144157
}
145158

146159
func createBlockDeviceConfiguration(diskPath string) (*vz.VirtioBlockDeviceConfiguration, error) {
147-
var attachment vz.StorageDeviceAttachment
148-
var err error
149-
150-
if nbdURL == "" {
151-
// create disk image with 64 GiB
152-
if err := vz.CreateDiskImage(diskPath, 64*1024*1024*1024); err != nil {
153-
if !os.IsExist(err) {
154-
return nil, fmt.Errorf("failed to create disk image: %w", err)
155-
}
160+
// create disk image with 64 GiB
161+
if err := vz.CreateDiskImage(diskPath, 64*1024*1024*1024); err != nil {
162+
if !os.IsExist(err) {
163+
return nil, fmt.Errorf("failed to create disk image: %w", err)
156164
}
165+
}
157166

158-
attachment, err = vz.NewDiskImageStorageDeviceAttachment(
159-
diskPath,
160-
false,
161-
)
162-
} else {
163-
attachment, err = vz.NewNetworkBlockDeviceStorageDeviceAttachment(
164-
nbdURL,
165-
10*time.Second,
166-
false,
167-
vz.DiskSynchronizationModeFull,
168-
)
167+
attachment, err := vz.NewDiskImageStorageDeviceAttachment(
168+
diskPath,
169+
false,
170+
)
171+
if err != nil {
172+
return nil, err
169173
}
174+
return vz.NewVirtioBlockDeviceConfiguration(attachment)
175+
}
176+
177+
func createNetworkBlockDeviceConfiguration(nbdURL string) (*vz.VirtioBlockDeviceConfiguration, error) {
178+
attachment, err := vz.NewNetworkBlockDeviceStorageDeviceAttachment(
179+
nbdURL,
180+
10*time.Second,
181+
false,
182+
vz.DiskSynchronizationModeFull,
183+
)
170184
if err != nil {
171185
return nil, err
172186
}
@@ -277,7 +291,15 @@ func setupVMConfiguration(platformConfig vz.PlatformConfiguration) (*vz.VirtualM
277291
if err != nil {
278292
return nil, fmt.Errorf("failed to create block device configuration: %w", err)
279293
}
280-
config.SetStorageDevicesVirtualMachineConfiguration([]vz.StorageDeviceConfiguration{blockDeviceConfig})
294+
sdconfigs := []vz.StorageDeviceConfiguration{blockDeviceConfig}
295+
if nbdURL != "" {
296+
ndbConfig, err := createNetworkBlockDeviceConfiguration(nbdURL)
297+
if err != nil {
298+
return nil, fmt.Errorf("failed to create network block device configuration: %w", err)
299+
}
300+
sdconfigs = append(sdconfigs, ndbConfig)
301+
}
302+
config.SetStorageDevicesVirtualMachineConfiguration(sdconfigs)
281303

282304
networkDeviceConfig, err := createNetworkDeviceConfiguration()
283305
if err != nil {
@@ -331,3 +353,13 @@ func setupVMConfiguration(platformConfig vz.PlatformConfiguration) (*vz.VirtualM
331353

332354
return config, nil
333355
}
356+
357+
func retrieveNetworkBlockDeviceStorageDeviceAttachment(storages []vz.StorageDeviceConfiguration) *vz.NetworkBlockDeviceStorageDeviceAttachment {
358+
for _, storage := range storages {
359+
attachment := storage.Attachment()
360+
if nbdAttachment, ok := attachment.(*vz.NetworkBlockDeviceStorageDeviceAttachment); ok {
361+
return nbdAttachment
362+
}
363+
}
364+
return nil
365+
}

storage.go

+85-11
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@ package vz
1212
import "C"
1313
import (
1414
"os"
15+
"runtime/cgo"
1516
"time"
17+
"unsafe"
1618

19+
infinity "github.com/Code-Hex/go-infinity-channel"
1720
"github.com/Code-Hex/vz/v3/internal/objc"
1821
)
1922

@@ -151,11 +154,17 @@ type StorageDeviceConfiguration interface {
151154
objc.NSObject
152155

153156
storageDeviceConfiguration()
157+
Attachment() StorageDeviceAttachment
154158
}
155159

156-
type baseStorageDeviceConfiguration struct{}
160+
type baseStorageDeviceConfiguration struct {
161+
attachment StorageDeviceAttachment
162+
}
157163

158164
func (*baseStorageDeviceConfiguration) storageDeviceConfiguration() {}
165+
func (b *baseStorageDeviceConfiguration) Attachment() StorageDeviceAttachment {
166+
return b.attachment
167+
}
159168

160169
var _ StorageDeviceConfiguration = (*VirtioBlockDeviceConfiguration)(nil)
161170

@@ -192,6 +201,9 @@ func NewVirtioBlockDeviceConfiguration(attachment StorageDeviceAttachment) (*Vir
192201
objc.Ptr(attachment),
193202
),
194203
),
204+
baseStorageDeviceConfiguration: &baseStorageDeviceConfiguration{
205+
attachment: attachment,
206+
},
195207
}
196208
objc.SetFinalizer(config, func(self *VirtioBlockDeviceConfiguration) {
197209
objc.Release(self)
@@ -250,10 +262,6 @@ type USBMassStorageDeviceConfiguration struct {
250262
*pointer
251263

252264
*baseStorageDeviceConfiguration
253-
254-
// marking as currently reachable.
255-
// This ensures that the object is not freed, and its finalizer is not run
256-
attachment StorageDeviceAttachment
257265
}
258266

259267
// NewUSBMassStorageDeviceConfiguration initialize a USBMassStorageDeviceConfiguration
@@ -269,7 +277,9 @@ func NewUSBMassStorageDeviceConfiguration(attachment StorageDeviceAttachment) (*
269277
pointer: objc.NewPointer(
270278
C.newVZUSBMassStorageDeviceConfiguration(objc.Ptr(attachment)),
271279
),
272-
attachment: attachment,
280+
baseStorageDeviceConfiguration: &baseStorageDeviceConfiguration{
281+
attachment: attachment,
282+
},
273283
}
274284
objc.SetFinalizer(usbMass, func(self *USBMassStorageDeviceConfiguration) {
275285
objc.Release(self)
@@ -284,10 +294,6 @@ type NVMExpressControllerDeviceConfiguration struct {
284294
*pointer
285295

286296
*baseStorageDeviceConfiguration
287-
288-
// marking as currently reachable.
289-
// This ensures that the object is not freed, and its finalizer is not run
290-
attachment StorageDeviceAttachment
291297
}
292298

293299
// NewNVMExpressControllerDeviceConfiguration creates a new NVMExpressControllerDeviceConfiguration with
@@ -306,7 +312,9 @@ func NewNVMExpressControllerDeviceConfiguration(attachment StorageDeviceAttachme
306312
pointer: objc.NewPointer(
307313
C.newVZNVMExpressControllerDeviceConfiguration(objc.Ptr(attachment)),
308314
),
309-
attachment: attachment,
315+
baseStorageDeviceConfiguration: &baseStorageDeviceConfiguration{
316+
attachment: attachment,
317+
},
310318
}
311319
objc.SetFinalizer(nvmExpress, func(self *NVMExpressControllerDeviceConfiguration) {
312320
objc.Release(self)
@@ -411,6 +419,9 @@ type NetworkBlockDeviceStorageDeviceAttachment struct {
411419
*pointer
412420

413421
*baseStorageDeviceAttachment
422+
423+
didEncounterError *infinity.Channel[error]
424+
connected *infinity.Channel[struct{}]
414425
}
415426

416427
var _ StorageDeviceAttachment = (*NetworkBlockDeviceStorageDeviceAttachment)(nil)
@@ -419,6 +430,9 @@ var _ StorageDeviceAttachment = (*NetworkBlockDeviceStorageDeviceAttachment)(nil
419430
// Uniform Resource Indicator (URI) represented as a URL, timeout value, and read-only and synchronization modes
420431
// that you provide.
421432
//
433+
// It also set up a channel that will be used by the VZNetworkBlockDeviceStorageDeviceAttachmentDelegate to
434+
// return changes to the NetworkBlockDeviceAttachment
435+
//
422436
// - url is the NBD server URI. The format specified by https://github.com/NetworkBlockDevice/nbd/blob/master/doc/uri.md
423437
// - timeout is the duration for the connection between the client and server. When the timeout expires, an attempt to reconnect with the server takes place.
424438
// - forcedReadOnly if true forces the disk attachment to be read-only, regardless of whether or not the NBD server supports write requests.
@@ -431,6 +445,17 @@ func NewNetworkBlockDeviceStorageDeviceAttachment(url string, timeout time.Durat
431445
return nil, err
432446
}
433447

448+
didEncounterError := infinity.NewChannel[error]()
449+
connected := infinity.NewChannel[struct{}]()
450+
451+
handle := cgo.NewHandle(func(err error) {
452+
if err != nil {
453+
didEncounterError.In() <- err
454+
return
455+
}
456+
connected.In() <- struct{}{}
457+
})
458+
434459
nserrPtr := newNSErrorAsNil()
435460

436461
urlChar := charWithGoString(url)
@@ -443,8 +468,11 @@ func NewNetworkBlockDeviceStorageDeviceAttachment(url string, timeout time.Durat
443468
C.bool(forcedReadOnly),
444469
C.int(syncMode),
445470
&nserrPtr,
471+
C.uintptr_t(handle),
446472
),
447473
),
474+
didEncounterError: didEncounterError,
475+
connected: connected,
448476
}
449477
if err := newNSError(nserrPtr); err != nil {
450478
return nil, err
@@ -454,3 +482,49 @@ func NewNetworkBlockDeviceStorageDeviceAttachment(url string, timeout time.Durat
454482
})
455483
return attachment, nil
456484
}
485+
486+
// Connected receive the signal via channel when the NBD client successfully connects or reconnects with the server.
487+
//
488+
// The NBD connection with the server takes place when the VM is first started, and reconnection attempts take place when the connection
489+
// times out or when the NBD client has encountered a recoverable error, such as an I/O error from the server.
490+
//
491+
// Note that the Virtualization framework may call this method multiple times during a VM’s life cycle. Reconnections are transparent to the guest.
492+
func (n *NetworkBlockDeviceStorageDeviceAttachment) Connected() <-chan struct{} {
493+
return n.connected.Out()
494+
}
495+
496+
// The DidEncounterError is triggered via the channel when the NBD client encounters an error that cannot be resolved on the client side.
497+
// In this state, the client will continue attempting to reconnect, but recovery depends entirely on the server's availability.
498+
// If the server resumes operation, the connection will recover automatically; however, until the server is restored, the client will continue to experience errors.
499+
func (n *NetworkBlockDeviceStorageDeviceAttachment) DidEncounterError() <-chan error {
500+
return n.didEncounterError.Out()
501+
}
502+
503+
// attachmentDidEncounterErrorHandler function is called when the NBD client encounters a nonrecoverable error.
504+
// After the attachment object calls this method, the NBD client is in a nonfunctional state.
505+
//
506+
//export attachmentDidEncounterErrorHandler
507+
func attachmentDidEncounterErrorHandler(cgoHandleUintptr C.uintptr_t, errorPtr unsafe.Pointer) {
508+
cgoHandle := cgo.Handle(cgoHandleUintptr)
509+
handler := cgoHandle.Value().(func(error))
510+
511+
err := newNSError(errorPtr)
512+
513+
handler(err)
514+
}
515+
516+
// attachmentWasConnectedHandler function is called when a connection to the server is first established as the VM starts,
517+
// and during any reconnection attempts triggered by connection timeouts or recoverable errors encountered by the NBD client,
518+
// such as server-side I/O errors.
519+
//
520+
// Note that the Virtualization framework may invoke this method multiple times throughout the VM’s lifecycle,
521+
// ensuring reconnection processes remain seamless and transparent to the guest.
522+
// For more details, see: https://developer.apple.com/documentation/virtualization/vznetworkblockdevicestoragedeviceattachmentdelegate/4168511-attachmentwasconnected?language=objc
523+
//
524+
//export attachmentWasConnectedHandler
525+
func attachmentWasConnectedHandler(cgoHandleUintptr C.uintptr_t) {
526+
cgoHandle := cgo.Handle(cgoHandleUintptr)
527+
handler := cgoHandle.Value().(func(error))
528+
529+
handler(nil)
530+
}

virtualization_11.h

+1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ void setSocketDevicesVZVirtualMachineConfiguration(void *config,
5959
void *socketDevicesVZVirtualMachineConfiguration(void *config);
6060
void setStorageDevicesVZVirtualMachineConfiguration(void *config,
6161
void *storageDevices);
62+
void *storageDevicesVZVirtualMachineConfiguration(void *config);
6263

6364
/* Configurations */
6465
void *newVZFileHandleSerialPortAttachment(int readFileDescriptor, int writeFileDescriptor);

virtualization_11.m

+12
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,18 @@ void setStorageDevicesVZVirtualMachineConfiguration(void *config,
327327
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
328328
}
329329

330+
/*!
331+
@abstract Return the list of storage devices configurations for this VZVirtualMachineConfiguration. Return an empty array if no storage device configuration is set.
332+
*/
333+
void *storageDevicesVZVirtualMachineConfiguration(void *config)
334+
{
335+
if (@available(macOS 11, *)) {
336+
return [(VZVirtualMachineConfiguration *)config storageDevices]; // NSArray<VZStorageDeviceConfiguration *>
337+
}
338+
339+
RAISE_UNSUPPORTED_MACOS_EXCEPTION();
340+
}
341+
330342
/*!
331343
@abstract Intialize the VZFileHandleSerialPortAttachment from file descriptors.
332344
@param readFileDescriptor File descriptor for reading from the file.

virtualization_14.h

+11-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,17 @@
1313
#import "virtualization_helper.h"
1414
#import <Virtualization/Virtualization.h>
1515

16+
/* exported from cgo */
17+
void attachmentDidEncounterErrorHandler(uintptr_t cgoHandle, void *err);
18+
void attachmentWasConnectedHandler(uintptr_t cgoHandle);
19+
1620
/* macOS 14 API */
1721
void *newVZNVMExpressControllerDeviceConfiguration(void *attachment);
1822
void *newVZDiskBlockDeviceStorageDeviceAttachment(int fileDescriptor, bool readOnly, int syncMode, void **error);
19-
void *newVZNetworkBlockDeviceStorageDeviceAttachment(const char *url, double timeout, bool forcedReadOnly, int syncMode, void **error);
23+
void *newVZNetworkBlockDeviceStorageDeviceAttachment(const char *url, double timeout, bool forcedReadOnly, int syncMode, void **error, uintptr_t cgoHandle);
24+
25+
@interface VZNetworkBlockDeviceStorageDeviceAttachmentDelegateImpl : NSObject <VZNetworkBlockDeviceStorageDeviceAttachmentDelegate>
26+
- (instancetype)initWithHandle:(uintptr_t)cgoHandle;
27+
- (void)attachment:(VZNetworkBlockDeviceStorageDeviceAttachment *)attachment didEncounterError:(NSError *)error;
28+
- (void)attachmentWasConnected:(VZNetworkBlockDeviceStorageDeviceAttachment *)attachment;
29+
@end

0 commit comments

Comments
 (0)