apache/iceberg · warning

Attempted to release already closed HTTP client: key={}

Error message

Attempted to release already closed HTTP client: key={}

What it means

This is a warning log (not an exception) from the internal reference-counted HTTP client cache in HttpClientCache. Each acquire() increments a ref count per client key and each release() decrements it; when the count reaches zero the client is closed. Seeing this means release() was called on a client whose entry was already closed, so the release is ignored and returns false.

Source

Thrown at aws/src/main/java/org/apache/iceberg/aws/HttpClientCache.java:138

     */
    synchronized ManagedHttpClient acquire() {
      if (closed) {
        throw new IllegalStateException("Cannot acquire closed HTTP client: " + clientKey);
      }
      refCount++;
      LOG.debug("Acquired HTTP client: key={}, refCount={}", clientKey, refCount);
      return this;
    }

    /**
     * Release a reference to the HTTP client, decrementing the reference count. If the count
     * reaches zero, the client is closed.
     *
     * @return true if the client was closed, false otherwise
     */
    synchronized boolean release() {
      if (closed) {
        LOG.warn("Attempted to release already closed HTTP client: key={}", clientKey);
        return false;
      }

      refCount--;
      LOG.debug("Released HTTP client: key={}, refCount={}", clientKey, refCount);
      if (refCount == 0) {
        return closeHttpClient();
      } else if (refCount < 0) {
        LOG.warn(
            "HTTP client reference count went negative key={}, refCount={}", clientKey, refCount);
        refCount = 0;
      }
      return false;
    }

    @VisibleForTesting
    SdkHttpClient httpClient() {
      return httpClient;

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Audit call sites so release() is called exactly once per acquire(), typically in a single finally block
  2. Guard with your own boolean flag so a double release can't happen on retry/failure paths
  3. Treat the warning as benign if it comes from tests intentionally probing double-release; the call returns false and takes no action
  4. Ensure no code path closes the cache entry externally before all users finish with the client

Example fix

// before
S3Client client = cache.acquire(key);
try {
  use(client);
} finally {
  cache.releaseClient(key);
  cache.releaseClient(key); // second release logs the warning
}
// after
S3Client client = cache.acquire(key);
try {
  use(client);
} finally {
  cache.releaseClient(key); // exactly one release per acquire
}
Defensive patterns

Strategy: try-catch

Validate before calling

// track balance yourself
int acquired = 0;
acquired++; client = cache.acquire(key);
// assert before releasing
assert acquired > 0;

Type guard

boolean canRelease = acquiredCount.get(key) != null && acquiredCount.get(key) > 0;

Try / catch

// release() never throws; just don't double-release
try {
  use(client);
} finally {
  if (released.compareAndSet(false, true)) {
    cache.releaseClient(key);
  }
}

Prevention

When it happens

Trigger: Calling HttpClientCache.releaseClient (or the internal release()) more times than acquire() for the same client key, or after the entry was closed by another thread due to the ref count hitting zero. The tests closed, closedAgain, multipleReleasesAfterClose exercise exactly this double-release path.

Common situations: A caller wraps client usage in a finally block that releases even when acquire failed or an earlier release already ran; a race where two threads both observe refCount==1 and both call release; a retry wrapper that releases the client on both the failure and success paths.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12). Data as JSON: /api/errors/47e075c4ccd831e6. Report an issue: GitHub.