aeron-io/aeron · error · AeronException

unexpected close of heartbeat timestamp counter

Error message

unexpected close of heartbeat timestamp counter: <counterId>

What it means

AeronException thrown when the client attempts to update its heartbeat timestamp counter but the underlying counter has been closed (freed by the driver timing the client out). This is a race between driver-side client timeout and client-side keepalive; the conductor terminates since the driver no longer considers this client live.

Solutions

  1. Treat the client as terminated and create a new Aeron instance (this is by design after driver eviction)
  2. Keep the conductor serviced promptly: avoid blocking callbacks and long pauses (see interServiceTimeout)
  3. Increase driver-side and client-side timeouts so marginal pauses do not evict the client
  4. Inspect getCause() to confirm the counter-free race if diagnostics are needed

Example fix

// before
Aeron aeron = Aeron.connect(ctx);
// long blocking work on the same thread ...
// after
Aeron aeron = Aeron.connect(ctx);
runBlockingWorkOnSeparateExecutor(); // conductor stays serviced, client not evicted
Defensive patterns

Strategy: fallback

Validate before calling

// Before heavy work that may stall the conductor:
if (System.nanoTime() - lastServiceNs > interServiceTimeoutNs / 2) { serviceConductorNow(); }

Try / catch

catch (AeronException e) { if (e.getMessage().startsWith("unexpected close of heartbeat timestamp counter")) { client.close(); client = Aeron.connect(ctx); } } // driver evicted this client; reconnect

Prevention

When it happens

Trigger: The driver timed out this client (e.g. because its conductor thread was starved) and freed the heartbeat counter; the client's next keepalive pass hits the closed counter and wraps the resulting exception.

Common situations: Client thread stalls long enough for the driver to expire the client, then resumes and races keepalive; paused VM/container resuming after driver timeout; debugging sessions freezing the client past the driver timeout.

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 aeron-io/aeron@6d60124e15 (2026-09-12). Data as JSON: /api/errors/9c50ec1f20197cf1. Report an issue: GitHub.

Appendix: source

Thrown at aeron-client/src/main/java/io/aeron/ClientConductor.java:1923

                    countersReader, HEARTBEAT_TYPE_ID, ctx.clientId());

                if (NULL_COUNTER_ID != counterId)
                {
                    try
                    {
                        heartbeatTimestamp = new AtomicCounter(counterValuesBuffer, counterId);
                        heartbeatTimestamp.setRelease(nowMs);
                        appendToLabel(
                            countersReader.metaDataBuffer(),
                            counterId,
                            " name=" + ctx.clientName() + " " +
                            formatVersionInfo(AeronVersion.VERSION, AeronVersion.GIT_SHA));
                        timeOfLastKeepAliveNs = nowNs;
                    }
                    catch (final RuntimeException ex)  // a race caused by the driver timing out the client
                    {
                        terminateConductor();
                        throw new AeronException("unexpected close of heartbeat timestamp counter: " + counterId, ex);
                    }
                }
            }
            else
            {
                final int counterId = heartbeatTimestamp.id();
                if (!HeartbeatTimestamp.isActive(countersReader, counterId, HEARTBEAT_TYPE_ID, ctx.clientId()))
                {
                    terminateConductor();
                    throw new AeronException("unexpected close of heartbeat timestamp counter: " + counterId);
                }

                heartbeatTimestamp.setRelease(nowMs);
                timeOfLastKeepAliveNs = nowNs;
            }

            return 1;
        }

View on GitHub (pinned to 6d60124e15)