Skip to content

Phantom-reachable wrappers can close resources twice after explicit cleanup #884

Description

@sylvesterkaczmarek

Description

The phantom-reachability helper registers a Java 9+ Cleaner action but discards the registration handle. Wrapper classes such as PhantomReachableClosingHttpClient, PhantomReachableSleeper, PhantomReachableClosingStreamResponse, PhantomReachableClosingAsyncStreamResponse, and PhantomReachableClosingHttpRequestAuthenticator then implement explicit close() by calling the underlying resource directly.

That creates two independent cleanup paths:

  1. explicit wrapper close;
  2. the still-registered cleaner action after the wrapper becomes phantom reachable.

A resource that is closed explicitly can therefore be closed a second time later by the cleaner.

Why it matters

AutoCloseable.close() is not generally required to be idempotent. Provider-owned authenticators, custom HTTP clients, stream responses, or sleepers can perform stateful cleanup or throw on repeated close. Cleanup should run at most once regardless of whether it is triggered explicitly or by GC fallback.

Expected behavior

Explicit cleanup and phantom-reachable cleanup should share one idempotent close gate. An explicit close should mark the registered cleanup as already performed, so a later Cleaner invocation becomes a no-op.

Suggested approach

Have closeWhenPhantomReachable(...) return a small close handle backed by a shared atomic once-guard. Register that same guarded action with the Cleaner and have explicit-close wrappers call the returned handle instead of closing the delegate independently.

Java 8 can use the same handle for explicit cleanup while retaining the existing no-Cleaner fallback.

Scope

The executor-service wrapper is less sensitive because repeated shutdown() is safe, but the AutoCloseable wrappers should not rely on delegate idempotency.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions