feat(storage): implement appendable upload connector#5956
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces the appendable_object_writer module and supporting bidi_write infrastructure to enable bidirectional streaming writes for Google Cloud Storage. Key feedback includes avoiding .expect() on parameter conversions to prevent panics, simplifying complex string-folding logic, removing an unnecessary clone of the request object, and ensuring the background worker fails loudly with an explicit error if the stream closes unexpectedly while flushes are pending.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #5956 +/- ##
==========================================
+ Coverage 97.73% 97.75% +0.01%
==========================================
Files 244 246 +2
Lines 61096 61703 +607
==========================================
+ Hits 59710 60315 +605
- Misses 1386 1388 +2 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
joshuatants
left a comment
There was a problem hiding this comment.
We're gone through this extensively offline.
f3e8345 to
4c4b7be
Compare
1713d73 to
9cca986
Compare
coryan
left a comment
There was a problem hiding this comment.
This is still too large. Aim for at most 200 lines of new code, which probably means 400 to 600 lines per PR. 1600 lines is simply too large.
Ok. I am going to split this PR into smaller PRs. I'll update once done. Thank you and sorry about this! |
9cca986 to
e86f327
Compare
I split this PR into three PRs:
Thank you. |
|
|
||
| #[allow(dead_code)] | ||
| pub fn handle_redirect(spec: Arc<Mutex<AppendObjectSpec>>, status: Status) -> crate::Error { | ||
| pub fn handle_redirect(state: Arc<Mutex<AppendObjectSpecState>>, status: Status) -> crate::Error { |
There was a problem hiding this comment.
Consider:
| pub fn handle_redirect(state: Arc<Mutex<AppendObjectSpecState>>, status: Status) -> crate::Error { | |
| pub fn handle_redirect(state: &mut AppendObjectSpecState, status: Status) -> crate::Error { |
or make handle_redirect() a member function of AppendObjectSpecState:
impl AppendObjectSpecState {
pub fn handle_redirect(&mut self, status: Status) -> crate::Error {
}Then you can use this (in tests) without an Arc<Mutex<>> and still use it in the production code as you want.
| AppendObjectSpecState::Append(spec) => { | ||
| let mut new_spec = spec.clone(); | ||
| new_spec.routing_token = redirect.routing_token; | ||
| new_spec.write_handle = redirect.write_handle; | ||
| if let Some(g) = redirect.generation { | ||
| new_spec.generation = g; | ||
| } | ||
| AppendObjectSpecState::Append(new_spec) | ||
| } |
There was a problem hiding this comment.
I suspect if you receive a &mut self then this can be implemented using simpler assignments.
|
|
||
| let new_state = match &*guard { | ||
| AppendObjectSpecState::Write(spec, _) => { | ||
| if let Some(generation) = redirect.generation { |
There was a problem hiding this comment.
The amount of copying and cloning here makes me sad... you should be able to do something like:
let new_spec = spec.resource.map(|r| /* move the data out of |r| */);
| #[derive(Clone, Debug)] | ||
| #[allow(dead_code)] | ||
| pub enum AppendObjectSpecState { | ||
| Write(Box<WriteObjectSpec>, Option<String>), |
There was a problem hiding this comment.
Why a Box<WriteObjectSpec> ?
| /// Represents a bidirectional streaming connection. | ||
| /// Contains the transmission channel for requests and the receiving stream for responses. | ||
| #[derive(Debug)] | ||
| #[allow(dead_code)] |
There was a problem hiding this comment.
Instead of repeating this line in each element, put it at the place the module is imported.
| let mut x_goog_request_params = format!("bucket={bucket_name}"); | ||
| if let Some(token) = routing_token { | ||
| x_goog_request_params.push_str("&routing_token="); | ||
| x_goog_request_params.push_str(&token); |
There was a problem hiding this comment.
This needs to be percent-encoded, unless you happen to know that it never needs the percent encoding, in which can a comment justifying that would be useful.
| x_goog_request_params.push_str(&token); | ||
| } | ||
|
|
||
| let (tx, rx) = tokio::sync::mpsc::channel::<BidiWriteObjectRequest>(100); |
There was a problem hiding this comment.
The magical 100 should be a configuration parameter or a constant. I apologize because I probably left a number of these in the code for open_object().
| let new_state = match &*guard { | ||
| AppendObjectSpecState::Write(s, _) => { | ||
| AppendObjectSpecState::Append(AppendObjectSpec { | ||
| bucket: s | ||
| .resource | ||
| .as_ref() | ||
| .map(|r| r.bucket.clone()) | ||
| .unwrap_or_default(), | ||
| object: s | ||
| .resource | ||
| .as_ref() | ||
| .map(|r| r.name.clone()) | ||
| .unwrap_or_default(), | ||
| generation: new_generation.unwrap_or(0), | ||
| write_handle: m.write_handle.clone(), | ||
| ..Default::default() | ||
| }) | ||
| } | ||
| AppendObjectSpecState::Append(s) => { | ||
| AppendObjectSpecState::Append(AppendObjectSpec { | ||
| generation: new_generation.unwrap_or(s.generation), | ||
| write_handle: m.write_handle.clone().or_else(|| s.write_handle.clone()), | ||
| ..s.clone() | ||
| }) | ||
| } | ||
| }; | ||
| *guard = new_state; |
There was a problem hiding this comment.
You should refactor this to a member function like I outline for handle_redirect()
| let problem = SubstitutionFail::MismatchExpecting( | ||
| bucket_name.to_string(), | ||
| "projects/_/buckets/*", | ||
| ); | ||
| let mismatch = SubstitutionMismatch { | ||
| field_name: "bucket", | ||
| problem, | ||
| }; | ||
| let mismatch = PathMismatch { | ||
| subs: vec![mismatch], | ||
| }; | ||
| let mismatch = BindingError { | ||
| paths: vec![mismatch], | ||
| }; | ||
|
|
||
| return Err(crate::Error::binding(mismatch)); |
There was a problem hiding this comment.
The connect_attempt() function is a bit long, consider refactoring things like this to helper functions.
This PR comes after PR #5932.