use anyhow::{Context, Error};
use fidl_fuchsia_update::{CommitStatusProviderMarker, CommitStatusProviderProxy};
use fuchsia_async as fasync;
use fuchsia_component::client::connect_to_protocol;
use futures::future::FusedFuture;
use futures::prelude::*;
use std::pin::pin;
use std::time::Duration;
const WARNING_DURATION: Duration = Duration::from_secs(30);
pub async fn handle_wait_for_commit() -> Result<(), Error> {
let proxy = connect_to_protocol::<CommitStatusProviderMarker>()
.context("while connecting to fuchsia.update/CommitStatusProvider")?;
handle_wait_for_commit_impl(&proxy, Printer).await
}
#[derive(Debug, PartialEq)]
enum CommitEvent {
Begin,
Warning,
End,
}
trait CommitObserver {
fn on_event(&self, event: CommitEvent);
}
struct Printer;
impl CommitObserver for Printer {
fn on_event(&self, event: CommitEvent) {
let text = match event {
CommitEvent::Begin => "Waiting for commit.",
CommitEvent::Warning => {
"It's been 30 seconds. Something is probably wrong. Consider \
running `update revert` to fall back to the previous slot."
}
CommitEvent::End => "Committed!",
};
println!("{text}");
}
}
async fn wait_for_commit(proxy: &CommitStatusProviderProxy) -> Result<(), Error> {
let p = proxy.is_current_system_committed().await.context("while obtaining EventPair")?;
fasync::OnSignals::new(&p, zx::Signals::USER_0)
.await
.context("while waiting for the commit")?;
Ok(())
}
async fn handle_wait_for_commit_impl(
proxy: &CommitStatusProviderProxy,
observer: impl CommitObserver,
) -> Result<(), Error> {
let () = observer.on_event(CommitEvent::Begin);
let commit_fut = wait_for_commit(proxy).fuse();
futures::pin_mut!(commit_fut);
let mut timer_fut = pin!(fasync::Timer::new(WARNING_DURATION).fuse());
let () = futures::select! {
commit_res = commit_fut => commit_res?,
_ = timer_fut => observer.on_event(CommitEvent::Warning),
};
if !commit_fut.is_terminated() {
let () = commit_fut.await.context("while calling wait_for_commit second")?;
}
let () = observer.on_event(CommitEvent::End);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use fidl_fuchsia_update::CommitStatusProviderRequest;
use fuchsia_sync::Mutex;
use futures::pin_mut;
use futures::task::Poll;
use zx::{EventPair, HandleBased, Peered};
struct TestObserver {
events: Mutex<Vec<CommitEvent>>,
}
impl TestObserver {
fn new() -> Self {
Self { events: Mutex::new(vec![]) }
}
fn assert_events(&self, expected_events: &[CommitEvent]) {
assert_eq!(self.events.lock().as_slice(), expected_events);
}
}
impl CommitObserver for &TestObserver {
fn on_event(&self, event: CommitEvent) {
self.events.lock().push(event);
}
}
#[test]
fn test_wait_for_commit() {
let mut executor = fasync::TestExecutor::new_with_fake_time();
let (proxy, mut stream) =
fidl::endpoints::create_proxy_and_stream::<CommitStatusProviderMarker>();
let (p, p_stream) = EventPair::create();
fasync::Task::spawn(async move {
while let Some(req) = stream.try_next().await.unwrap() {
let CommitStatusProviderRequest::IsCurrentSystemCommitted { responder } = req;
let pair = p_stream.duplicate_handle(zx::Rights::BASIC).unwrap();
let () = responder.send(pair).unwrap();
}
})
.detach();
let observer = TestObserver::new();
let fut = handle_wait_for_commit_impl(&proxy, &observer);
pin_mut!(fut);
match executor.run_until_stalled(&mut fut) {
Poll::Ready(res) => panic!("future unexpectedly completed with: {res:?}"),
Poll::Pending => (),
};
observer.assert_events(&[CommitEvent::Begin]);
executor.set_fake_time(fasync::MonotonicInstant::after(
(WARNING_DURATION - Duration::from_secs(1)).into(),
));
assert!(!executor.wake_expired_timers());
match executor.run_until_stalled(&mut fut) {
Poll::Ready(res) => panic!("future unexpectedly completed with: {res:?}"),
Poll::Pending => (),
};
observer.assert_events(&[CommitEvent::Begin]);
executor
.set_fake_time(fasync::MonotonicInstant::after(zx::MonotonicDuration::from_seconds(1)));
assert!(executor.wake_expired_timers());
match executor.run_until_stalled(&mut fut) {
Poll::Ready(res) => panic!("future unexpectedly completed with: {res:?}"),
Poll::Pending => (),
};
observer.assert_events(&[CommitEvent::Begin, CommitEvent::Warning]);
let () = p.signal_peer(zx::Signals::NONE, zx::Signals::USER_0).unwrap();
match executor.run_until_stalled(&mut fut) {
Poll::Ready(res) => res.unwrap(),
Poll::Pending => panic!("future unexpectedly pending"),
};
observer.assert_events(&[CommitEvent::Begin, CommitEvent::Warning, CommitEvent::End]);
}
}