Skip to content

Commit d4096db

Browse files
authored
feat: support download persistent cache task from parents (#950)
Signed-off-by: Gaius <[email protected]>
1 parent 1093318 commit d4096db

File tree

12 files changed

+1249
-217
lines changed

12 files changed

+1249
-217
lines changed

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ dragonfly-client-backend = { path = "dragonfly-client-backend", version = "0.2.5
3030
dragonfly-client-util = { path = "dragonfly-client-util", version = "0.2.5" }
3131
dragonfly-client-init = { path = "dragonfly-client-init", version = "0.2.5" }
3232
thiserror = "1.0"
33-
dragonfly-api = "=2.1.9"
33+
dragonfly-api = "=2.1.12"
3434
reqwest = { version = "0.12.4", features = [
3535
"stream",
3636
"native-tls",

dragonfly-client-storage/src/lib.rs

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,144 @@ impl Storage {
517517
self.metadata.piece_id(task_id, number)
518518
}
519519

520+
/// download_persistent_cache_piece_started updates the metadata of the persistent cache piece and writes
521+
/// the data of piece to file when the persistent cache piece downloads started.
522+
#[instrument(skip_all)]
523+
pub async fn download_persistent_cache_piece_started(
524+
&self,
525+
piece_id: &str,
526+
number: u32,
527+
) -> Result<metadata::Piece> {
528+
// Wait for the piece to be finished.
529+
match self
530+
.wait_for_persistent_cache_piece_finished(piece_id)
531+
.await
532+
{
533+
Ok(piece) => Ok(piece),
534+
// If piece is not found or wait timeout, create piece metadata.
535+
Err(_) => self.metadata.download_piece_started(piece_id, number),
536+
}
537+
}
538+
539+
/// download_persistent_cache_piece_from_parent_finished is used for downloading persistent cache piece from parent.
540+
#[instrument(skip_all)]
541+
pub async fn download_persistent_cache_piece_from_parent_finished<
542+
R: AsyncRead + Unpin + ?Sized,
543+
>(
544+
&self,
545+
piece_id: &str,
546+
task_id: &str,
547+
offset: u64,
548+
expected_digest: &str,
549+
parent_id: &str,
550+
reader: &mut R,
551+
) -> Result<metadata::Piece> {
552+
let response = self
553+
.content
554+
.write_piece_with_crc32_castagnoli(task_id, offset, reader)
555+
.await?;
556+
557+
let length = response.length;
558+
let digest = Digest::new(Algorithm::Crc32, response.hash);
559+
560+
// Check the digest of the piece.
561+
if expected_digest != digest.to_string() {
562+
return Err(Error::DigestMismatch(
563+
expected_digest.to_string(),
564+
digest.to_string(),
565+
));
566+
}
567+
568+
self.metadata.download_piece_finished(
569+
piece_id,
570+
offset,
571+
length,
572+
digest.to_string().as_str(),
573+
Some(parent_id.to_string()),
574+
)
575+
}
576+
577+
/// download_persistent_cache_piece_failed updates the metadata of the persistent cache piece when the persistent cache piece downloads failed.
578+
#[instrument(skip_all)]
579+
pub fn download_persistent_cache_piece_failed(&self, piece_id: &str) -> Result<()> {
580+
self.metadata.download_piece_failed(piece_id)
581+
}
582+
583+
/// upload_persistent_cache_piece updates the metadata of the piece and_then
584+
/// returns the data of the piece.
585+
#[instrument(skip_all)]
586+
pub async fn upload_persistent_cache_piece(
587+
&self,
588+
piece_id: &str,
589+
task_id: &str,
590+
range: Option<Range>,
591+
) -> Result<impl AsyncRead> {
592+
// Wait for the persistent cache piece to be finished.
593+
self.wait_for_persistent_cache_piece_finished(piece_id)
594+
.await?;
595+
596+
// Start uploading the persistent cache task.
597+
self.metadata
598+
.upload_persistent_cache_task_started(task_id)?;
599+
600+
// Get the persistent cache piece metadata and return the content of the persistent cache piece.
601+
match self.metadata.get_piece(piece_id) {
602+
Ok(Some(piece)) => {
603+
match self
604+
.content
605+
.read_piece(task_id, piece.offset, piece.length, range)
606+
.await
607+
{
608+
Ok(reader) => {
609+
// Finish uploading the persistent cache task.
610+
self.metadata
611+
.upload_persistent_cache_task_finished(task_id)?;
612+
Ok(reader)
613+
}
614+
Err(err) => {
615+
// Failed uploading the persistent cache task.
616+
self.metadata.upload_persistent_cache_task_failed(task_id)?;
617+
Err(err)
618+
}
619+
}
620+
}
621+
Ok(None) => {
622+
// Failed uploading the persistent cache task.
623+
self.metadata.upload_persistent_cache_task_failed(task_id)?;
624+
Err(Error::PieceNotFound(piece_id.to_string()))
625+
}
626+
Err(err) => {
627+
// Failed uploading the persistent cache task.
628+
self.metadata.upload_persistent_cache_task_failed(task_id)?;
629+
Err(err)
630+
}
631+
}
632+
}
633+
634+
/// get_persistent_cache_piece returns the persistent cache piece metadata.
635+
#[instrument(skip_all)]
636+
pub fn get_persistent_cache_piece(&self, piece_id: &str) -> Result<Option<metadata::Piece>> {
637+
self.metadata.get_piece(piece_id)
638+
}
639+
640+
/// is_persistent_cache_piece_exists returns whether the persistent cache piece exists.
641+
#[instrument(skip_all)]
642+
pub fn is_persistent_cache_piece_exists(&self, piece_id: &str) -> Result<bool> {
643+
self.metadata.is_piece_exists(piece_id)
644+
}
645+
646+
/// get_persistent_cache_pieces returns the persistent cache piece metadatas.
647+
pub fn get_persistent_cache_pieces(&self, task_id: &str) -> Result<Vec<metadata::Piece>> {
648+
self.metadata.get_pieces(task_id)
649+
}
650+
651+
/// persistent_cache_piece_id returns the persistent cache piece id.
652+
#[inline]
653+
#[instrument(skip_all)]
654+
pub fn persistent_cache_piece_id(&self, task_id: &str, number: u32) -> String {
655+
self.metadata.piece_id(task_id, number)
656+
}
657+
520658
/// wait_for_piece_finished waits for the piece to be finished.
521659
#[instrument(skip_all)]
522660
async fn wait_for_piece_finished(&self, piece_id: &str) -> Result<metadata::Piece> {
@@ -552,4 +690,43 @@ impl Storage {
552690
}
553691
}
554692
}
693+
694+
/// wait_for_persistent_cache_piece_finished waits for the persistent cache piece to be finished.
695+
#[instrument(skip_all)]
696+
async fn wait_for_persistent_cache_piece_finished(
697+
&self,
698+
piece_id: &str,
699+
) -> Result<metadata::Piece> {
700+
// Initialize the timeout of piece.
701+
let piece_timeout = tokio::time::sleep(self.config.download.piece_timeout);
702+
tokio::pin!(piece_timeout);
703+
704+
// Initialize the interval of piece.
705+
let mut wait_for_piece_count = 0;
706+
let mut interval = tokio::time::interval(DEFAULT_WAIT_FOR_PIECE_FINISHED_INTERVAL);
707+
loop {
708+
tokio::select! {
709+
_ = interval.tick() => {
710+
let piece = self
711+
.get_persistent_cache_piece(piece_id)?
712+
.ok_or_else(|| Error::PieceNotFound(piece_id.to_string()))?;
713+
714+
// If the piece is finished, return.
715+
if piece.is_finished() {
716+
debug!("wait piece finished success");
717+
return Ok(piece);
718+
}
719+
720+
if wait_for_piece_count > 0 {
721+
debug!("wait piece finished");
722+
}
723+
wait_for_piece_count += 1;
724+
}
725+
_ = &mut piece_timeout => {
726+
self.metadata.wait_for_piece_finished_failed(piece_id).unwrap_or_else(|err| error!("delete piece metadata failed: {}", err));
727+
return Err(Error::WaitForPieceFinishedTimeout(piece_id.to_string()));
728+
}
729+
}
730+
}
731+
}
555732
}

dragonfly-client-util/src/id_generator/mod.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,15 @@ impl IDGenerator {
153153
hasher.write(application.as_bytes());
154154
}
155155

156-
// Generate the persistent cache task id.
157-
Ok(hasher.finish().to_string())
156+
// Generate the task id by wyhash.
157+
let id = hasher.finish().to_string();
158+
159+
// Generate the persistent cache task ID. The original ID is too short, so we calculate the SHA-256
160+
// hash to ensure it can be prefix-searched by the storage engine.
161+
let mut hasher = Sha256::new();
162+
hasher.update(id);
163+
164+
Ok(hex::encode(hasher.finalize()))
158165
}
159166

160167
/// peer_id generates the peer id.
@@ -263,21 +270,21 @@ mod tests {
263270
"This is a test file",
264271
Some("tag1"),
265272
Some("app1"),
266-
"15288906590529426257",
273+
"ed401a8aa6b9a47b426d2aa01245127d9ac2d1b7974ca866719da59b5456ac4d",
267274
),
268275
(
269276
IDGenerator::new("127.0.0.1".to_string(), "localhost".to_string(), false),
270277
"This is a test file",
271278
None,
272279
Some("app1"),
273-
"16142504978820333826",
280+
"4cbb2c5142f609e98a7d9a887c6404c7432475a52d6c64c52d543b5614a99c63",
274281
),
275282
(
276283
IDGenerator::new("127.0.0.1".to_string(), "localhost".to_string(), false),
277284
"This is a test file",
278285
Some("tag1"),
279286
None,
280-
"3790865882910844849",
287+
"65094f31f9997904f779a27ed0d1ce460c9c4082f214e7626a179f2ea491d34e",
281288
),
282289
];
283290

0 commit comments

Comments
 (0)