aeron-io/aeron · error · ArchiveException

failed to send invalidate recording request

Error message

failed to send invalidate recording request

What it means

AeronArchive throws this when the client could not offer a purgeRecording request onto the archive control publication. ArchiveProxy.purgeRecording returns false when the publication offer fails (session closed or back-pressured after retries), so the request never reached the archive. It indicates a transport/control-session problem, not a problem with the recordingId itself.

Solutions

  1. Check archive connectivity before the call: verify ctx.controlResponseChannel/StreamId and that the archive is running on the configured control channel.
  2. Wrap the operation with retry: catch ArchiveException, call context().close() and AeronArchive.connect() again, then retry purgeRecording once the session is re-established.
  3. Inspect archive logs for session timeout/close; increase archive control session timeout if sessions expire during long operations.
  4. Verify network reachability of the control channel (firewall, interface binding) and that the archive's control-channel counter tracks the same endpoint.
  5. If the offer is persistently back-pressured, reduce load or use a dedicated control session (AeronArchive.Configuration control channel tuning).

Example fix

// before
archive.purgeRecording(recordingId);
// after
if (!archive.context().controlSessionId() != 0 && archive.context().isClosed()) {
    archive = AeronArchive.connect(archive.context());
}
try {
    archive.purgeRecording(recordingId);
} catch (ArchiveException e) {
    if (e.message().contains("failed to send")) {
        archive.close();
        archive = AeronArchive.connect(ctx);
        archive.purgeRecording(recordingId);
    } 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().startsWith("failed to send"); }

Try / catch

try { archive.purgeRecording(recordingId); } catch (ArchiveException e) { if (isSendFailure(e)) { archive = AeronArchive.connect(ctx); archive.purgeRecording(recordingId); } else { throw e; } }

Prevention

When it happens

Trigger: Calling AeronArchive.purgeRecording(recordingId) while the control session is no longer connected, or when the control response/publication is back-pressured and the offer fails after ArchiveProxy retries. Also raised if the archive has closed the session (e.g. timeout, archive shutdown).

Common situations: Archive process restarted between connect and purge; control channel misconfigured so the publication never connects; slow archive leaving the exclusive control publication back-pressured; using an AeronArchive whose underlying session was closed by another thread.

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/34b3aac02f6ba0a3. Report an issue: GitHub.

Appendix: source

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

     * Purge a stopped recording, i.e. mark recording as {@link io.aeron.archive.codecs.RecordingState#INVALID}
     * and delete the corresponding segment files. The space in the Catalog will be reclaimed upon compaction.
     *
     * @param recordingId of the stopped recording to be purged.
     * @return count of deleted segment files.
     */
    public long purgeRecording(final long recordingId)
    {
        lock.lock();
        try
        {
            ensureConnected();
            ensureNotReentrant();

            lastCorrelationId = aeron.nextCorrelationId();

            if (!archiveProxy.purgeRecording(recordingId, lastCorrelationId, controlSessionId))
            {
                throw new ArchiveException("failed to send invalidate recording request");
            }

            return pollForResponse(lastCorrelationId);
        }
        finally
        {
            lock.unlock();
        }
    }

    /**
     * List active recording subscriptions in the archive. These are the result of requesting one of
     * {@link #startRecording(String, int, SourceLocation)} or a
     * {@link #extendRecording(long, String, int, SourceLocation)}. The returned subscription id can be used for
     * passing to {@link #stopRecording(long)}.
     *
     * @param pseudoIndex       in the active list at which to begin for paging.
     * @param subscriptionCount to get in a listing.

View on GitHub (pinned to 6d60124e15)