max / makenotwork
1 file changed,
+11 insertions,
-3 deletions
| @@ -500,7 +500,7 @@ impl S3Client { | |||
| 500 | 500 | // Backoff: 200ms, 800ms. Cheap enough not to | |
| 501 | 501 | // mask a permanent failure; long enough that | |
| 502 | 502 | // a brief network glitch resolves. | |
| 503 | - | let delay_ms = 200u64 * (1u64 << (attempt - 1) * 2); | |
| 503 | + | let delay_ms = 200u64 * (1u64 << ((attempt - 1) * 2)); | |
| 504 | 504 | tracing::warn!( | |
| 505 | 505 | part_number, attempt, delay_ms, error = ?e, | |
| 506 | 506 | "S3 upload_part transient failure, retrying" | |
| @@ -540,7 +540,8 @@ impl S3Client { | |||
| 540 | 540 | .set_parts(Some(completed_parts)) | |
| 541 | 541 | .build(); | |
| 542 | 542 | ||
| 543 | - | self.client | |
| 543 | + | if let Err(e) = self | |
| 544 | + | .client | |
| 544 | 545 | .complete_multipart_upload() | |
| 545 | 546 | .bucket(&self.bucket) | |
| 546 | 547 | .key(key) | |
| @@ -548,7 +549,14 @@ impl S3Client { | |||
| 548 | 549 | .multipart_upload(completed) | |
| 549 | 550 | .send() | |
| 550 | 551 | .await | |
| 551 | - | .map_err(|e| format!("S3 complete multipart upload failed: {e}"))?; | |
| 552 | + | { | |
| 553 | + | // Abort before returning, like every other failure branch (Sto-S1). | |
| 554 | + | // Without this, a failed completion leaves the multipart upload and all | |
| 555 | + | // its uploaded parts stranded server-side, accruing storage cost and | |
| 556 | + | // violating this function's "aborts on failure" contract. | |
| 557 | + | self.abort_multipart_upload(key, &upload_id).await; | |
| 558 | + | return Err(format!("S3 complete multipart upload failed: {e}")); | |
| 559 | + | } | |
| 552 | 560 | ||
| 553 | 561 | Ok(()) | |
| 554 | 562 | } |