aeron-io/aeron · error · ClusterException

initial ack already received from service: possible…

Error message

initial ack already received from service: possible duplicate serviceId=<serviceId>

What it means

captureServiceAck records the first (ackId==0) ack from each clustered service, keyed by serviceId. If a second initial ack arrives for a service that already recorded one, the internal invariant (exactly one initial ack per service) is violated, so ClusterException is thrown.

Solutions

  1. Ensure each ClusteredService in the configuration has a unique serviceId
  2. Check that service containers are not started twice against the same cluster
  3. Review custom ClusteredService implementations for duplicate ack(AckId 0) sends
  4. Restart the node cleanly if a partially-restarted container left stale ack state

Example fix

// before: duplicate serviceId across services
ClusteredServiceContainer.Configuration.serviceId(0); // service A
ClusteredServiceContainer.Configuration.serviceId(0); // service B

// after: unique ids
ClusteredServiceContainer.Configuration.serviceId(0); // service A
ClusteredServiceContainer.Configuration.serviceId(1); // service B
Defensive patterns

Strategy: validation

Validate before calling

// validate unique serviceIds at startup
Set<Integer> ids = new HashSet<>();
for (ClusteredServiceContainer.Context c : serviceContexts)
{
    if (!ids.add(c.serviceId()))
    {
        throw new IllegalArgumentException("duplicate serviceId: " + c.serviceId());
    }
}

Try / catch

catch (ClusterException e)
{
    if (e.getMessage().contains("duplicate serviceId"))
    {
        log.error("Configuration error: two services share one serviceId", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: Thrown when ackId is 0 and serviceClientIds[serviceId] is not NULL_VALUE, i.e. a duplicate ackId=0 ack from the same serviceId.

Common situations: Two services configured with the same serviceId; a service container restarted and re-sent its initial ack; a misbehaving or buggy custom ClusteredService sending duplicate acks.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of aeron-io/aeron@6d60124e15 (2026-09-12). Data as JSON: /api/errors/4fadf0607b7107cf. Report an issue: GitHub.

Appendix: source

Thrown at aeron-cluster/src/main/java/io/aeron/cluster/ConsensusModuleAgent.java:2720

            NodeControl.ToggleState.reset(nodeControlToggle);

            return 1;
        }

        return 0;
    }

    private boolean appendAction(final ClusterAction action, final long timestamp, final int flags)
    {
        return logPublisher.appendClusterAction(leadershipTermId, timestamp, action, flags);
    }

    private void captureServiceAck(final long logPosition, final long ackId, final long relevantId, final int serviceId)
    {
        if (0 == ackId && NULL_VALUE != serviceClientIds[serviceId])
        {
            throw new ClusterException(
                "initial ack already received from service: possible duplicate serviceId=" + serviceId);
        }

        serviceAckQueues[serviceId].offerLast(new ServiceAck(ackId, logPosition, relevantId));
    }

    private boolean tryCreateAppendPosition(final int logSessionId)
    {
        final CountersReader counters = aeron.countersReader();
        final int counterId = RecordingPos.findCounterIdBySession(counters, logSessionId, archive.archiveId());
        if (CountersReader.NULL_COUNTER_ID == counterId)
        {
            return false;
        }

        final long registrationId = counters.getCounterRegistrationId(counterId);
        if (0 == registrationId)
        {

View on GitHub (pinned to 6d60124e15)