Skip to content

feat(storage): implement appendable upload connector#5956

Open
vsharonlynn wants to merge 2 commits into
googleapis:mainfrom
vsharonlynn:appendable_upload_3
Open

feat(storage): implement appendable upload connector#5956
vsharonlynn wants to merge 2 commits into
googleapis:mainfrom
vsharonlynn:appendable_upload_3

Conversation

@vsharonlynn

Copy link
Copy Markdown
Contributor

This PR comes after PR #5932.

@product-auto-label product-auto-label Bot added the api: storage Issues related to the Cloud Storage API. label Jun 28, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/storage/src/storage/bidi_write/connector.rs Outdated
Comment thread src/storage/src/storage/bidi_write/connector.rs Outdated
Comment thread src/storage/src/storage/bidi_write/connector.rs Outdated
Comment thread src/storage/src/storage/bidi_write/connector.rs Outdated
Comment thread src/storage/src/storage/bidi_write/worker.rs Outdated
@codecov

codecov Bot commented Jun 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.48562% with 22 lines in your changes missing coverage. Please review.
✅ Project coverage is 97.75%. Comparing base (affbeb2) to head (c997071).

Files with missing lines Patch % Lines
src/storage/src/storage/bidi_write/connector.rs 97.24% 14 Missing ⚠️
src/storage/src/storage/bidi_write/mocks.rs 85.71% 4 Missing ⚠️
src/storage/src/storage/bidi_write/redirect.rs 95.50% 4 Missing ⚠️
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@joshuatants joshuatants left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're gone through this extensively offline.

Comment thread src/storage/src/storage/bidi_write/connector.rs Outdated
Comment thread src/storage/src/storage/bidi_write/redirect.rs Outdated
Comment thread src/storage/src/storage/bidi_write/transport.rs Outdated
@vsharonlynn vsharonlynn force-pushed the appendable_upload_3 branch 8 times, most recently from f3e8345 to 4c4b7be Compare June 29, 2026 11:57
@vsharonlynn vsharonlynn marked this pull request as ready for review June 29, 2026 12:02
@vsharonlynn vsharonlynn requested review from a team as code owners June 29, 2026 12:02
@vsharonlynn vsharonlynn force-pushed the appendable_upload_3 branch 2 times, most recently from 1713d73 to 9cca986 Compare June 29, 2026 17:13

@coryan coryan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/storage/src/storage/bidi_write/connector.rs
Comment thread src/storage/src/storage/bidi_write/connector.rs Outdated
Comment thread src/storage/src/storage/bidi_write/connector.rs Outdated
Comment thread src/storage/src/storage/bidi_write.rs Outdated
Comment thread src/storage/src/storage/bidi_write/worker.rs Outdated
Comment thread src/storage/src/storage/bidi_write/worker.rs Outdated
@vsharonlynn

Copy link
Copy Markdown
Contributor Author

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!

@vsharonlynn vsharonlynn force-pushed the appendable_upload_3 branch from 9cca986 to e86f327 Compare July 8, 2026 02:58
@vsharonlynn vsharonlynn changed the title feat(storage): implement transport, connector, worker feat(storage): implement appendable upload connector Jul 8, 2026
@vsharonlynn

Copy link
Copy Markdown
Contributor Author

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!

I split this PR into three PRs:

Thank you.

@vsharonlynn vsharonlynn requested a review from coryan July 9, 2026 08:53

#[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 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider:

Suggested change
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.

Comment on lines +67 to +75
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)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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().

Comment on lines +281 to +307
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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should refactor this to a member function like I outline for handle_redirect()

Comment on lines +218 to +233
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));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The connect_attempt() function is a bit long, consider refactoring things like this to helper functions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api: storage Issues related to the Cloud Storage API.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants