apache/druid · error · IllegalStateException
Cannot run() more than once.
Error message
Cannot run() more than once.
What it means
SuperSorter.run() starts the sorting pipeline and may only be called once per SuperSorter instance; it records completion in an internal SettableFuture (allDone). A second call finds allDone already set and throws IllegalStateException. The instance is single-use by design because its internal channels and worker state are consumed by the first run().
Source
Thrown at processing/src/main/java/org/apache/druid/frame/processor/SuperSorter.java:291
throw new IAE("maxChannelsPerMerger[%d] < 2", maxChannelsPerMerger);
}
if (rowLimit != UNLIMITED && rowLimit <= 0) {
throw new IAE("rowLimit[%d] must be positive", rowLimit);
}
}
/**
* Starts sorting. Can only be called once. Work is performed in the {@link FrameProcessorExecutor} that was
* passed to the constructor.
*
* Returns a future containing partitioned sorted output channels.
*/
public ListenableFuture<OutputChannels> run()
{
synchronized (runWorkersLock) {
if (allDone != null) {
throw new ISE("Cannot run() more than once.");
}
allDone = SettableFuture.create();
runWorkersIfPossible();
// When output partitions become known, that may unblock some additional layers of merging.
outputPartitionsFuture.addListener(
() -> {
synchronized (runWorkersLock) {
if (outputPartitionsFuture.isDone()) { // Update the progress tracker
superSorterProgressTracker.setTotalMergersForUltimateLevel(getOutputPartitions().size());
}
runWorkersIfPossible();
setAllDoneIfPossible();
}
},
exec.asExecutor(cancellationId)
);View on GitHub (pinned to 9b90983fd2)
Solutions
- Create a new SuperSorter instance for each sorting operation and call run() exactly once.
- Guard call sites so run() executes once, e.g. capture the returned future and reuse it instead of calling run() again.
- If you need the result, hold the ListenableFuture<OutputChannels> returned by the first run() and await it rather than re-running.
- Restructure retry logic to retry the whole pipeline (new SuperSorter) rather than the run() call.
Example fix
// before sorter.run(); OutputChannels channels = sorter.run().get(); // second call -> ISE // after ListenableFuture<OutputChannels> future = sorter.run(); OutputChannels channels = FutureUtils.getUnchecked(future, true);
Defensive patterns
Strategy: try-catch
Validate before calling
// track one-time use explicitly
private final AtomicBoolean started = new AtomicBoolean();
if (!started.compareAndSet(false, true)) {
throw new IllegalStateException("SuperSorter already started");
} Try / catch
try {
future = sorter.run();
} catch (IllegalStateException e) {
// already run: reuse previously captured future
future = previouslyCapturedFuture;
} Prevention
- Treat SuperSorter as single-use: one instance per sort operation.
- Store the future returned by run() and reuse it instead of re-calling run().
- Wrap run() in an idempotent accessor that memoizes the future.
- Never cache SuperSorter instances across requests.
When it happens
Trigger: Calling run() a second time on the same SuperSorter instance — e.g. retry logic that re-invokes run() after a failure, code that calls run() in both an initialization path and an execution path, or reusing a cached SuperSorter across queries instead of constructing a new one.
Common situations: Application code memoizing/reusing a SuperSorter field across requests; retry wrappers that assume restartable operations; refactoring that moved run() into a method invoked more than once (directly or via the channels()/outputChannels() accessors that can trigger workers).
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
AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07).
Data as JSON: /api/errors/560dd8fc2b6c659c.
Report an issue: GitHub.