apache/druid · error · IllegalStateException
Already started
Error message
Already started
What it means
MessageRelay's internal listener uses a watermark AtomicReference to ensure start() is idempotent: it CASes the watermark from INIT to 0. If the watermark is no longer INIT, the relay has already been started, and calling start() again throws ISE('Already started'). This protects against double registration of the server listener and duplicate message fetch loops.
Source
Thrown at server/src/main/java/org/apache/druid/messages/client/MessageRelay.java:109
/**
* Retrieves messages that are being sent to this client and hands them to {@link #listener}.
*/
private class Collector
{
private final MessageListener<MessageType> listener;
private final AtomicLong epoch = new AtomicLong(INIT);
private final AtomicLong watermark = new AtomicLong(INIT);
private final AtomicReference<ListenableFuture<?>> currentCall = new AtomicReference<>();
public Collector(final MessageListener<MessageType> listener)
{
this.listener = listener;
}
private void start()
{
if (!watermark.compareAndSet(INIT, 0)) {
throw new ISE("Already started");
}
listener.serverAdded(serverNode);
issueNextGetMessagesCall();
}
private void issueNextGetMessagesCall()
{
if (closed.get()) {
return;
}
final long theEpoch = epoch.get();
final long theWatermark = watermark.get();
log.debug(
"Getting messages from server[%s] for client[%s] (current state: epoch[%s] watermark[%s]).",
serverNode.getHostAndPortToUse(),View on GitHub (pinned to 9b90983fd2)
Solutions
- Ensure start() is invoked exactly once per MessageRelay instance; recreate the relay object before restarting
- Guard the restart path so lifecycle stop() fully resets or replaces the relay before start() runs
- Serialize initialization with a single owner (e.g. lifecycle start/stop management) to avoid concurrent start calls
Example fix
// before
relay.setListener(listener);
relay.start(); // called again on restart -> ISE
// after
if (relay != null) { relay.stop(); }
relay = new MessageRelay(...);
relay.setListener(listener);
relay.start(); Defensive patterns
Strategy: try-catch
Try / catch
try { relay.start(); } catch (IllegalStateException e) { if (!e.getMessage().contains("Already started")) { throw e; } log.debug("MessageRelay already started, ignoring"); } Prevention
- Call start() only once per MessageRelay instance; recreate the object on restart
- Manage the relay through Druid's Lifecycle so stop/start are paired
- Avoid concurrent initialization of the same relay from multiple threads
When it happens
Trigger: Calling the private start() twice on the same MessageRelay instance — e.g. a lifecycle supervisor restart that does not recreate the relay object, or racing start() calls from two threads where the second loses the CAS.
Common situations: Server lifecycle restart/reconfiguration paths that re-invoke start without constructing a new MessageRelay; concurrent initialization during coordinator leadership transitions.
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
- Emit called unexpectedly before service start
- Factory [%s] not started
- Null queryRunner! Looks to be some segment unmapping action
- Already started, not starting again
- can't stop.
AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07).
Data as JSON: /api/errors/5e0c295634be100f.
Report an issue: GitHub.