fidl_fuchsia_pkg_rewrite_ext/
transaction.rs1use crate::errors::EditTransactionError;
6use crate::rule::Rule;
7use flex_client::ProxyHasDomain;
8use flex_fuchsia_pkg_rewrite as rewrite;
9use flex_fuchsia_pkg_rewrite::{EditTransactionProxy, EngineProxy};
10use std::future::Future;
11use zx_status as zx;
12
13const RETRY_ATTEMPTS: usize = 100;
14
15pub struct EditTransaction {
17 transaction: EditTransactionProxy,
18}
19
20impl EditTransaction {
21 pub fn reset_all(&self) -> Result<(), EditTransactionError> {
24 self.transaction.reset_all().map_err(EditTransactionError::from)
25 }
26
27 pub async fn list_dynamic(&self) -> Result<Vec<Rule>, EditTransactionError> {
31 let (iter, iter_server_end) =
32 self.transaction.domain().create_proxy::<rewrite::RuleIteratorMarker>();
33 self.transaction.list_dynamic(iter_server_end)?;
34
35 let mut rules = Vec::new();
36 loop {
37 let chunk = iter.next().await?;
38 if chunk.is_empty() {
39 break;
40 }
41
42 for rule in chunk {
43 rules.push(Rule::try_from(rule)?);
44 }
45 }
46
47 Ok(rules)
48 }
49
50 pub async fn add(&self, rule: Rule) -> Result<(), EditTransactionError> {
53 self.transaction
54 .add(&rule.into())
55 .await?
56 .map_err(|err| EditTransactionError::AddError(zx::Status::from_raw(err)))
57 }
58}
59
60pub async fn do_transaction<T, R>(engine: &EngineProxy, cb: T) -> Result<(), EditTransactionError>
67where
68 T: Fn(EditTransaction) -> R,
69 R: Future<Output = Result<EditTransaction, EditTransactionError>>,
70{
71 for _ in 0..RETRY_ATTEMPTS {
73 let (transaction, transaction_server_end) =
74 engine.domain().create_proxy::<rewrite::EditTransactionMarker>();
75
76 let () = engine
77 .start_edit_transaction(transaction_server_end)
78 .map_err(EditTransactionError::from)?;
79
80 let transaction = cb(EditTransaction { transaction }).await?;
81
82 let response =
83 transaction.transaction.commit().await.map_err(EditTransactionError::from)?;
84
85 return match response.map_err(zx::Status::from_raw) {
87 Ok(()) => Ok(()),
88 Err(zx::Status::UNAVAILABLE) => {
89 continue;
90 }
91 Err(status) => Err(EditTransactionError::CommitError(status)),
92 };
93 }
94
95 Err(EditTransactionError::CommitError(zx::Status::UNAVAILABLE))
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101 use assert_matches::assert_matches;
102 use flex_fuchsia_pkg_rewrite::{
103 EditTransactionRequest, EngineMarker, EngineRequest, RuleIteratorRequest,
104 };
105 use fuchsia_async as fasync;
106 use futures::TryStreamExt;
107 use std::sync::atomic::{AtomicUsize, Ordering};
108 use std::sync::{Arc, Mutex};
109
110 #[derive(Debug, PartialEq)]
111 enum Event {
112 ResetAll,
113 ListDynamic,
114 IteratorNext,
115 Add(Rule),
116 CommitFailed,
117 Commit,
118 }
119
120 struct Engine {
121 engine: EngineProxy,
122 events: Arc<Mutex<Vec<Event>>>,
123 #[cfg(feature = "fdomain")]
124 _client: Arc<flex_client::Client>,
125 }
126
127 macro_rules! rule {
128 ($host_match:expr => $host_replacement:expr,
129 $path_prefix_match:expr => $path_prefix_replacement:expr) => {
130 Rule::new($host_match, $host_replacement, $path_prefix_match, $path_prefix_replacement)
131 .unwrap()
132 };
133 }
134
135 impl Engine {
136 fn new() -> Self {
137 Self::with_fail_attempts(0, zx::Status::OK)
138 }
139
140 fn with_fail_attempts(mut fail_attempts: usize, fail_status: zx::Status) -> Self {
141 #[cfg(feature = "fdomain")]
142 let client = fdomain_local::local_client_empty();
143 #[cfg(not(feature = "fdomain"))]
144 let client = flex_client::fidl::ZirconClient;
145 let events = Arc::new(Mutex::new(Vec::new()));
146 let events_task = Arc::clone(&events);
147
148 let (engine, mut engine_stream) = client.create_proxy_and_stream::<EngineMarker>();
149
150 fasync::Task::local(async move {
151 while let Some(req) = engine_stream.try_next().await.unwrap() {
152 match req {
153 EngineRequest::StartEditTransaction { transaction, control_handle: _ } => {
154 let mut tx_stream = transaction.into_stream();
155
156 while let Some(req) = tx_stream.try_next().await.unwrap() {
157 match req {
158 EditTransactionRequest::ResetAll { control_handle: _ } => {
159 events_task.lock().unwrap().push(Event::ResetAll);
160 }
161 EditTransactionRequest::ListDynamic {
162 iterator,
163 control_handle: _,
164 } => {
165 events_task.lock().unwrap().push(Event::ListDynamic);
166 let mut stream = iterator.into_stream();
167
168 let mut rules = vec![
169 rule!("fuchsia.com" => "example.com", "/" => "/"),
170 rule!("fuchsia.com" => "mycorp.com", "/" => "/"),
171 ]
172 .into_iter();
173
174 while let Some(req) = stream.try_next().await.unwrap() {
175 let RuleIteratorRequest::Next { responder } = req;
176 events_task.lock().unwrap().push(Event::IteratorNext);
177
178 if let Some(rule) = rules.next() {
179 responder.send(&[rule.into()]).unwrap();
180 } else {
181 responder.send(&[]).unwrap();
182 }
183 }
184 }
185 EditTransactionRequest::Add { rule, responder } => {
186 events_task
187 .lock()
188 .unwrap()
189 .push(Event::Add(rule.try_into().unwrap()));
190 responder.send(Ok(())).unwrap();
191 }
192 EditTransactionRequest::Commit { responder } => {
193 if fail_attempts > 0 {
194 fail_attempts -= 1;
195 events_task.lock().unwrap().push(Event::CommitFailed);
196 responder.send(Err(fail_status.into_raw())).unwrap();
197 } else {
198 events_task.lock().unwrap().push(Event::Commit);
199 responder.send(Ok(())).unwrap();
200 }
201 }
202 }
203 }
204 }
205 _ => {
206 panic!("unexpected reqest: {:?}", req);
207 }
208 }
209 }
210 })
211 .detach();
212
213 Self {
214 engine,
215 events,
216 #[cfg(feature = "fdomain")]
217 _client: client,
218 }
219 }
220
221 fn take_events(&self) -> Vec<Event> {
222 self.events.lock().unwrap().drain(..).collect()
223 }
224 }
225
226 #[fuchsia::test]
227 async fn test_do_transaction_empty_always_commits() {
228 let engine = Engine::new();
229
230 do_transaction(&engine.engine, |transaction| async { Ok(transaction) }).await.unwrap();
231
232 assert_eq!(engine.take_events(), vec![Event::Commit]);
233 }
234
235 #[fuchsia::test]
236 async fn test_do_transaction_reset_all() {
237 let engine = Engine::new();
238
239 do_transaction(&engine.engine, |transaction| async {
240 transaction.reset_all()?;
241 Ok(transaction)
242 })
243 .await
244 .unwrap();
245
246 assert_eq!(engine.take_events(), vec![Event::ResetAll, Event::Commit]);
247 }
248
249 #[fuchsia::test]
250 async fn test_do_transaction_list_dynamic() {
251 let engine = Engine::new();
252
253 do_transaction(&engine.engine, |transaction| async {
254 let rules = transaction.list_dynamic().await?;
255 assert_eq!(
256 rules,
257 vec![
258 rule!("fuchsia.com" => "example.com", "/" => "/"),
259 rule!("fuchsia.com" => "mycorp.com", "/" => "/"),
260 ]
261 );
262 Ok(transaction)
263 })
264 .await
265 .unwrap();
266
267 assert_eq!(
268 engine.take_events(),
269 vec![
271 Event::ListDynamic,
272 Event::IteratorNext,
273 Event::IteratorNext,
274 Event::IteratorNext,
275 Event::Commit
276 ]
277 );
278 }
279
280 #[fuchsia::test]
281 async fn test_do_transaction_add() {
282 let engine = Engine::new();
283
284 let attempts = Arc::new(AtomicUsize::new(0));
285 do_transaction(&engine.engine, |transaction| async {
286 attempts.fetch_add(1, Ordering::SeqCst);
287 transaction.add(rule!("foo.com" => "bar.com", "/" => "/")).await?;
288 transaction.add(rule!("baz.com" => "boo.com", "/" => "/")).await?;
289 Ok(transaction)
290 })
291 .await
292 .unwrap();
293
294 assert_eq!(attempts.load(Ordering::SeqCst), 1);
295 assert_eq!(
296 engine.take_events(),
297 vec![
298 Event::Add(rule!("foo.com" => "bar.com", "/" => "/")),
299 Event::Add(rule!("baz.com" => "boo.com", "/" => "/")),
300 Event::Commit,
301 ],
302 );
303 }
304
305 #[fuchsia::test]
306 async fn test_do_transaction_closure_error_does_not_commit() {
307 let engine = Engine::new();
308
309 let attempts = Arc::new(AtomicUsize::new(0));
310 let err = do_transaction(&engine.engine, |_transaction| async {
311 attempts.fetch_add(1, Ordering::SeqCst);
312 Err(EditTransactionError::AddError(zx::Status::INTERNAL))
313 })
314 .await
315 .unwrap_err();
316
317 assert_eq!(attempts.load(Ordering::SeqCst), 1);
318 assert_matches!(err, EditTransactionError::AddError(zx::Status::INTERNAL));
319 assert_eq!(engine.take_events(), vec![]);
320 }
321
322 #[fuchsia::test]
323 async fn test_do_transaction_retries_commit_errors() {
324 let engine = Engine::with_fail_attempts(5, zx::Status::UNAVAILABLE);
325
326 let attempts = Arc::new(AtomicUsize::new(0));
327 do_transaction(&engine.engine, |transaction| async {
328 attempts.fetch_add(1, Ordering::SeqCst);
329 Ok(transaction)
330 })
331 .await
332 .unwrap();
333
334 assert_eq!(attempts.load(Ordering::SeqCst), 6);
335 assert_eq!(
336 engine.take_events(),
337 vec![
338 Event::CommitFailed,
339 Event::CommitFailed,
340 Event::CommitFailed,
341 Event::CommitFailed,
342 Event::CommitFailed,
343 Event::Commit,
344 ]
345 );
346 }
347
348 #[fuchsia::test]
349 async fn test_do_transaction_eventually_gives_up() {
350 let engine = Engine::with_fail_attempts(RETRY_ATTEMPTS + 1, zx::Status::UNAVAILABLE);
351
352 let attempts = Arc::new(AtomicUsize::new(0));
353 let err = do_transaction(&engine.engine, |transaction| async {
354 attempts.fetch_add(1, Ordering::SeqCst);
355 Ok(transaction)
356 })
357 .await
358 .unwrap_err();
359
360 assert_eq!(attempts.load(Ordering::SeqCst), RETRY_ATTEMPTS);
361 assert_matches!(err, EditTransactionError::CommitError(zx::Status::UNAVAILABLE));
362 assert_eq!(
363 engine.take_events(),
364 (0..RETRY_ATTEMPTS).map(|_| Event::CommitFailed).collect::<Vec<_>>(),
365 );
366 }
367
368 #[fuchsia::test]
369 async fn test_do_transaction_does_not_retry_other_errors() {
370 let engine = Engine::with_fail_attempts(5, zx::Status::INTERNAL);
371
372 let attempts = Arc::new(AtomicUsize::new(0));
373 let err = do_transaction(&engine.engine, |transaction| async {
374 attempts.fetch_add(1, Ordering::SeqCst);
375 Ok(transaction)
376 })
377 .await
378 .unwrap_err();
379
380 assert_eq!(attempts.load(Ordering::SeqCst), 1);
381 assert_matches!(err, EditTransactionError::CommitError(zx::Status::INTERNAL));
382 assert_eq!(engine.take_events(), vec![Event::CommitFailed,]);
383 }
384}