apache/dolphinscheduler · error · IllegalStateException

"InternalThread is already started"

Error message

"InternalThread is already started"

What it means

Second guard in WorkflowSerialCoordinator.start(): if internalThread is already created, the coordinator's background thread was started and start() cannot run again. Distinct from the flag check to catch direct thread mutation.

Source

Thrown at dolphinscheduler-master/src/main/java/org/apache/dolphinscheduler/server/master/engine/workflow/serial/WorkflowSerialCoordinator.java:83

    @Autowired
    private SerialCommandPriorityHandler serialCommandPriorityHandler;

    private volatile boolean flag = false;

    private Thread internalThread;

    private static final int DEFAULT_FETCH_SIZE = 1000;

    private static final int DEFAULT_FETCH_INTERVAL_SECONDS = 5;

    @Override
    public synchronized void start() {
        log.info("WorkflowSerialCoordinator starting...");
        if (flag) {
            throw new IllegalStateException("WorkflowSerialCoordinator is already started");
        }
        if (internalThread != null) {
            throw new IllegalStateException("InternalThread is already started");
        }
        flag = true;
        internalThread = new BaseDaemonThread(this::doStart) {
        };
        internalThread.setName("WorkflowSerialCoordinator-Thread");
        internalThread.start();
        log.info("WorkflowSerialCoordinator started...");
    }

    private void doStart() {
        while (flag) {
            try {
                final StopWatch workflowSerialCoordinatorRoundCost = StopWatch.createStarted();
                final List<SerialCommandsGroup> serialCommandsGroups = fetchSerialCommands();
                serialCommandsGroups.forEach(this::handleSerialCommand);
                log.debug("WorkflowSerialCoordinator handled SerialCommandsGroup size: {}, cost: {}/ms ",
                        serialCommandsGroups.size(),
                        workflowSerialCoordinatorRoundCost.getDuration().toMillis());

View on GitHub (pinned to 02eac45a1b)

Solutions

  1. Only start the coordinator once per process lifetime
  2. Recreate the coordinator object instead of re-calling start()
  3. Remove duplicate start invocations from lifecycle code
  4. Reset internalThread=null only after a proper close()/stop()

Example fix

// before
internalThread = null; coordinator.start(); // restart attempt
// after
coordinator.close();
WorkflowSerialCoordinator fresh = new WorkflowSerialCoordinator(...);
fresh.start();
Defensive patterns

Strategy: validation

Validate before calling

if (coordinator.isStarted()) { log.warn("already started"); return; }

Try / catch

try { coordinator.start(); } catch (IllegalStateException e) { log.warn("start ignored: {}", e.getMessage()); }

Prevention

When it happens

Trigger: Repeated start() calls where the flag may have been reset but internalThread is non-null; double initialization of the coordinator.

Common situations: Same as double-start: duplicate lifecycle hooks, tests reusing an instance, manual restart attempts.

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/dolphinscheduler@02eac45a1b (2026-09-06). Data as JSON: /api/errors/0dc00e9b87d8b993. Report an issue: GitHub.