`VirtualCpu` should be `Sync` to allow cross-thread exit() calls
Hi! Thank you so much for creating and maintaining ahv - it's a wonderful Rust binding for Hypervisor framework.
Background
I've been working with the VirtualCpu struct and noticed something that might be worth discussing. Currently, the struct uses PhantomData<*const u8> to make it !Send, which makes perfect sense since a vCPU should be tied to a specific thread. However, this marker also makes the struct !Sync, which seems to prevent some usage patterns that Apple's documentation suggests should be possible.
Apple's Documentation
Looking at Apple's Hypervisor framework documentation, I found this note:
Hypervisor creates Virtual CPUs with
hv_vcpu_create(_:_:_:). Call functions that operate on the vCPU from the same thread with the exception ofhv_vcpus_exit(_:_:). Enter the vCPU by usinghv_vcpu_run(_:). The function runs until the guest traps or an other thread callshv_vcpus_exit(_:_:). On exit,hv_vcpu_run(_:)populates thehv_vcpu_exit_twith the exit reason.
This seems to suggest that hv_vcpus_exit() is designed to be called from a different thread, which would be a common pattern for interrupting a running vCPU.
Use Case Example
I was hoping to implement something like this:
use std::sync::Arc;
use std::thread;
fn example_usage() {
let vcpu = Arc::new(vm.create_vcpu(None));
let vcpu_clone = vcpu.clone();
// Thread: Interrupt the vCPU when needed
let interruptor = thread::spawn(move || {
thread::sleep(Duration::from_secs(5));
vcpu_clone.exit(); // Send interrupt from another thread
});
loop {
vcpu.run(); // Execute guest code
// Handle exits, continue execution, etc.
}
interruptor.join().unwrap();
}
Current Situation
Right now, the PhantomData<*const u8> marker makes VirtualCpu both !Send and !Sync. While !Send is exactly what we want, !Sync prevents sharing references across threads, which seems to block the cross-thread exit() pattern that Apple's documentation describes.
I also noticed there's an exit_vcpus(&mut self, vcpus: &[hv_vcpu_t]) API on the VirtualMachine struct, but since VirtualMachine is also not Send or Sync, it faces the same limitation when trying to implement cross-thread interrupt functionality.
Possible Approach
I wonder if we could use a marker that's !Send but allows Sync? Something like:
// Option 1: Custom marker
struct NotSendButSync<T>(PhantomData<fn() -> T>);
unsafe impl<T> Sync for NotSendButSync<T> {}
pub struct VirtualCpu {
not_send_marker: NotSendButSync<()>,
// ... rest of fields
}
Or perhaps:
// Option 2: Use Rc<()> which is !Send but Sync
pub struct VirtualCpu {
not_send_marker: PhantomData<std::rc::Rc<()>>,
// ... rest of fields
}
This would keep the VirtualCpu tied to its thread while allowing references to be shared for the exit() functionality.
Question
Does this make sense, or am I missing something about the intended design? I'd love to hear your thoughts on whether this kind of cross-thread exit() pattern is something you'd want to support.
Thanks again for all your work on this project!