Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

Closing a partial fixed-length duplex request body does not complete the request side

Open
#9,648 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

  • #9660 by @Sskift — closed without merging

Assessment

Difficulty
3/5
Estimated time
1-2 days
Newbie friendliness
45/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Stale
Tech stack
kotlin

Research direction

Read Exchange.RequestBodySink.close() in okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/connection/Exchange.kt around the linked lines, then trace how complete(...) marks the request side finished. Reproduce the HTTP/2 duplex cancellation sequence described in the issue and check the connection allocation state. Done means closing a partial fixed-length body reports the length mismatch while allowing the cancelled call's connection allocation to be released.

Written by the indexing model from the issue text.

Description

This was observed with OkHttp 5.3.2 while working on https://github.com/ktorio/ktor/pull/5791. The current OkHttp main branch contains the same relevant implementation.

When a fixed-length duplex request is cancelled before all declared bytes are written, closing its sink throws:

ProtocolException: unexpected end of stream

The exception is expected, but the request side is not marked complete, leaving the connection allocated:

connectionCount() == 1
idleConnectionCount() == 0
How to reproduce

The observed sequence is:

  1. Start an HTTP/2 duplex request whose body returns a positive contentLength().
  2. Write only part of the request body and receive the server's final response.
  3. Cancel the call and close the partially written request sink.
  4. Observe the length-mismatch exception and that the connection does not become idle.

Exchange.RequestBodySink.close()throws before calling complete(...):

https://github.com/lysine-dev/okhttp/blob/61423f472da24e0ccc42b6a2c0863fb27932fea5/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/connection/Exchange.kt#L294-L306

A subsequent close() cannot complete the request because closed is already true.

Should the request side be completed with failure before reporting the length mismatch, so the cancelled call can release its connection allocation?

Workaround

As a workaround in Ktor, after request cancellation we attempt to call flush() before the sink is closed. Because the HTTP/2 stream has already been reset, RequestBodySink.flush() fails and invokes complete(e), which marks the request side complete.

Unfortunately this relies on OkHttp’s internal implementation and only works when flush() fails after cancellation.

Dominant language
Kotlin
Stars
47.1k
Forks
9.3k
Avg merge
18h 18m
Merged PRs (30d)
15

Getting set up

This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from lysine-dev/okhttp

All issues in lysine-dev/okhttp

Similar issues

More Kotlin issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.