|
1 | 1 | use async_trait::async_trait; |
| 2 | +use backoff::future::retry; |
| 3 | +use backoff::ExponentialBackoff; |
| 4 | +use futures::stream::FuturesUnordered; |
| 5 | +use futures::StreamExt; |
2 | 6 | use parity_scale_codec::Decode; |
3 | 7 | use std::collections::BTreeSet; |
4 | 8 | use std::error::Error; |
| 9 | +use std::future::Future; |
| 10 | +use std::pin::Pin; |
5 | 11 | use std::sync::atomic::{AtomicBool, Ordering}; |
6 | 12 | use std::time::Duration; |
7 | 13 | use subspace_core_primitives::{Piece, PieceIndex, PieceIndexHash}; |
8 | 14 | use subspace_farmer_components::plotting::PieceReceiver; |
9 | 15 | use subspace_networking::libp2p::PeerId; |
10 | 16 | use subspace_networking::utils::multihash::MultihashCode; |
11 | 17 | use subspace_networking::{Node, PieceByHashRequest, PieceKey, ToMultihash}; |
12 | | -use tokio::time::sleep; |
| 18 | +use tokio::time::{sleep, timeout}; |
13 | 19 | use tracing::{debug, error, info, trace, warn}; |
14 | 20 |
|
15 | | -/// Defines a duration between get_piece calls. |
16 | | -const GET_PIECE_WAITING_DURATION_IN_SECS: u64 = 1; |
| 21 | +/// Defines initial duration between get_piece calls. |
| 22 | +const GET_PIECE_INITIAL_INTERVAL: Duration = Duration::from_secs(1); |
| 23 | +/// Defines max duration between get_piece calls. |
| 24 | +const GET_PIECE_MAX_INTERVAL: Duration = Duration::from_secs(5); |
| 25 | +/// Delay for getting piece from cache before resorting to archival storage |
| 26 | +const GET_PIECE_ARCHIVAL_STORAGE_DELAY: Duration = Duration::from_secs(1); |
| 27 | +/// Max time allocated for getting piece from DSN before attempt is considered to fail |
| 28 | +const GET_PIECE_TIMEOUT: Duration = Duration::from_secs(5); |
17 | 29 |
|
18 | 30 | // Temporary struct serving pieces from different providers using configuration arguments. |
19 | 31 | pub(crate) struct MultiChannelPieceReceiver<'a> { |
@@ -181,21 +193,49 @@ impl<'a> PieceReceiver for MultiChannelPieceReceiver<'a> { |
181 | 193 | ) -> Result<Option<Piece>, Box<dyn Error + Send + Sync + 'static>> { |
182 | 194 | trace!(%piece_index, "Piece request."); |
183 | 195 |
|
184 | | - // until we get a valid piece |
185 | | - loop { |
186 | | - self.check_cancellation()?; |
187 | | - |
188 | | - if let Some(piece) = self.get_piece_from_cache(piece_index).await { |
189 | | - return Ok(Some(piece)); |
190 | | - } |
191 | | - |
192 | | - if let Some(piece) = self.get_piece_from_archival_storage(piece_index).await { |
193 | | - return Ok(Some(piece)); |
| 196 | + let backoff = ExponentialBackoff { |
| 197 | + initial_interval: GET_PIECE_INITIAL_INTERVAL, |
| 198 | + max_interval: GET_PIECE_MAX_INTERVAL, |
| 199 | + // Try until we get a valid piece |
| 200 | + max_elapsed_time: None, |
| 201 | + ..ExponentialBackoff::default() |
| 202 | + }; |
| 203 | + |
| 204 | + retry(backoff, || async { |
| 205 | + self.check_cancellation() |
| 206 | + .map_err(backoff::Error::Permanent)?; |
| 207 | + |
| 208 | + // Try to pull pieces in two ways, whichever is faster |
| 209 | + let mut piece_attempts = [ |
| 210 | + timeout( |
| 211 | + GET_PIECE_TIMEOUT, |
| 212 | + Box::pin(self.get_piece_from_cache(piece_index)) |
| 213 | + as Pin<Box<dyn Future<Output = _> + Send>>, |
| 214 | + ), |
| 215 | + timeout( |
| 216 | + GET_PIECE_TIMEOUT, |
| 217 | + Box::pin(async { |
| 218 | + // Prefer cache if it can return quickly, otherwise fall back to archival storage |
| 219 | + sleep(GET_PIECE_ARCHIVAL_STORAGE_DELAY).await; |
| 220 | + self.get_piece_from_archival_storage(piece_index).await |
| 221 | + }) as Pin<Box<dyn Future<Output = _> + Send>>, |
| 222 | + ), |
| 223 | + ] |
| 224 | + .into_iter() |
| 225 | + .collect::<FuturesUnordered<_>>(); |
| 226 | + |
| 227 | + while let Some(maybe_piece) = piece_attempts.next().await { |
| 228 | + if let Ok(Some(piece)) = maybe_piece { |
| 229 | + return Ok(Some(piece)); |
| 230 | + } |
194 | 231 | } |
195 | 232 |
|
196 | | - warn!(%piece_index, "Couldn't get a piece from DSN. Starting a new attempt..."); |
| 233 | + warn!(%piece_index, "Couldn't get a piece from DSN. Retrying..."); |
197 | 234 |
|
198 | | - sleep(Duration::from_secs(GET_PIECE_WAITING_DURATION_IN_SECS)).await; |
199 | | - } |
| 235 | + Err(backoff::Error::transient( |
| 236 | + "Couldn't get piece from DSN".into(), |
| 237 | + )) |
| 238 | + }) |
| 239 | + .await |
200 | 240 | } |
201 | 241 | } |
0 commit comments