[BUG] InputStreamContent closes caller InputStream between retry attempts, causing resettableContent() to fail with "Stream closed"
questioncustomer-reportedneeds-triage
### Describe the bug
`InputStreamContent` built from a mark/reset-capable `InputStream` loses replayability after the first failed HTTP attempt, causing all retries to fail with "stream closed" exception.
The bug is in `RestProxyUtils`: when preparing the HTTP request body, it extracts the raw stream from the caller's `InputStreamContent`, wraps it in a `LengthValidatingInputStream`, and builds a new `InputStreamContent` from that wrapper (lines 65–67 and 142–147). The new `InputStreamContent` correctly detects the stream as replayable (because `markSupported()` returns true via delegation), stores `() -> resettableContent(LengthValidatingInputStream)` as its replay lambda, and calls `mark()` on the `LengthValidatingInputStream`.
After each failed attempt the Reactor pipeline (via `MonoUsing`) closes the stream it obtained from `toStream()`. `LengthValidatingInputStream.close()` delegates unconditionally to `inner.close()`, which closes the caller's original stream. On the next retry, `resettableContent()` calls `LengthValidatingInputStream.reset()` → `inner.reset()` → the original stream throws `IOException: Stream closed`.
Verified present in `azure-core` 1.55.4 and 1.58.0.
---
### Exception or Stack Trace
```
java.io.UncheckedIOException: java.io.IOException: Stream closed
at com.azure.core.implementation.util.InputStreamContent.resettableContent(InputStreamContent.java:198)
at com.azure.core.implementation.util.InputStreamContent.lambda$new$0(InputStreamContent.java:65)
at com.azure.core.implementation.util.InputStreamContent.toStream(InputStreamContent.java:106)
at reactor.core.publisher.MonoUsing.subscribe(MonoUsing.java:75)
...
at reactor.netty.channel.ChannelOperationsHandler.channelActive(ChannelOperationsHandler.java:62)
Caused by: java.io.IOException: Stream closed
at MyStream.reset(NobeBufInputStream.java:85)
at com.azure.core.implementation.http.rest.LengthValidatingInputStream.reset(LengthValidatingInputStream.java:95)
at com.azure.core.implementation.util.InputStreamContent.resettableContent(InputStreamContent.java:195)
...
Suppressed: java.util.concurrent.TimeoutException: Channel response timed out after 10000 milliseconds.
```
### Expected behavior
When `BinaryData.fromStream(is, length)` is called with a mark/reset-capable `InputStream` and retries are enabled, each retry attempt should be able to reset and re-read the stream. `LengthValidatingInputStream` should not close the underlying stream (or `RestProxyUtils` should shield the caller's stream from closure between retry attempts), since the stream is needed for subsequent retries.
A minimal fix would be for `RestProxyUtils` to wrap the caller's stream in a non-closing decorator before handing it to `LengthValidatingInputStream`.
Alternatively, `LengthValidatingInputStream` could avoid closing the inner stream when it is used in a retry context.
Workaround: wrap the stream in a non-closing decorator before passing to `BinaryData.fromStream()`.
### Reproducer
Test demonstrating both the bug and workaround is in attached file. AI-created, human-checked.
[AzureSdkInputStreamRetryBugTest.java](https://github.com/user-attachments/files/29382005/AzureSdkInputStreamRetryBugTest.java)
0 条评论