aeron-io/aeron · error · ArchiveException

failed to send list recording subscriptions request

Error message

failed to send list recording subscriptions request

What it means

AeronArchive throws this when the listRecordingSubscriptions request could not be offered to the archive control publication. ArchiveProxy.listRecordingSubscriptions returned false because the offer failed after retries, meaning the archive never received the query. This is a control-channel transport failure, not an empty result.

Solutions

  1. Reconnect the archive client (close context, AeronArchive.connect(ctx)) and retry the listing.
  2. Verify the control channel/stream configuration matches the running archive (aeron.archive.dir, control-channel in archive config).
  3. Check archive logs and Aeron error log for publication CLOSED or back-pressure events around the failure.
  4. Retry with backoff if the archive is temporarily overloaded; spread out polling calls.
  5. Ensure a single thread owns the AeronArchive (it is not reentrant); concurrent use can corrupt session state leading to failed offers.

Example fix

// before
int count = archive.listRecordingSubscriptions(0, 10, "", streamId, false, consumer);
// after
int count;
try {
    count = archive.listRecordingSubscriptions(0, 10, "", streamId, false, consumer);
} catch (ArchiveException e) {
    if (e.message().contains("failed to send")) {
        archive = reconnectQuietly(archive);
        count = archive.listRecordingSubscriptions(0, 10, "", streamId, false, consumer);
    } else {
        throw e;
    }
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (archive == null || archive.context().isClosed()) { archive = AeronArchive.connect(ctx); }

Type guard

static boolean isSendFailure(ArchiveException e) { return e.getMessage() != null && e.getMessage().contains("failed to send"); }

Try / catch

try { archive.listRecordingSubscriptions(0, n, fragment, streamId, applyStreamId, consumer); } catch (ArchiveException e) { if (isSendFailure(e)) { archive = reconnect(archive); } else { throw e; } }

Prevention

When it happens

Trigger: Calling listRecordingSubscriptions(cursor, subscriptionCount, channelFragment, streamId, applyStreamId, consumer) while the control session publication is closed, disconnected, or persistently back-pressured (offer retried to the limit and failed).

Common situations: Long-running archive client whose session was reaped by the archive; control channel congestion under heavy polling load; archive restarted while a listing client was idle; misconfigured control channel after a version/config change.

Understand the failure class

Background: "API request failed": what wrapped HTTP errors from external APIs mean and how to find the real cause — this error's family across 29 libraries.

Related errors


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

Appendix: source

Thrown at aeron-archive/src/main/java/io/aeron/archive/client/AeronArchive.java:1773

        lock.lock();
        try
        {
            ensureConnected();
            ensureNotReentrant();

            isInCallback = true;
            lastCorrelationId = aeron.nextCorrelationId();

            if (!archiveProxy.listRecordingSubscriptions(
                pseudoIndex,
                subscriptionCount,
                channelFragment,
                streamId,
                applyStreamId,
                lastCorrelationId,
                controlSessionId))
            {
                throw new ArchiveException("failed to send list recording subscriptions request");
            }

            return pollForSubscriptionDescriptors(lastCorrelationId, subscriptionCount, consumer);
        }
        finally
        {
            isInCallback = false;
            lock.unlock();
        }
    }

    /**
     * Replicate a recording from a source archive to a destination which can be considered a backup for a primary
     * archive. The source recording will be replayed via the provided replay channel and use the original stream id.
     * If the destination recording id is {@link io.aeron.Aeron#NULL_VALUE} then a new destination recording is created,
     * otherwise the provided destination recording id will be extended. The details of the source recording
     * descriptor will be replicated.
     * <p>

View on GitHub (pinned to 6d60124e15)