hopr_strategy/
strategy.rs1use std::fmt::{Debug, Display, Formatter};
11
12use async_trait::async_trait;
13use tracing::warn;
14
15use crate::errors::Result;
16
17#[async_trait]
26pub trait Strategy: Display + Send {
27 async fn run(&mut self) -> Result<()>;
29}
30
31pub struct MultiStrategy {
37 strategies: Vec<Box<dyn Strategy + Send>>,
38}
39
40impl MultiStrategy {
41 pub fn new(strategies: Vec<Box<dyn Strategy + Send>>) -> Self {
47 Self { strategies }
48 }
49}
50
51impl Debug for MultiStrategy {
52 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
53 write!(f, "MultiStrategy({} sub-strategies)", self.strategies.len())
54 }
55}
56
57impl Display for MultiStrategy {
58 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
59 let names: Vec<String> = self.strategies.iter().map(|s| s.to_string()).collect();
60 if names.is_empty() {
61 write!(f, "multi_strategy(passive)")
62 } else {
63 write!(f, "multi_strategy({})", names.join(", "))
64 }
65 }
66}
67
68#[async_trait]
69impl Strategy for MultiStrategy {
70 async fn run(&mut self) -> Result<()> {
71 use futures::StreamExt as _;
72 use hopr_utils::runtime::prelude::{AbortHandle, abortable, spawn};
73
74 let strategies = std::mem::take(&mut self.strategies);
75
76 if strategies.is_empty() {
77 futures::future::pending::<()>().await;
79 return Ok(());
80 }
81
82 let mut join_handles = Vec::new();
86 let mut abort_handles: Vec<AbortHandle> = Vec::new();
87 for mut s in strategies {
88 let proc = hopr_utils::runtime::diagnostics::instrument(
89 async move { s.run().await },
90 "multi_strategy_sub_task",
91 module_path!(),
92 file!(),
93 line!(),
94 );
95 let (proc, abort_handle) = abortable(proc);
96 join_handles.push(spawn(proc));
97 abort_handles.push(abort_handle);
98 }
99
100 struct AbortGuard(Vec<AbortHandle>);
101 impl Drop for AbortGuard {
102 fn drop(&mut self) {
103 for h in &self.0 {
104 h.abort();
105 }
106 }
107 }
108 let _guard = AbortGuard(abort_handles);
109
110 let mut pending: futures::stream::FuturesUnordered<_> = join_handles.into_iter().collect();
113
114 while let Some(join_result) = pending.next().await {
115 let strategy_result = match join_result {
116 Err(e) => Err(crate::errors::StrategyError::Other(e.into())),
117 Ok(Ok(result)) => result,
118 Ok(Err(_aborted)) => continue, };
120
121 if let Err(e) = strategy_result {
122 warn!(%e, "sub-strategy failed");
123 }
124 }
125
126 Ok(())
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use std::fmt::{Display, Formatter};
133
134 use super::*;
135 use crate::errors::StrategyError;
136
137 struct OkStrategy;
138 impl Display for OkStrategy {
139 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
140 write!(f, "ok")
141 }
142 }
143 #[async_trait]
144 impl Strategy for OkStrategy {
145 async fn run(&mut self) -> Result<()> {
146 Ok(())
147 }
148 }
149
150 struct FailStrategy;
151 impl Display for FailStrategy {
152 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
153 write!(f, "fail")
154 }
155 }
156 #[async_trait]
157 impl Strategy for FailStrategy {
158 async fn run(&mut self) -> Result<()> {
159 Err(StrategyError::Other(anyhow::anyhow!("error")))
160 }
161 }
162
163 struct ExternalStrategy {
165 ran: bool,
166 }
167 impl Display for ExternalStrategy {
168 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
169 write!(f, "external")
170 }
171 }
172 #[async_trait]
173 impl Strategy for ExternalStrategy {
174 async fn run(&mut self) -> Result<()> {
175 self.ran = true;
176 Ok(())
177 }
178 }
179
180 #[tokio::test]
181 async fn test_multi_strategy_sub_failure_does_not_propagate() -> anyhow::Result<()> {
182 let mut ms = MultiStrategy::new(vec![Box::new(FailStrategy), Box::new(OkStrategy)]);
184 ms.run().await?;
185 Ok(())
186 }
187
188 #[tokio::test]
189 async fn test_multi_strategy_accepts_external_strategy() -> anyhow::Result<()> {
190 let mut ms = MultiStrategy::new(vec![Box::new(OkStrategy), Box::new(ExternalStrategy { ran: false })]);
192 ms.run().await?;
193 Ok(())
194 }
195
196 #[tokio::test]
197 async fn test_multi_strategy_empty_is_passive() {
198 let mut ms = MultiStrategy::new(vec![]);
200 let result =
201 futures_time::future::FutureExt::timeout(ms.run(), futures_time::time::Duration::from_millis(50)).await;
202 assert!(result.is_err(), "empty MultiStrategy should block (timeout expected)");
203 }
204
205 #[test]
206 fn test_multi_strategy_display() {
207 let ms = MultiStrategy::new(vec![Box::new(OkStrategy), Box::new(FailStrategy)]);
208 assert_eq!(ms.to_string(), "multi_strategy(ok, fail)");
209 }
210
211 #[test]
212 fn test_multi_strategy_display_passive() {
213 let ms = MultiStrategy::new(vec![]);
214 assert_eq!(ms.to_string(), "multi_strategy(passive)");
215 }
216}