eclipse-vertx/vert.x · warning · IllegalStateException

Already closed

Error message

Already closed

What it means

TaskQueue.close() drains and suspends outstanding tasks and threads; calling it a second time finds the closed flag set and throws IllegalStateException('Already closed'). The queue cannot be closed twice nor reused after close.

Source

Thrown at vertx-core/src/main/java/io/vertx/core/impl/TaskQueue.java:215

     */
    public List<Runnable> suspendedTasks() {
      return suspendedTasks;
    }
  }

  /**
   * Close the queue.
   *
   * @return a structure of suspended threads and pending tasks
   */
  public CloseResult close() {
    List<Thread> suspendedThreads;
    List<Runnable> suspendedTasks;
    Thread activeThread;
    Runnable activeTask;
    synchronized (tasks) {
      if (closed) {
        throw new IllegalStateException("Already closed");
      }
      suspendedThreads = new ArrayList<>(continuations.size());
      suspendedTasks = new ArrayList<>(continuations.size());
      Iterator<Task> it = tasks.iterator();
      while (it.hasNext()) {
        Task task = it.next();
        if (task instanceof ContinuationTask) {
          ContinuationTask continuationTask = (ContinuationTask) task;
          suspendedThreads.add(continuationTask.thread);
          suspendedTasks.add(continuationTask.task.runnable);
          it.remove();
        }
      }
      for (ContinuationTask cont : continuations) {
        suspendedThreads.add(cont.thread);
        suspendedTasks.add(cont.task.runnable);
      }
      continuations.clear();

View on GitHub (pinned to fb308bd8c3)

Solutions

  1. Track closure with an AtomicBoolean and skip the second close()
  2. Route all shutdown through one owner (single lifecycle hook) instead of multiple call sites
  3. If sharing the queue, wrap it in a facade exposing an idempotent close
  4. Do not reuse a closed TaskQueue — create a new one

Example fix

// before
queue.close();
cleanup();
queue.close(); // IllegalStateException
// after
if (closedRef.compareAndSet(false, true)) {
  queue.close();
  cleanup();
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (!closedRef.compareAndSet(false, true)) return; // idempotent close

Type guard

boolean isOpen(TaskQueue q){ return !closedRef.get(); }

Try / catch

try { queue.close(); } catch (IllegalStateException e) { if (!"Already closed".equals(e.getMessage())) throw e; /* ignore double close */ }

Prevention

When it happens

Trigger: Calling close() twice on the same TaskQueue; submitting after close (related) ; concurrent close from multiple threads racing; lifecycle code that closes on both failure and completion paths (tests result/th/testCloseBeforeResumeExecution/res/testSubmitAfterClose exercise these paths).

Common situations: Double shutdown in finally blocks without idempotency guard; closing a shared TaskQueue owned by a Vert.x internal (e.g. context-ordered execution) from user code; shutting down a worker pool twice.

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 eclipse-vertx/vert.x@fb308bd8c3 (2026-09-06). Data as JSON: /api/errors/ff381532184d7971. Report an issue: GitHub.