[service-vm] Ensure only one service VM is running at any time
Bug: 299090012
Test: atest MicrodroidHostTests rialto_test
Change-Id: Ic301a4822bbe6460522b0ee7542373f387075274
diff --git a/service_vm_manager/Android.bp b/service_vm_manager/Android.bp
index b3618a6..6469212 100644
--- a/service_vm_manager/Android.bp
+++ b/service_vm_manager/Android.bp
@@ -12,6 +12,7 @@
"android.system.virtualizationservice-rust",
"libanyhow",
"libciborium",
+ "liblazy_static",
"liblog_rust",
"libnix",
"libservice_vm_comm",
diff --git a/service_vm_manager/src/lib.rs b/service_vm_manager/src/lib.rs
index a645202..8dedec5 100644
--- a/service_vm_manager/src/lib.rs
+++ b/service_vm_manager/src/lib.rs
@@ -25,12 +25,14 @@
binder::ParcelFileDescriptor,
};
use anyhow::{anyhow, ensure, Context, Result};
+use lazy_static::lazy_static;
use log::{info, warn};
use service_vm_comm::{Request, Response, ServiceVmRequest, VmType};
use std::fs::{File, OpenOptions};
use std::io::{self, BufRead, BufReader, BufWriter, Write};
use std::os::unix::io::FromRawFd;
use std::path::{Path, PathBuf};
+use std::sync::{Condvar, Mutex, MutexGuard};
use std::thread;
use std::time::Duration;
use vmclient::{DeathReason, VmInstance};
@@ -45,6 +47,43 @@
const READ_TIMEOUT: Duration = Duration::from_secs(10);
const WRITE_TIMEOUT: Duration = Duration::from_secs(10);
+lazy_static! {
+ static ref SERVICE_VM_STATE: State = State::default();
+}
+
+/// The running state of the Service VM.
+#[derive(Debug, Default)]
+struct State {
+ is_running: Mutex<bool>,
+ stopped: Condvar,
+}
+
+impl State {
+ fn wait_until_no_service_vm_running(&self) -> Result<MutexGuard<'_, bool>> {
+ // The real timeout can be longer than 10 seconds since the time to acquire
+ // is_running mutex is not counted in the 10 seconds.
+ let (guard, wait_result) = self
+ .stopped
+ .wait_timeout_while(
+ self.is_running.lock().unwrap(),
+ Duration::from_secs(10),
+ |&mut is_running| is_running,
+ )
+ .unwrap();
+ ensure!(
+ !wait_result.timed_out(),
+ "Timed out while waiting for the running service VM to stop."
+ );
+ Ok(guard)
+ }
+
+ fn notify_service_vm_shutdown(&self) {
+ let mut is_running_guard = self.is_running.lock().unwrap();
+ *is_running_guard = false;
+ self.stopped.notify_one();
+ }
+}
+
/// Service VM.
pub struct ServiceVm {
vsock_stream: VsockStream,
@@ -55,11 +94,18 @@
impl ServiceVm {
/// Starts the service VM and returns its instance.
/// The same instance image is used for different VMs.
- /// TODO(b/278858244): Allow only one service VM running at each time.
+ /// At any given time, only one service should be running. If a service VM is
+ /// already running, this function will start the service VM once the running one
+ /// shuts down.
pub fn start() -> Result<Self> {
+ let mut is_running_guard = SERVICE_VM_STATE.wait_until_no_service_vm_running()?;
+
let instance_img_path = Path::new(VIRT_DATA_DIR).join(INSTANCE_IMG_NAME);
let vm = protected_vm_instance(instance_img_path)?;
- Self::start_vm(vm, VmType::ProtectedVm)
+
+ let vm = Self::start_vm(vm, VmType::ProtectedVm)?;
+ *is_running_guard = true;
+ Ok(vm)
}
/// Starts the given VM instance and sets up the vsock connection with it.
@@ -128,6 +174,7 @@
Ok(reason) => info!("Exit the service VM successfully: {reason:?}"),
Err(e) => warn!("Service VM shutdown request failed '{e:?}', killing it."),
}
+ SERVICE_VM_STATE.notify_service_vm_shutdown();
}
}