-
Notifications
You must be signed in to change notification settings - Fork 248
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fetch multiple pieces during object reconstruction #3158
Changes from all commits
74a30fe
01750d2
170588b
ab23ed3
d163158
d12935d
baff7b7
b9150af
0755013
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,17 +2,19 @@ | |
|
||
use async_trait::async_trait; | ||
use futures::stream::StreamExt; | ||
use futures::{FutureExt, Stream}; | ||
use std::fmt; | ||
use std::ops::{Deref, DerefMut}; | ||
use std::ops::Deref; | ||
use std::sync::Arc; | ||
use subspace_core_primitives::pieces::{Piece, PieceIndex}; | ||
use subspace_data_retrieval::piece_getter::{BoxError, ObjectPieceGetter}; | ||
use subspace_data_retrieval::piece_getter::ObjectPieceGetter; | ||
use subspace_networking::utils::piece_provider::{PieceProvider, PieceValidator}; | ||
|
||
/// The maximum number of peer-to-peer walking rounds for L1 archival storage. | ||
const MAX_RANDOM_WALK_ROUNDS: usize = 15; | ||
|
||
/// Wrapper type for PieceProvider, so it can implement ObjectPieceGetter. | ||
pub struct DsnPieceGetter<PV: PieceValidator>(pub PieceProvider<PV>); | ||
pub struct DsnPieceGetter<PV: PieceValidator>(pub Arc<PieceProvider<PV>>); | ||
|
||
impl<PV> fmt::Debug for DsnPieceGetter<PV> | ||
where | ||
|
@@ -25,35 +27,35 @@ where | |
} | ||
} | ||
|
||
impl<PV> Deref for DsnPieceGetter<PV> | ||
impl<PV> Clone for DsnPieceGetter<PV> | ||
where | ||
PV: PieceValidator, | ||
{ | ||
type Target = PieceProvider<PV>; | ||
|
||
fn deref(&self) -> &Self::Target { | ||
&self.0 | ||
fn clone(&self) -> Self { | ||
Self(self.0.clone()) | ||
} | ||
} | ||
|
||
impl<PV> DerefMut for DsnPieceGetter<PV> | ||
impl<PV> Deref for DsnPieceGetter<PV> | ||
where | ||
PV: PieceValidator, | ||
{ | ||
fn deref_mut(&mut self) -> &mut Self::Target { | ||
&mut self.0 | ||
type Target = PieceProvider<PV>; | ||
|
||
fn deref(&self) -> &Self::Target { | ||
&self.0 | ||
} | ||
} | ||
|
||
// TODO: | ||
// - change ObjectPieceGetter trait to take a list of piece indexes | ||
// - reconstruct segment if piece is missing | ||
// - move this piece getter impl into a new library part of this crate | ||
#[async_trait] | ||
impl<PV> ObjectPieceGetter for DsnPieceGetter<PV> | ||
where | ||
PV: PieceValidator, | ||
{ | ||
async fn get_piece(&self, piece_index: PieceIndex) -> Result<Option<Piece>, BoxError> { | ||
async fn get_piece(&self, piece_index: PieceIndex) -> anyhow::Result<Option<Piece>> { | ||
if let Some((got_piece_index, maybe_piece)) = | ||
self.get_from_cache([piece_index]).await.next().await | ||
{ | ||
|
@@ -68,6 +70,38 @@ where | |
.get_piece_from_archival_storage(piece_index, MAX_RANDOM_WALK_ROUNDS) | ||
.await) | ||
Comment on lines
70
to
71
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, random walking is a cold storage fallback fr cases when a piece is not cached, in that case it is wandering around the network hoping to stumble upon someone storing a piece in their plot rather than cache |
||
} | ||
|
||
async fn get_pieces<'a, PieceIndices>( | ||
&'a self, | ||
piece_indices: PieceIndices, | ||
) -> anyhow::Result< | ||
Box<dyn Stream<Item = (PieceIndex, anyhow::Result<Option<Piece>>)> + Send + Unpin + 'a>, | ||
> | ||
where | ||
PieceIndices: IntoIterator<Item = PieceIndex, IntoIter: Send> + Send + 'a, | ||
{ | ||
let piece_getter = (*self).clone(); | ||
|
||
let stream = self | ||
.get_from_cache(piece_indices) | ||
.await | ||
.then(move |(index, maybe_piece)| { | ||
let piece_getter = piece_getter.clone(); | ||
let fut = async move { | ||
if let Some(piece) = maybe_piece { | ||
return (index, Ok(Some(piece))); | ||
} | ||
|
||
piece_getter | ||
.get_piece_from_archival_storage(index, MAX_RANDOM_WALK_ROUNDS) | ||
.map(|piece| (index, Ok(piece))) | ||
.await | ||
}; | ||
Box::pin(fut) | ||
}); | ||
|
||
Ok(Box::new(stream)) | ||
} | ||
} | ||
|
||
impl<PV> DsnPieceGetter<PV> | ||
|
@@ -76,6 +110,6 @@ where | |
{ | ||
/// Creates new DSN piece getter. | ||
pub fn new(piece_provider: PieceProvider<PV>) -> Self { | ||
Self(piece_provider) | ||
Self(Arc::new(piece_provider)) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,9 +16,8 @@ | |
//! Fetching pieces of the archived history of Subspace Network. | ||
|
||
use crate::object_fetcher::Error; | ||
use crate::piece_getter::{BoxError, ObjectPieceGetter}; | ||
use futures::stream::FuturesOrdered; | ||
use futures::TryStreamExt; | ||
use crate::piece_getter::ObjectPieceGetter; | ||
use futures::StreamExt; | ||
use subspace_core_primitives::pieces::{Piece, PieceIndex}; | ||
use tracing::{debug, trace}; | ||
|
||
|
@@ -31,7 +30,7 @@ use tracing::{debug, trace}; | |
pub async fn download_pieces<PG>( | ||
piece_indexes: &[PieceIndex], | ||
piece_getter: &PG, | ||
) -> Result<Vec<Piece>, BoxError> | ||
) -> anyhow::Result<Vec<Piece>> | ||
where | ||
PG: ObjectPieceGetter, | ||
{ | ||
|
@@ -42,46 +41,31 @@ where | |
); | ||
|
||
// TODO: | ||
// - consider using a semaphore to limit the number of concurrent requests, like | ||
// download_segment_pieces() | ||
// - if we're close to the number of pieces in a segment, use segment downloading and piece | ||
// - if we're close to the number of pieces in a segment, or we can't find a piece, use segment downloading and piece | ||
// reconstruction instead | ||
// Currently most objects are limited to 4 pieces, so this isn't needed yet. | ||
let received_pieces = piece_indexes | ||
.iter() | ||
.map(|piece_index| async move { | ||
match piece_getter.get_piece(*piece_index).await { | ||
Ok(Some(piece)) => { | ||
trace!(?piece_index, "Piece request succeeded",); | ||
Ok(piece) | ||
} | ||
Ok(None) => { | ||
trace!(?piece_index, "Piece not found"); | ||
Err(Error::PieceNotFound { | ||
piece_index: *piece_index, | ||
} | ||
.into()) | ||
} | ||
Err(error) => { | ||
trace!( | ||
%error, | ||
?piece_index, | ||
"Piece request caused an error", | ||
); | ||
Err(error) | ||
} | ||
} | ||
}) | ||
.collect::<FuturesOrdered<_>>(); | ||
let mut received_pieces = piece_getter | ||
.get_pieces(piece_indexes.iter().copied()) | ||
.await?; | ||
|
||
// We want exact pieces, so any errors are fatal. | ||
let received_pieces: Vec<Piece> = received_pieces.try_collect().await?; | ||
let mut pieces = Vec::new(); | ||
pieces.resize(piece_indexes.len(), Piece::default()); | ||
Comment on lines
+51
to
+52
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will work, but it is a bit unfortunate that we'll be allocating so much upfront (each piece) and then simply throwing those allocations away. I'd probably push all results into |
||
|
||
while let Some((piece_index, maybe_piece)) = received_pieces.next().await { | ||
// We want exact pieces, so any errors are fatal. | ||
let piece = maybe_piece?.ok_or(Error::PieceNotFound { piece_index })?; | ||
let index_position = piece_indexes | ||
.iter() | ||
.position(|i| *i == piece_index) | ||
.expect("get_pieces only returns indexes it was supplied; qed"); | ||
pieces[index_position] = piece; | ||
} | ||
|
||
trace!( | ||
count = piece_indexes.len(), | ||
?piece_indexes, | ||
"Successfully retrieved exact pieces" | ||
); | ||
|
||
Ok(received_pieces) | ||
Ok(pieces) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This puzzled me for a moment, this is the first time I saw
From
used instead ofArc::new()
🤔