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
- Check archive connectivity before the call: verify ctx.controlResponseChannel/StreamId and that the archive is running on the configured control channel.
- Wrap the operation with retry: catch ArchiveException, call context().close() and AeronArchive.connect() again, then retry purgeRecording once the session is re-established.
- Inspect archive logs for session timeout/close; increase archive control session timeout if sessions expire during long operations.
- Verify network reachability of the control channel (firewall, interface binding) and that the archive's control-channel counter tracks the same endpoint.
- 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
- Keep the AeronArchive session alive with periodic lightweight calls if it is long-lived and mostly idle.
- Recreate the AeronArchive after any archive restart or network reconfiguration.
- Never share one AeronArchive across threads; it is guarded as non-reentrant and misuse can corrupt session state.
- Monitor Aeron error logs and control publication counters for back-pressure/disconnects before they surface as this error.
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
- failed to send list recording subscriptions request
- failed to send replicate request
- failed to send tagged replicate request
- failed to send stop replication request
- failed to send detach segments request
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)