Conversation
landonxjames
left a comment
There was a problem hiding this comment.
Few questions about some of the logic. Nothing major or blocking.
| return PollWork::Done; | ||
| } | ||
|
|
||
| let mut state = self.inner.state.lock().unwrap(); |
There was a problem hiding this comment.
| let mut state = self.inner.state.lock().unwrap(); | |
| let mut state = self.inner.state.lock().expect("Failed to unlock transfer state"); |
There was a problem hiding this comment.
This suggestion applies to a few places, just putting it here so I don't spam it everywhere.
There was a problem hiding this comment.
More generally, this Mutex seems like it is going to be locked for every poll_work and execute_* and like that could cause a lot of contention. I wonder if using something that is supposed to be more performant, like parking_lot::Mutex would make sense here?
Probably a premature optimization, but worth evaluating when you get to benchmarking this
| distribute_work(&mut mpu_data, ctx)?; | ||
| Ok(UploadType::MultipartUpload(mpu_data)) | ||
| } | ||
| // TODO: Relax this constraint - unknown content length implies MPU |
There was a problem hiding this comment.
| // TODO: Relax this constraint - unknown content length implies MPU | |
| // TODO(vnext): Relax this constraint - unknown content length implies MPU |
| })) | ||
| } | ||
| Ok(None) => { | ||
| tracing::warn!("part_reader returned None for part {}", part_number); |
There was a problem hiding this comment.
Think this should just be a trace log? If content_length is just an upper bound instead of exact (which is how I think size_hint works?) then this will happen sometimes and shouldn't be exposed as something failing.
|
|
||
| let (stream, content_length) = { | ||
| let mut state = self.inner.state.lock().unwrap(); | ||
| match std::mem::replace(&mut *state, UploadState::Done) { |
There was a problem hiding this comment.
Is replacing this with Done correct? If poll_work is called in the window what state is set to Done won't it return PollWork::Done, causing the scheduler to remove
the transfer entirely? Seems like it would be possible since you have to re-acquire the lock on the state later in this function?
There was a problem hiding this comment.
Good catch, no this is wrong.
| .clone() | ||
| .unwrap_or_default(); | ||
| match abort_policy { | ||
| FailedMultipartUploadPolicy::Retain => Ok(AbortedUpload::default()), |
There was a problem hiding this comment.
We are no longer checking the abort_policy is that intended?
|
|
||
| let resp = match req | ||
| .customize() | ||
| .disable_payload_signing() |
There was a problem hiding this comment.
We disable this for MPU but not PutObject? Might make sense but would probably want a comment explaining why
There was a problem hiding this comment.
Just a miss when converting.
| use crate::types::{ConcurrencyMode, PartSize}; | ||
|
|
||
| #[tokio::test] | ||
| async fn test_basic_mpu() { |
There was a problem hiding this comment.
Doesn't seem to be a new version of the happy path test_basic_mpu and test_basic_upload_object tests?
|
Again, to aid code review: a runtime sequence diagram for MPU |
ysaito1001
left a comment
There was a problem hiding this comment.
Left nit comments. Looks good!
| /// State machine for work progression | ||
| state: Mutex<UploadState>, | ||
| /// The original request (body taken for processing) | ||
| request: Arc<UploadInput>, |
There was a problem hiding this comment.
Will this end up being double Arc, seen from UploadTransfer?
| } | ||
|
|
||
| /// Get the upload_id if MPU was started. | ||
| pub(crate) fn upload_id(&self) -> Option<String> { |
There was a problem hiding this comment.
nit: do we always want to clone a returned String, as opposed to Option<&str>?
There was a problem hiding this comment.
It's not on a hot path, only for abort, I'm going to leave for now.
| distribute_work(&mut mpu_data, ctx)?; | ||
| Ok(UploadType::MultipartUpload(mpu_data)) | ||
| } | ||
| // TODO: Relax this constraint - unknown content length implies MPU |
| return PollWork::Pending; | ||
| } | ||
| self.inner.ctx.set_pending(); | ||
| return PollWork::Pending; |
There was a problem hiding this comment.
Just to double check, once next_part exceeds total_parts, is returning PollWork::Pending here is expected?
There was a problem hiding this comment.
No, in practice this state is never observed I think because we transition to completing before poll_work sees it. I'll update it though to transition to completing as well.
Summary
Replace the Tower service-based upload orchestration (
ConcurrencyLimitLayer,service_fn,Buffer) with a scheduler-driven state machine. The upload transfer implements theTransfertrait from #131. The scheduler polls the transfer for work; the transfer produces work items lazily, one at a time.Upload Lifecycle
Multipart upload
Small file (below MPU threshold)
Skips multipart entirely — produces a single
PutObjectwork item.Design
Each
UploadParthas two phases: a DataIO phase that reads the part from thePartReader, then a Network phase that sends it to S3. The Network phase is a follow-on work item that bypasses the ready set, so the buffer isn't held waiting behind other transfers.Error handling follows the download transfer's contract:
fail()stores the original error viactx.set_failed(error)and signals terminaljoin()awaitscompletion_rxfromTransferContext, then reads the error fromctx.take_error()on failure or the result fromtransfer.take_result()on successTransferContextand the transfer struct respectivelyWhat changed
operation/upload/transfer.rsTransfertrait.operation/upload/upload.rsorchestrate()createsUploadTransferand enqueues to scheduler instead of spawning Tower service pipeline.operation/upload/handle.rsUploadTransfer+completion_rxdirectly.join()follows download's pattern. Abort waits for scheduler idle, callsAbortMultipartUpload.operation/upload/service.rsscheduler/mod.rsStateMachineTerminalReceiver.What did not change
runtime::schedulerand service patternupload_objects/download_objectsunchangedmiddleware/,runtime/kept for download and multi-object operationsBy submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.