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:
- explicit wrapper close;
- 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.
Description
The phantom-reachability helper registers a Java 9+
Cleaneraction but discards the registration handle. Wrapper classes such asPhantomReachableClosingHttpClient,PhantomReachableSleeper,PhantomReachableClosingStreamResponse,PhantomReachableClosingAsyncStreamResponse, andPhantomReachableClosingHttpRequestAuthenticatorthen implement explicitclose()by calling the underlying resource directly.That creates two independent cleanup paths:
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 theAutoCloseablewrappers should not rely on delegate idempotency.