Skip to content

MLE-32970 Prevent retries of multipart streaming requests - #1979

Open
rjdew-progress wants to merge 2 commits into
developfrom
MLE-32953
Open

rjdew-progress wants to merge 2 commits into
developfrom
MLE-32953

Conversation

@rjdew-progress

Copy link
Copy Markdown
Contributor

Why

A Data Services request with streaming multipart input can fail with Stream closed after a preceding gzipped Row Manager response. The IOException retry interceptor sees only the outer MultipartBody, so it can retry the request even though a nested StreamingOutputImpl has already consumed and closed its source.

Approach

  • Mark StreamingOutputImpl as one-shot using OkHttp's native RequestBody.isOneShot() contract.
  • Make the IOException retry interceptor honor isOneShot() in addition to the Java Client's RetryableRequestBody contract.
  • Add a regression test proving that one-shot status propagates through MultipartBody and prevents a second request attempt.

Validation

  • ./gradlew marklogic-client-api:test --tests com.marklogic.client.impl.StreamingOutputImplTest
  • The complete core test task was also attempted; its server-dependent tests could not run because no deployed MarkLogic test instance was available on localhost:8012.

Copilot AI 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.

Copilot review overview

🟡 Changes recommended

Multipart bodies wrapping InputStreamHandle may still be replayed because one-shot status is not propagated through ObjectRequestBody.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 High severity

Open (1)
What changed in this PR

Prevents retries of multipart requests containing one-shot streaming bodies.

Changes:

  • Marks StreamingOutputImpl as one-shot.
  • Honors OkHttp’s isOneShot() contract in retry handling.
  • Adds multipart streaming regression coverage.
File Reviewed change
marklogic-client-api/​src/​test/​java/​com/​marklogic/​client/​impl/​StreamingOutputImplTest.java Tests one-shot multipart behavior.
marklogic-client-api/​src/​main/​java/​com/​marklogic/​client/​impl/​StreamingOutputImpl.java Declares streaming bodies as one-shot.
marklogic-client-api/​src/​main/​java/​com/​marklogic/​client/​impl/​okhttp/​RetryIOExceptionInterceptor.java Avoids retries for one-shot request bodies.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Copilot AI 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.

Copilot review overview

🟢 Approval recommended

All reviewed changes are covered and no unresolved blocking issues were identified.

Review effort: Lite
Findings: None

Resolved since last review (1)

@rjdew-progress rjdew-progress changed the title MLE-32953 Prevent retries of multipart streaming requests MLE-32970 Prevent retries of multipart streaming requests Sep 24, 2026
…ndability

StreamingOutputImpl previously hardcoded isRetryable()=false / isOneShot()=true
for every wrapped OutputStreamSender, even when the originating write handle
was fully buffered and resendable (e.g. JacksonHandle, StringHandle). This
meant any request body built from such a handle -- including Row Manager plan
execution, document writes, and Data Services parameters -- was needlessly
blocked from being retried by RetryIOExceptionInterceptor after a transient
connection error, even though the content could safely be written again.

Thread each call site's already-known isResendable()/handleBase.isResendable()
value into StreamingOutputImpl instead of assuming non-resendable:
- writeDocumentImpl-style document writes
- putPostValueImpl
- postResource / putResource (used by RowManagerImpl for Optic plan execution)
- postIteratedResourceImpl (multipart-mixed eval/invoke style resources)
- addParts multipart builder
- makeRequestBodyForContent and BaseProxy's makeRequestBody (Data Services
  single-node parameters)

Call sites without an existing resendable concept (structured query search,
alert match) keep the prior conservative false/one-shot default via new 3-arg
doPost/doPut overloads, so behavior is unchanged where resendability was
never determined.

Added StreamingOutputImplTest cases covering: a resendable body being retried
after a simulated connection failure, and a non-resendable part inside a
MultipartBody still propagating isOneShot()=true so it is never retried.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants