Sruhvx-jpg commented on PR #3165:
URL: https://github.com/apache/iceberg-rust/pull/3165#issuecomment-5792328243

   @laskoviymishka Addressed all the feedback from this pass.
   
   Regarding the multipart upload lifecycle, we replaced `WriteMultipart` with 
direct `Box<dyn MultipartUpload>` management so `abort()` can be awaited if 
completion or part uploads fail:
   ```rust
   struct ObjectStoreWriter {
       upload: Option<Box<dyn MultipartUpload>>,
       buffer: BytesMut,
       tasks: Vec<UploadPart>,
   }
   ```
   In `close()`, any failure during in-flight joins or 
`upload.complete().await` explicitly awaits `upload.abort().await` before 
returning `Err`, while `Drop` remains as the background safety net. We also 
added two extra hardening guards here: (1) handling 0-byte uploads so closing a 
fresh writer flushes an empty part (satisfying S3's requirement of $\ge$ 1 part 
to complete a multipart upload), and (2) guarding `append_bytes` against 
`remaining == 0` edge cases. Let me know if you are good with these extra 
guards.
   
   For testing abort behavior, we dropped the false-positive integration 
assertion and introduced unit tests backed by a `MockMultipartUpload` spy:
   ```rust
   #[tokio::test]
   async fn test_writer_part_failure_aborts() {
       let (mut writer, _parts, completed, aborted) = make_mock_writer(true, 
false);
       writer.write(Bytes::from(vec![0u8; 6 * 1024 * 1024])).await.unwrap();
       let err = writer.close().await.unwrap_err();
       assert!(aborted.load(Ordering::SeqCst));
   }
   ```
   This deterministically verifies the atomic `aborted` flag across clean 
closes, part failures, complete failures, and unclosed drops.
   
   On error mapping parity, we aligned storage errors to 
`ErrorKind::Unexpected` to match opendal and Iceberg conventions:
   ```rust
   object_store::Error::NotFound { path, .. } => (ErrorKind::Unexpected, 
format!("Object not found: {path}")),
   object_store::Error::AlreadyExists { path, .. } => (ErrorKind::Unexpected, 
format!("Object already exists: {path}")),
   object_store::Error::PermissionDenied { path, .. } => 
(ErrorKind::Unexpected, format!("Permission denied: {path}")),
   object_store::Error::Unauthenticated { path, .. } => (ErrorKind::Unexpected, 
format!("Unauthenticated: {path}")),
   ```
   We also mapped closed writer operations to `ErrorKind::PreconditionFailed`, 
unsupported schemes to `ErrorKind::FeatureUnsupported`, and config key parsing 
to `ErrorKind::DataInvalid`.
   
   For `delete_stream`, `StoreAndPath` now returns `bucket: String` directly, 
eliminating the duplicate `parse_s3_url` call, and grouping is handled via a 
single `BucketBatch` map:
   ```rust
   struct BucketBatch {
       store: Arc<dyn ObjectStore>,
       locations: Vec<ObjectStorePath>,
   }
   ```
   This removes the two-map structure and eliminates the `.expect("store must 
exist")` panic entirely.
   
   Lastly, for URL delimiter parsing, we switched from 
`trim_start_matches('/')` to `strip_prefix`:
   ```rust
   let relative = url.path().strip_prefix('/').unwrap_or(url.path());
   ```
   This preserves consecutive slashes on object keys (`s3://bucket//path` -> 
`/path`).
   
   All 34 unit tests, MinIO integration tests, and clippy/formatting checks are 
passing cleanly.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to