Async interface for running odrefresh
This creates a variant of startTestOdrefresh that uses the
ICompilationTask async mechanism of startTestCompile.
OdrefreshTask implements ICompilationTask; instead of owning a spawned
odrefresh process it owns the VM and runs the blocking call to compsvc
inside a thread.
Add a new command to composd_cmd to exercise this.
Test: composd_cmd async-odrefresh
Bug: 209572296
Change-Id: I3780d40876dbca17d4e2a3d8f7c2b809f5561aee
diff --git a/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl b/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl
index 5e72cd2..e73963d 100644
--- a/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl
+++ b/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl
@@ -50,6 +50,19 @@
* and writes the results to a test directory to avoid disrupting any real artifacts in
* existence.
*
+ * Compilation continues in the background, and success/failure is reported via the supplied
+ * callback, unless the returned ICompilationTask is cancelled. The caller should maintain
+ * a reference to the ICompilationTask until compilation completes or is cancelled.
+ */
+ ICompilationTask startAsyncOdrefresh(ICompilationTaskCallback callback);
+
+ /**
+ * Run odrefresh in a test instance of CompOS until completed or failed.
+ *
+ * This compiles BCP extensions and system server, even if the system artifacts are up to date,
+ * and writes the results to a test directory to avoid disrupting any real artifacts in
+ * existence.
+ *
* TODO(205750213): Change the API to async.
*/
byte startTestOdrefresh();
diff --git a/compos/composd/src/composd_main.rs b/compos/composd/src/composd_main.rs
index 67b5974..2915a58 100644
--- a/compos/composd/src/composd_main.rs
+++ b/compos/composd/src/composd_main.rs
@@ -24,6 +24,7 @@
mod instance_starter;
mod internal_service;
mod odrefresh;
+mod odrefresh_task;
mod service;
mod util;
diff --git a/compos/composd/src/odrefresh_task.rs b/compos/composd/src/odrefresh_task.rs
new file mode 100644
index 0000000..f017f67
--- /dev/null
+++ b/compos/composd/src/odrefresh_task.rs
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+use crate::fd_server_helper::FdServerConfig;
+use crate::instance_starter::CompOsInstance;
+use crate::odrefresh;
+use crate::service::open_dir;
+use android_system_composd::aidl::android::system::composd::{
+ ICompilationTask::ICompilationTask, ICompilationTaskCallback::ICompilationTaskCallback,
+};
+use android_system_composd::binder::{Interface, Result as BinderResult, Strong};
+use anyhow::{bail, Result};
+use compos_aidl_interface::aidl::com::android::compos::ICompOsService::ICompOsService;
+use log::{error, warn};
+use num_traits::FromPrimitive;
+use rustutils::system_properties;
+use std::os::unix::io::AsRawFd;
+use std::path::{Path, PathBuf};
+use std::sync::{Arc, Mutex};
+use std::thread;
+
+#[derive(Clone)]
+pub struct OdrefreshTask {
+ running_task: Arc<Mutex<Option<RunningTask>>>,
+}
+
+impl Interface for OdrefreshTask {}
+
+impl ICompilationTask for OdrefreshTask {
+ fn cancel(&self) -> BinderResult<()> {
+ let task = self.take();
+ // Drop the VM, which should end compilation - and cause our thread to exit
+ drop(task);
+ Ok(())
+ }
+}
+
+impl OdrefreshTask {
+ /// Return the current running task, if any, removing it from this CompilationTask.
+ /// Once removed, meaning the task has ended or been canceled, further calls will always return
+ /// None.
+ fn take(&self) -> Option<RunningTask> {
+ self.running_task.lock().unwrap().take()
+ }
+
+ pub fn start(
+ comp_os: Arc<CompOsInstance>,
+ output_dir_path: PathBuf,
+ callback: &Strong<dyn ICompilationTaskCallback>,
+ ) -> Result<OdrefreshTask> {
+ let service = comp_os.get_service();
+ let task = RunningTask { comp_os, callback: callback.clone() };
+ let task = OdrefreshTask { running_task: Arc::new(Mutex::new(Some(task))) };
+
+ task.clone().start_thread(service, output_dir_path);
+
+ Ok(task)
+ }
+
+ fn start_thread(self, service: Strong<dyn ICompOsService>, output_dir_path: PathBuf) {
+ thread::spawn(move || {
+ let exit_code = try_odrefresh(service, &output_dir_path);
+
+ let task = self.take();
+ // We don't do the callback if cancel has already happened.
+ if let Some(task) = task {
+ let result = match exit_code {
+ Ok(odrefresh::ExitCode::CompilationSuccess) => task.callback.onSuccess(),
+ Ok(exit_code) => {
+ error!("Unexpected odrefresh result: {:?}", exit_code);
+ task.callback.onFailure()
+ }
+ Err(e) => {
+ error!("Running odrefresh failed: {:?}", e);
+ task.callback.onFailure()
+ }
+ };
+ if let Err(e) = result {
+ warn!("Failed to deliver callback: {:?}", e);
+ }
+ }
+ });
+ }
+}
+
+fn try_odrefresh(
+ service: Strong<dyn ICompOsService>,
+ output_dir_path: &Path,
+) -> Result<odrefresh::ExitCode> {
+ let output_dir = open_dir(output_dir_path)?;
+ let system_dir = open_dir(Path::new("/system"))?;
+
+ // Spawn a fd_server to serve the FDs.
+ let fd_server_config = FdServerConfig {
+ ro_dir_fds: vec![system_dir.as_raw_fd()],
+ rw_dir_fds: vec![output_dir.as_raw_fd()],
+ ..Default::default()
+ };
+ let fd_server_raii = fd_server_config.into_fd_server()?;
+
+ let zygote_arch = system_properties::read("ro.zygote")?;
+ let exit_code =
+ service.odrefresh(system_dir.as_raw_fd(), output_dir.as_raw_fd(), &zygote_arch)?.exitCode;
+
+ drop(fd_server_raii);
+ if let Some(exit_code) = FromPrimitive::from_i8(exit_code) {
+ Ok(exit_code)
+ } else {
+ bail!("odrefresh exited with {}", exit_code)
+ }
+}
+
+struct RunningTask {
+ callback: Strong<dyn ICompilationTaskCallback>,
+ #[allow(dead_code)] // Keeps the CompOS VM alive
+ comp_os: Arc<CompOsInstance>,
+}
diff --git a/compos/composd/src/service.rs b/compos/composd/src/service.rs
index a2898a2..ab28e0a 100644
--- a/compos/composd/src/service.rs
+++ b/compos/composd/src/service.rs
@@ -20,6 +20,7 @@
use crate::compilation_task::CompilationTask;
use crate::fd_server_helper::FdServerConfig;
use crate::instance_manager::InstanceManager;
+use crate::odrefresh_task::OdrefreshTask;
use crate::util::to_binder_result;
use android_system_composd::aidl::android::system::composd::{
ICompilationTask::{BnCompilationTask, ICompilationTask},
@@ -67,6 +68,14 @@
to_binder_result(self.do_start_test_compile(callback))
}
+ fn startAsyncOdrefresh(
+ &self,
+ callback: &Strong<dyn ICompilationTaskCallback>,
+ ) -> binder::Result<Strong<dyn ICompilationTask>> {
+ check_permissions()?;
+ to_binder_result(self.do_start_async_odrefresh(callback))
+ }
+
fn startTestOdrefresh(&self) -> binder::Result<i8> {
check_permissions()?;
to_binder_result(self.do_odrefresh_for_test())
@@ -97,6 +106,20 @@
Ok(BnCompilationTask::new_binder(task, BinderFeatures::default()))
}
+ fn do_start_async_odrefresh(
+ &self,
+ callback: &Strong<dyn ICompilationTaskCallback>,
+ ) -> Result<Strong<dyn ICompilationTask>> {
+ let output_dir_path =
+ composd_native::palette_create_odrefresh_staging_directory()?.to_path_buf();
+
+ let comp_os = self.instance_manager.start_test_instance().context("Starting CompOS")?;
+
+ let task = OdrefreshTask::start(comp_os, output_dir_path, callback)?;
+
+ Ok(BnCompilationTask::new_binder(task, BinderFeatures::default()))
+ }
+
fn do_odrefresh_for_test(&self) -> Result<i8> {
let compos = self
.instance_manager
@@ -137,7 +160,7 @@
/// Returns an owned FD of the directory. It currently returns a `File` as a FD owner, but
/// it's better to use `std::os::unix::io::OwnedFd` once/if it becomes standard.
-fn open_dir(path: &Path) -> Result<File> {
+pub fn open_dir(path: &Path) -> Result<File> {
OpenOptions::new()
.custom_flags(libc::O_DIRECTORY)
.read(true) // O_DIRECTORY can only be opened with read
diff --git a/compos/composd_cmd/composd_cmd.rs b/compos/composd_cmd/composd_cmd.rs
index f22dc13..37d5378 100644
--- a/compos/composd_cmd/composd_cmd.rs
+++ b/compos/composd_cmd/composd_cmd.rs
@@ -34,11 +34,9 @@
fn main() -> Result<()> {
let app = clap::App::new("composd_cmd").arg(
- clap::Arg::with_name("command")
- .index(1)
- .takes_value(true)
- .required(true)
- .possible_values(&["staged-apex-compile", "forced-compile-test", "forced-odrefresh"]),
+ clap::Arg::with_name("command").index(1).takes_value(true).required(true).possible_values(
+ &["staged-apex-compile", "forced-compile-test", "forced-odrefresh", "async-odrefresh"],
+ ),
);
let args = app.get_matches();
let command = args.value_of("command").unwrap();
@@ -49,6 +47,7 @@
"staged-apex-compile" => run_staged_apex_compile()?,
"forced-compile-test" => run_forced_compile_for_test()?,
"forced-odrefresh" => run_forced_odrefresh_for_test()?,
+ "async-odrefresh" => run_async_odrefresh_for_test()?,
_ => panic!("Unexpected command {}", command),
}
@@ -113,6 +112,10 @@
run_async_compilation(|service, callback| service.startTestCompile(callback))
}
+fn run_async_odrefresh_for_test() -> Result<()> {
+ run_async_compilation(|service, callback| service.startAsyncOdrefresh(callback))
+}
+
fn run_async_compilation<F>(start_compile_fn: F) -> Result<()>
where
F: FnOnce(