Move VM callback to vmclient
Instead of having clients directly register a callback with VS,
implement a Rust level callback interface in vmclient. This saves an
extra binder call on each notification, a bunch of boilerplate code,
and allows us to provide a slightly better interface (e.g. we can use
the Rust DeathReason enum, as elsewhere in vmclient, for instantly
better logging).
I also replaced all our usages of <some_interface>::binder::{...} with
direct access to binder::{...}. That makes it clearer what depends on
the interface itself and what is just generic binder code. I realise
this should be a separate change, but I only realised that after doing
bits of both.
Test: composd_cmd test-compile, observe logs (on both success & failure)
Test: atest -b (to make sure all our tests build)
Test: Presubmits
Change-Id: Iceda8d7b8f8008f9d7a2c51106c2794f09bb378e
diff --git a/vmclient/src/lib.rs b/vmclient/src/lib.rs
index b3bb635..129e6c3 100644
--- a/vmclient/src/lib.rs
+++ b/vmclient/src/lib.rs
@@ -64,6 +64,32 @@
_death_recipient: DeathRecipient,
}
+/// A trait to be implemented by clients to handle notification of significant changes to the VM
+/// state. Default implementations of all functions are provided so clients only need to handle the
+/// notifications they are interested in.
+#[allow(unused_variables)]
+pub trait VmCallback {
+ /// Called when the payload has been started within the VM. If present, `stream` is connected
+ /// to the stdin/stdout of the payload.
+ fn on_payload_started(&self, cid: i32, stream: Option<&File>) {}
+
+ /// Callend when the payload has notified Virtualization Service that it is ready to serve
+ /// clients.
+ fn on_payload_ready(&self, cid: i32) {}
+
+ /// Called when the payload has exited in the VM. `exit_code` is the exit code of the payload
+ /// process.
+ fn on_payload_finished(&self, cid: i32, exit_code: i32) {}
+
+ /// Called when an error has occurred in the VM. The `error_code` and `message` may give
+ /// further details.
+ fn on_error(&self, cid: i32, error_code: i32, message: &str) {}
+
+ /// Called when the VM has exited, all resources have been freed, and any logs have been
+ /// written. `death_reason` gives an indication why the VM exited.
+ fn on_died(&self, cid: i32, death_reason: DeathReason) {}
+}
+
impl VmInstance {
/// Creates (but doesn't start) a new VM with the given configuration.
pub fn create(
@@ -71,6 +97,7 @@
config: &VirtualMachineConfig,
console: Option<File>,
log: Option<File>,
+ callback: Option<Box<dyn VmCallback + Send + Sync>>,
) -> BinderResult<Self> {
let console = console.map(ParcelFileDescriptor::new);
let log = log.map(ParcelFileDescriptor::new);
@@ -82,7 +109,7 @@
// Register callback before starting VM, in case it dies immediately.
let state = Arc::new(Monitor::new(VmState::default()));
let callback = BnVirtualMachineCallback::new_binder(
- VirtualMachineCallback { state: state.clone() },
+ VirtualMachineCallback { state: state.clone(), client_callback: callback },
BinderFeatures::default(),
);
vm.registerCallback(&callback)?;
@@ -219,9 +246,21 @@
}
}
-#[derive(Debug)]
struct VirtualMachineCallback {
state: Arc<Monitor<VmState>>,
+ client_callback: Option<Box<dyn VmCallback + Send + Sync>>,
+}
+
+impl Debug for VirtualMachineCallback {
+ fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
+ fmt.debug_struct("VirtualMachineCallback")
+ .field("state", &self.state)
+ .field(
+ "client_callback",
+ &if self.client_callback.is_some() { "Some(...)" } else { "None" },
+ )
+ .finish()
+ }
}
impl Interface for VirtualMachineCallback {}
@@ -229,25 +268,37 @@
impl IVirtualMachineCallback for VirtualMachineCallback {
fn onPayloadStarted(
&self,
- _cid: i32,
- _stream: Option<&ParcelFileDescriptor>,
+ cid: i32,
+ stream: Option<&ParcelFileDescriptor>,
) -> BinderResult<()> {
self.state.notify_state(VirtualMachineState::STARTED);
+ if let Some(ref callback) = self.client_callback {
+ callback.on_payload_started(cid, stream.map(ParcelFileDescriptor::as_ref));
+ }
Ok(())
}
- fn onPayloadReady(&self, _cid: i32) -> BinderResult<()> {
+ fn onPayloadReady(&self, cid: i32) -> BinderResult<()> {
self.state.notify_state(VirtualMachineState::READY);
+ if let Some(ref callback) = self.client_callback {
+ callback.on_payload_ready(cid);
+ }
Ok(())
}
- fn onPayloadFinished(&self, _cid: i32, _exit_code: i32) -> BinderResult<()> {
+ fn onPayloadFinished(&self, cid: i32, exit_code: i32) -> BinderResult<()> {
self.state.notify_state(VirtualMachineState::FINISHED);
+ if let Some(ref callback) = self.client_callback {
+ callback.on_payload_finished(cid, exit_code);
+ }
Ok(())
}
- fn onError(&self, _cid: i32, _error_code: i32, _message: &str) -> BinderResult<()> {
+ fn onError(&self, cid: i32, error_code: i32, message: &str) -> BinderResult<()> {
self.state.notify_state(VirtualMachineState::FINISHED);
+ if let Some(ref callback) = self.client_callback {
+ callback.on_error(cid, error_code, message);
+ }
Ok(())
}
@@ -257,8 +308,12 @@
Ok(())
}
- fn onDied(&self, _cid: i32, reason: AidlDeathReason) -> BinderResult<()> {
- self.state.notify_death(reason.into());
+ fn onDied(&self, cid: i32, reason: AidlDeathReason) -> BinderResult<()> {
+ let reason = reason.into();
+ self.state.notify_death(reason);
+ if let Some(ref callback) = self.client_callback {
+ callback.on_died(cid, reason);
+ }
Ok(())
}
}