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.

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.

Appendix: 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)