-
Notifications
You must be signed in to change notification settings - Fork 17
Description
Detail Bug Report
Summary
- Context: The SDK's append functionality uses a frame monitoring system (introduced in recent commit 5061307) to detect if any request body data was sent over the network, enabling safe retries when using
AppendRetryPolicy::NoSideEffects. - Bug: When compression is enabled and
AppendRetryPolicy::NoSideEffectsis used, append requests fail immediately with a compression error before any data is sent. - Actual vs. expected: The append operation fails with "streaming request bodies cannot be compressed" error instead of successfully compressing and sending the request.
- Impact: Users who enable compression with
NoSideEffectsretry policy cannot perform append operations at all - all appends fail immediately.
Code with Bug
In sdk/src/client.rs, the monitored() method wraps a body in a streaming wrapper:
pub(crate) fn monitored(self, signal: FrameSignal) -> Self {
Self(BodyInner::Streaming(BoxBody::new( // <-- BUG 🔴 Converts Full to Streaming
RequestFrameMonitorBody::new(self.into_http_body(), signal),
)))
}Later, compress_body() attempts to compress the body:
async fn compress_body(
body: Body,
compression: Compression,
) -> Result<(Body, Option<HeaderValue>), Error> {
match compression {
Compression::None => Ok((body, None)),
Compression::Gzip => {
let Some(data) = body.as_bytes() else { // <-- BUG 🔴 Returns None for Streaming body
return Err(Error::Compression(
"streaming request bodies cannot be compressed".into(),
));
};
// ... compression code
}
// ... similar for Zstd
}
}And as_bytes() returns None for streaming bodies:
fn as_bytes(&self) -> Option<&[u8]> {
match &self.0 {
BodyInner::Empty => Some(&[]),
BodyInner::Full(bytes) => Some(bytes),
BodyInner::Streaming(_) => None, // <-- BUG 🔴 Streaming bodies can't be compressed
}
}Explanation
AppendRetryPolicy::NoSideEffects enables frame monitoring by wrapping the request body with RequestFrameMonitorBody, which changes the body representation to BodyInner::Streaming. The compression code path requires body.as_bytes() to retrieve the full payload; for streaming bodies as_bytes() returns None, causing compress_body() to error out with "streaming request bodies cannot be compressed". This happens before any network I/O, and the error is mapped to a non-retryable client error, so the append fails immediately.
Execution path (high level): user enables compression + NoSideEffects → request is cloned and with_monitored_body(...) is applied (body becomes streaming) → execute_unary_with calls compress_body(...) → compression fails because body is streaming.
Recommended Fix
Apply compression before wrapping the body with frame monitoring (i.e., compress while the body is still Full, then apply with_monitored_body(...)). This preserves monitoring behavior while allowing compression to operate on bytes.
History
This bug was introduced in commit 5061307. The commit added frame monitoring for append retry safety by wrapping request bodies in a RequestFrameMonitorBody, which converts bodies from BodyInner::Full to BodyInner::Streaming. The bug occurred because compression (which existed prior) requires calling body.as_bytes() to access the full payload, but this method returns None for streaming bodies, causing compression to fail before any monitoring or retry logic can execute.