Skip to content

Commit 5363740

Browse files
authored
fix(gcs+s3): Drain response bodies (#538)
1 parent fb95d44 commit 5363740

3 files changed

Lines changed: 71 additions & 31 deletions

File tree

objectstore-service/src/backend/gcs.rs

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,7 @@ impl GcsBackend {
537537
.map_err(|e| Error::reqwest("GCS: get metadata request", e))?;
538538

539539
if resp.status() == StatusCode::NOT_FOUND {
540+
resp.drain_body().await;
540541
return Ok(None);
541542
}
542543

@@ -595,7 +596,9 @@ impl GcsBackend {
595596
.send()
596597
.await
597598
.check_error("GCS: update custom time")
598-
.await?;
599+
.await?
600+
.drain_body()
601+
.await;
599602
Ok(())
600603
})
601604
.await
@@ -667,7 +670,9 @@ impl Backend for GcsBackend {
667670
.send()
668671
.await
669672
.check_error("GCS: upload object")
670-
.await?;
673+
.await?
674+
.drain_body()
675+
.await;
671676

672677
Ok(())
673678
}
@@ -701,17 +706,14 @@ impl Backend for GcsBackend {
701706
.get(header::CONTENT_RANGE)
702707
.and_then(|v| v.to_str().ok());
703708
let total = raw.and_then(ContentRange::parse_unsatisfiable_total);
704-
match total {
705-
Some(total) => return Err(Error::RangeNotSatisfiable { total }),
706-
None => {
707-
return Err(Error::Generic {
708-
context: format!(
709-
"GCS: 416 response with invalid Content-Range: {raw:?}"
710-
),
711-
cause: None,
712-
});
713-
}
714-
}
709+
let err = match total {
710+
Some(total) => Error::RangeNotSatisfiable { total },
711+
None => Error::generic(format!(
712+
"GCS: 416 response with invalid Content-Range: {raw:?}"
713+
)),
714+
};
715+
resp.drain_body().await;
716+
return Err(err);
715717
}
716718

717719
resp.check_error("GCS: get payload").await
@@ -764,10 +766,14 @@ impl Backend for GcsBackend {
764766

765767
// Do not error for objects that do not exist
766768
if resp.status() == StatusCode::NOT_FOUND {
769+
resp.drain_body().await;
767770
return Ok(());
768771
}
769772

770-
resp.check_error("GCS: delete object").await?;
773+
resp.check_error("GCS: delete object")
774+
.await?
775+
.drain_body()
776+
.await;
771777

772778
Ok(())
773779
})
@@ -960,6 +966,8 @@ impl MultipartUploadBackend for GcsBackend {
960966
.map(|s| s.to_owned())
961967
.ok_or_else(|| Error::generic("GCS: upload part response missing ETag header"))?;
962968

969+
resp.drain_body().await;
970+
963971
Ok(etag)
964972
}
965973

@@ -1021,7 +1029,9 @@ impl MultipartUploadBackend for GcsBackend {
10211029
.send()
10221030
.await
10231031
.check_error("GCS: abort multipart upload")
1024-
.await?;
1032+
.await?
1033+
.drain_body()
1034+
.await;
10251035

10261036
Ok(())
10271037
}

objectstore-service/src/backend/response.rs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,13 @@ pub trait ResponseExt {
6666
/// When called on `Result<Response, reqwest::Error>`, transport errors are
6767
/// wrapped as [`Error::Reqwest`] with the same context string.
6868
async fn check_error(self, context: &'static str) -> Result<Response>;
69+
70+
/// Drains the response body of a response we are otherwise done with.
71+
///
72+
/// reqwest only returns a connection to its pool once the response body has been fully read, so
73+
/// we need to explicitly drain it. Errors are swallowed, since the caller has already obtained
74+
/// everything it needs from the response.
75+
async fn drain_body(self);
6976
}
7077

7178
impl ResponseExt for Response {
@@ -86,9 +93,11 @@ impl ResponseExt for Response {
8693
} else if ct.starts_with("application/xml") || ct.starts_with("text/xml") {
8794
parse_xml_error(self).await
8895
} else {
89-
return self
90-
.error_for_status()
91-
.map_err(|e| Error::reqwest(context, e));
96+
let Err(e) = self.error_for_status_ref() else {
97+
return Ok(self);
98+
};
99+
self.drain_body().await;
100+
return Err(Error::reqwest(context, e));
92101
};
93102

94103
Err(Error::BackendResponse {
@@ -97,6 +106,10 @@ impl ResponseExt for Response {
97106
detail,
98107
})
99108
}
109+
110+
async fn drain_body(mut self) {
111+
while let Ok(Some(_)) = self.chunk().await {}
112+
}
100113
}
101114

102115
impl ResponseExt for Result<Response, reqwest::Error> {
@@ -109,6 +122,12 @@ impl ResponseExt for Result<Response, reqwest::Error> {
109122
}),
110123
}
111124
}
125+
126+
async fn drain_body(self) {
127+
if let Ok(resp) = self {
128+
resp.drain_body().await;
129+
}
130+
}
112131
}
113132

114133
async fn parse_json_error(resp: Response) -> BackendDetail {

objectstore-service/src/backend/s3_compatible.rs

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ where
179179

180180
if response.status() == StatusCode::NOT_FOUND {
181181
objectstore_log::debug!("Object not found");
182+
response.drain_body().await;
182183
return Ok(None);
183184
}
184185

@@ -188,15 +189,14 @@ where
188189
.get(reqwest::header::CONTENT_RANGE)
189190
.and_then(|v| v.to_str().ok());
190191
let total = raw.and_then(ContentRange::parse_unsatisfiable_total);
191-
match total {
192-
Some(total) => return Err(Error::RangeNotSatisfiable { total }),
193-
None => {
194-
return Err(Error::Generic {
195-
context: format!("S3: 416 response with invalid Content-Range: {raw:?}"),
196-
cause: None,
197-
});
198-
}
199-
}
192+
let err = match total {
193+
Some(total) => Error::RangeNotSatisfiable { total },
194+
None => Error::generic(format!(
195+
"S3: 416 response with invalid Content-Range: {raw:?}"
196+
)),
197+
};
198+
response.drain_body().await;
199+
return Err(err);
200200
}
201201

202202
let response = response.check_error("S3: failed to get object").await?;
@@ -263,7 +263,9 @@ where
263263
.send()
264264
.await
265265
.check_error("S3: update expiration time")
266-
.await?;
266+
.await?
267+
.drain_body()
268+
.await;
267269

268270
Ok(())
269271
}
@@ -312,7 +314,9 @@ impl<T: TokenProvider> Backend for S3CompatibleBackend<T> {
312314
.send()
313315
.await
314316
.check_error("S3: failed to put object")
315-
.await?;
317+
.await?
318+
.drain_body()
319+
.await;
316320

317321
Ok(())
318322
}
@@ -352,10 +356,17 @@ impl<T: TokenProvider> Backend for S3CompatibleBackend<T> {
352356
})?;
353357

354358
// Do not error for objects that do not exist.
355-
if response.status() != StatusCode::NOT_FOUND {
356-
response.check_error("S3: failed to delete object").await?;
359+
if response.status() == StatusCode::NOT_FOUND {
360+
response.drain_body().await;
361+
return Ok(());
357362
}
358363

364+
response
365+
.check_error("S3: failed to delete object")
366+
.await?
367+
.drain_body()
368+
.await;
369+
359370
Ok(())
360371
}
361372
}

0 commit comments

Comments
 (0)