1use anyhow::{Context, Error};
6use fidl_fuchsia_update::{CommitStatusProviderMarker, CommitStatusProviderProxy};
7use fuchsia_async as fasync;
8use fuchsia_component::client::connect_to_protocol;
9use futures::future::FusedFuture;
10use futures::prelude::*;
11use std::pin::pin;
12use std::time::Duration;
13
14const WARNING_DURATION: Duration = Duration::from_secs(30);
15
16pub async fn handle_wait_for_commit() -> Result<(), Error> {
18 let proxy = connect_to_protocol::<CommitStatusProviderMarker>()
19 .context("while connecting to fuchsia.update/CommitStatusProvider")?;
20 handle_wait_for_commit_impl(&proxy, Printer).await
21}
22
23#[derive(Debug, PartialEq)]
25enum CommitEvent {
26 Begin,
27 Warning,
28 End,
29}
30
31trait CommitObserver {
33 fn on_event(&self, event: CommitEvent);
34}
35
36struct Printer;
38impl CommitObserver for Printer {
39 fn on_event(&self, event: CommitEvent) {
40 let text = match event {
41 CommitEvent::Begin => "Waiting for commit.",
42 CommitEvent::Warning => {
43 "It's been 30 seconds. Something is probably wrong. Consider \
44 running `update revert` to fall back to the previous slot."
45 }
46 CommitEvent::End => "Committed!",
47 };
48 println!("{text}");
49 }
50}
51
52async fn wait_for_commit(proxy: &CommitStatusProviderProxy) -> Result<(), Error> {
54 let p = proxy.is_current_system_committed().await.context("while obtaining EventPair")?;
55 fasync::OnSignals::new(&p, zx::Signals::USER_0)
56 .await
57 .context("while waiting for the commit")?;
58 Ok(())
59}
60
61async fn handle_wait_for_commit_impl(
67 proxy: &CommitStatusProviderProxy,
68 observer: impl CommitObserver,
69) -> Result<(), Error> {
70 let () = observer.on_event(CommitEvent::Begin);
71
72 let commit_fut = wait_for_commit(proxy).fuse();
73 futures::pin_mut!(commit_fut);
74 let mut timer_fut = pin!(fasync::Timer::new(WARNING_DURATION).fuse());
75
76 let () = futures::select! {
78 commit_res = commit_fut => commit_res?,
79 _ = timer_fut => observer.on_event(CommitEvent::Warning),
80 };
81
82 if !commit_fut.is_terminated() {
84 let () = commit_fut.await.context("while calling wait_for_commit second")?;
85 }
86
87 let () = observer.on_event(CommitEvent::End);
88 Ok(())
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94 use fidl_fuchsia_update::CommitStatusProviderRequest;
95 use fuchsia_sync::Mutex;
96 use futures::pin_mut;
97 use futures::task::Poll;
98 use zx::{EventPair, HandleBased, Peered};
99
100 struct TestObserver {
101 events: Mutex<Vec<CommitEvent>>,
102 }
103 impl TestObserver {
104 fn new() -> Self {
105 Self { events: Mutex::new(vec![]) }
106 }
107 fn assert_events(&self, expected_events: &[CommitEvent]) {
108 assert_eq!(self.events.lock().as_slice(), expected_events);
109 }
110 }
111 impl CommitObserver for &TestObserver {
112 fn on_event(&self, event: CommitEvent) {
113 self.events.lock().push(event);
114 }
115 }
116
117 #[test]
118 fn test_wait_for_commit() {
119 let mut executor = fasync::TestExecutor::new_with_fake_time();
120
121 let (proxy, mut stream) =
122 fidl::endpoints::create_proxy_and_stream::<CommitStatusProviderMarker>();
123 let (p, p_stream) = EventPair::create();
124 fasync::Task::spawn(async move {
125 while let Some(req) = stream.try_next().await.unwrap() {
126 let CommitStatusProviderRequest::IsCurrentSystemCommitted { responder } = req;
127 let pair = p_stream.duplicate_handle(zx::Rights::BASIC).unwrap();
128 let () = responder.send(pair).unwrap();
129 }
130 })
131 .detach();
132
133 let observer = TestObserver::new();
134
135 let fut = handle_wait_for_commit_impl(&proxy, &observer);
136 pin_mut!(fut);
137
138 match executor.run_until_stalled(&mut fut) {
140 Poll::Ready(res) => panic!("future unexpectedly completed with: {res:?}"),
141 Poll::Pending => (),
142 };
143 observer.assert_events(&[CommitEvent::Begin]);
144
145 executor.set_fake_time(fasync::MonotonicInstant::after(
148 (WARNING_DURATION - Duration::from_secs(1)).into(),
149 ));
150 assert!(!executor.wake_expired_timers());
151 match executor.run_until_stalled(&mut fut) {
152 Poll::Ready(res) => panic!("future unexpectedly completed with: {res:?}"),
153 Poll::Pending => (),
154 };
155 observer.assert_events(&[CommitEvent::Begin]);
156
157 executor
159 .set_fake_time(fasync::MonotonicInstant::after(zx::MonotonicDuration::from_seconds(1)));
160 assert!(executor.wake_expired_timers());
161 match executor.run_until_stalled(&mut fut) {
162 Poll::Ready(res) => panic!("future unexpectedly completed with: {res:?}"),
163 Poll::Pending => (),
164 };
165 observer.assert_events(&[CommitEvent::Begin, CommitEvent::Warning]);
166
167 let () = p.signal_peer(zx::Signals::NONE, zx::Signals::USER_0).unwrap();
169 match executor.run_until_stalled(&mut fut) {
170 Poll::Ready(res) => res.unwrap(),
171 Poll::Pending => panic!("future unexpectedly pending"),
172 };
173 observer.assert_events(&[CommitEvent::Begin, CommitEvent::Warning, CommitEvent::End]);
174 }
175}