Skip to content

[BUG] InputStreamContent closes caller InputStream between retry attempts, causing resettableContent() to fail with "Stream closed" #49648

Description

@mihhailmeshkov

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    customer-reportedIssues that are reported by GitHub users external to the Azure organization.needs-triageWorkflow: This is a new issue that needs to be triaged to the appropriate team.questionThe issue doesn't require a change to the product in order to be resolved. Most issues start as that

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions