Skip to content

Commit 31d5bff

Browse files
committed
fix: remove partial blob output
1 parent 252f7cb commit 31d5bff

4 files changed

Lines changed: 63 additions & 8 deletions

File tree

blob/src/bao_tree.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,8 @@ impl AsyncStreamReader for RecvStreamWrapper<'_> {
151151
// ----- FuturesAsyncReader/opendal Writer impls for bao_tree ----------
152152
pub struct OpenDalWriter {
153153
writer: opendal::Writer,
154+
operator: Operator,
155+
storage_path: String,
154156
pub hasher: Hasher,
155157
idle_timeout: Duration,
156158
}
@@ -208,6 +210,8 @@ impl OpenDalWriter {
208210
) -> Result<Self, BlobLibError> {
209211
Ok(Self {
210212
writer: operator.writer(storage_path).await?,
213+
operator: operator.clone(),
214+
storage_path: storage_path.to_string(),
211215
hasher: Hasher::new(),
212216
idle_timeout,
213217
})
@@ -227,14 +231,14 @@ impl OpenDalWriter {
227231
)
228232
.await;
229233
if let Err(err) = close_result {
230-
abort_partial_writer(&mut self.writer).await;
234+
abort_partial_writer(&mut self.writer, &self.operator, &self.storage_path).await;
231235
return Err(err);
232236
}
233237
Ok(())
234238
}
235239

236240
pub async fn abort(mut self) {
237-
abort_partial_writer(&mut self.writer).await;
241+
abort_partial_writer(&mut self.writer, &self.operator, &self.storage_path).await;
238242
}
239243
}
240244

blob/src/blob/io.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,20 +39,20 @@ impl BlobHandler {
3939
let bytes = match chunk {
4040
Ok(bytes) => bytes,
4141
Err(err) => {
42-
abort_partial_writer(&mut writer).await;
42+
abort_partial_writer(&mut writer, &operator, &storage_path).await;
4343
return BlobEvent::Error(BlobError::WriteError(err.to_string()));
4444
}
4545
};
4646
hasher.update(&bytes);
4747
if let Err(err) = writer.write(bytes.to_vec()).await {
48-
abort_partial_writer(&mut writer).await;
48+
abort_partial_writer(&mut writer, &operator, &storage_path).await;
4949
return BlobEvent::Error(BlobError::WriteError(err.to_string()));
5050
}
5151
bytes_written += bytes.len() as u64;
5252
}
5353

5454
if let Err(err) = writer.close().await {
55-
abort_partial_writer(&mut writer).await;
55+
abort_partial_writer(&mut writer, &operator, &storage_path).await;
5656
return BlobEvent::Error(BlobError::WriteError(err.to_string()));
5757
}
5858
location.blob_size = bytes_written;
@@ -247,7 +247,7 @@ impl BlobHandler {
247247
let bytes_written = match compose_result {
248248
Ok(bytes_written) => bytes_written,
249249
Err(err) => {
250-
abort_partial_writer(&mut writer).await;
250+
abort_partial_writer(&mut writer, &operator, &storage_path).await;
251251
return BlobEvent::Error(err);
252252
}
253253
};

blob/src/blob/tests.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,50 @@ async fn write_finalization_failure_emits_no_success_or_load() {
666666
assert_eq!(aborts.load(std::sync::atomic::Ordering::SeqCst), 1);
667667
}
668668

669+
#[tokio::test]
670+
async fn failed_write_cleans() {
671+
let context = setup_blob_handle(1).await;
672+
let root = tempdir().unwrap();
673+
let location = BackendLocation {
674+
root: root.path().to_str().unwrap().to_string(),
675+
storage_bucket: "bucket".to_string(),
676+
backend_path: "partial.bin".to_string(),
677+
ulid: Ulid::new(),
678+
compressed: false,
679+
encrypted: false,
680+
created_by: test_user_id(),
681+
created_at: SystemTime::now(),
682+
staging: false,
683+
partial: false,
684+
blob_size: 0,
685+
hashes: HashMap::new(),
686+
};
687+
let operator = crate::opendal::init_operator(
688+
Backend::FileSystem,
689+
HashMap::from([(
690+
"root".to_string(),
691+
root.path().to_str().unwrap().to_string(),
692+
)]),
693+
)
694+
.unwrap();
695+
let blob = BackendStream::new(futures::stream::iter([
696+
Ok(bytes::Bytes::from_static(b"partial")),
697+
Err(std::io::Error::other("injected stream failure")),
698+
]));
699+
700+
let event = context
701+
.blob_handle
702+
.handler
703+
.write_stream_to_location(location.clone(), operator, blob)
704+
.await;
705+
706+
assert!(matches!(event, BlobEvent::Error(BlobError::WriteError(_))));
707+
assert!(
708+
!std::path::Path::new(&location.get_full_path().unwrap()).exists(),
709+
"partial filesystem target remains"
710+
);
711+
}
712+
669713
#[tokio::test]
670714
async fn compose_close_fails() {
671715
let context = setup_blob_handle(5).await;

blob/src/opendal.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,16 @@ use opendal::layers::{LoggingLayer, RetryLayer};
88
use opendal::{Builder, Operator, services};
99
use std::collections::HashMap;
1010

11-
pub(crate) async fn abort_partial_writer(writer: &mut opendal::Writer) {
11+
pub(crate) async fn abort_partial_writer(
12+
writer: &mut opendal::Writer,
13+
operator: &Operator,
14+
storage_path: &str,
15+
) {
1216
if let Err(err) = writer.abort().await {
13-
tracing::warn!(error = %err, "failed to abort partial blob writer");
17+
tracing::warn!(error = %err, "failed to abort partial blob writer; deleting output");
18+
if let Err(delete_err) = operator.delete(storage_path).await {
19+
tracing::warn!(error = %delete_err, "failed to delete partial blob output");
20+
}
1421
}
1522
}
1623

0 commit comments

Comments
 (0)