Skip to content

Over-cap logged response body can close the underlying transport source twice #15

Description

@OmarAlJarrah

On the over-cap path, LoggableResponseBody.source() hands out a one-shot stream whose tail is the live delegate source:

val tail = liveTail ?: return buf.peek()
return provider.bufferedSource(PrefixThenTailSource(buf.peek(), tail))

where liveTail == delegate.source() (sdk-core/src/main/kotlin/org/dexpace/sdk/core/http/response/LoggableResponseBody.kt:138-148).

Two independent paths can then close that same source:

  • PrefixThenTailSource.close() closes tail (:304-310)
  • close() on the wrapper closes delegate (:193-196), which per the transport contract cascades into the same source (OkHttp ResponseAdapter: "closing the SDK response cascades into the okio source, which closes the OkHttp body").

A consumer that does body.source().use { … } and closes the Response closes the underlying transport source twice. It is currently saved only by okio's idempotent close() — the exact assumption the rest of this class refuses to make (the delegateClosed bookkeeping exists precisely because "some sockets / streams throw on double-close"). With the default 8 KiB preview, any response body over 8 KiB under BODY_AND_HEADERS takes this path, and the wrapper is provider-agnostic.

Suggested fix

Track whether the live tail was already closed (or route the tail close through the same delegateClosed guard so the underlying source is closed at most once), and add a test that closes the handed-out source and then the response.

Minor (same method)

The liveTail ?: return buf.peek() fallback at :146 is unreachable after a successful drain — the drain always sets either fullyCaptured or liveTail, otherwise drainError throws first — yet it still consumes the single-use token. A check(liveTail != null) would state the invariant instead of silently falling back.

Code added on fix/http-stack-correctness-resource-safety (#13).

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    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