All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.vertx.core.impl.TaskQueue Maven / Gradle / Ivy

/*
 * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
 * which is available at https://www.apache.org/licenses/LICENSE-2.0.
 *
 * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
 */

package io.vertx.core.impl;

import io.vertx.core.internal.logging.Logger;
import io.vertx.core.internal.logging.LoggerFactory;

import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import java.util.function.Consumer;

/**
 * A task queue that always run all tasks in order. The executor to run the tasks is passed
 * when the tasks are executed, this executor is not guaranteed to be used, as if several
 * tasks are queued, the original thread will be used.
 *
 * More specifically, any call B to the {@link #execute(Runnable, Executor)} method that happens-after another call A to the
 * same method, will result in B's task running after A's.
 *
 * @author David Lloyd
 * @author Tim Fox
 * @author Julien Viet
 */
public class TaskQueue {

  static final Logger log = LoggerFactory.getLogger(TaskQueue.class);

  // @protectedby tasks
  private final LinkedList tasks = new LinkedList<>();
  private final Set continuations = new HashSet<>();
  private boolean closed;
  private Executor currentExecutor;
  private Thread currentThread;
  private ExecuteTask currentTask;

  private final Runnable runner;

  public TaskQueue() {
    runner = this::run;
  }

  private void run() {
    for (; ; ) {
      final ExecuteTask execute;
      synchronized (tasks) {
        Task task = tasks.poll();
        if (task == null) {
          currentExecutor = null;
          return;
        }
        if (task instanceof ContinuationTask) {
          ContinuationTask resume = (ContinuationTask) task;
          currentExecutor = resume.executor;
          currentThread = resume.thread;
          currentTask = resume.task;
          resume.latch.run();
          return;
        }
        execute = (ExecuteTask) task;
        if (execute.exec != currentExecutor) {
          tasks.addFirst(execute);
          execute.exec.execute(runner);
          currentExecutor = execute.exec;
          return;
        }
      }
      try {
        currentThread = Thread.currentThread();
        currentTask = execute;
        execute.runnable.run();
      } catch (Throwable t) {
        log.error("Caught unexpected Throwable", t);
      } finally {
        currentThread = null;
        currentTask = null;
      }
    }
  }

  /**
   * A task of this queue.
   */
  private interface Task {
  }

  /**
   * Return a continuation task for the current task execution.
   *
   * @return the controller
   * @throws IllegalStateException if the current thread is not currently being executed by the queue
   */
  private ContinuationTask continuationTask() {
    ExecuteTask task;
    Thread thread;
    Executor executor;
    synchronized (tasks) {
      if (Thread.currentThread() != currentThread) {
        throw new IllegalStateException();
      }
      thread = currentThread;
      executor = currentExecutor;
      task = currentTask;
    }
    return new ContinuationTask(task, thread, executor);
  }

  /**
   * Run a task.
   *
   * @param task the task to run.
   */
  public void execute(Runnable task, Executor executor) throws RejectedExecutionException {
    synchronized (tasks) {
      if (currentExecutor == null) {
        currentExecutor = executor;
        try {
          executor.execute(runner);
        } catch (RejectedExecutionException e) {
          currentExecutor = null;
          throw e;
        }
      }
      // Add the task after the runner has been accepted to the executor
      // to cover the case of a rejected execution exception.
      tasks.add(new ExecuteTask(task, executor));
    }
  }

  /**
   * Test if the task queue is empty and no current executor is running anymore.
   */
  public boolean isEmpty() {
    synchronized (tasks) {
      return tasks.isEmpty() && currentExecutor == null;
    }
  }

  /**
   * Structure holding the queue state at close time.
   */
  public final static class CloseResult {

    private final Thread activeThread;
    private final Runnable activeTask;
    private final List suspendedTasks;
    private final List suspendedThreads;

    private CloseResult(Thread activeThread,
                        Runnable activeTask,
                        List suspendedThreads,
                        List suspendedTasks) {
      this.activeThread = activeThread;
      this.activeTask = activeTask;
      this.suspendedThreads = suspendedThreads;
      this.suspendedTasks = suspendedTasks;
    }

    /**
     * @return the thread that was active
     */
    public Thread activeThread() {
      return activeThread;
    }

    public Runnable activeTask() {
      return activeTask;
    }

    /**
     * @return the list of suspended threads
     */
    public List suspendedThreads() {
      return suspendedThreads;
    }

    /**
     * @return the list of suspended tasks
     */
    public List suspendedTasks() {
      return suspendedTasks;
    }
  }

  /**
   * Close the queue.
   *
   * @return a structure of suspended threads and pending tasks
   */
  public CloseResult close() {
    List suspendedThreads;
    List 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 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();
      activeThread = currentThread;
      activeTask = currentTask != null ? currentTask.runnable : null;
      currentExecutor = null;
      closed = true;
    }
    return new CloseResult(activeThread, activeTask, suspendedThreads, suspendedTasks);
  }

  private class ContinuationTask extends CountDownLatch implements WorkerExecutor.Continuation, Task {

    private static final int ST_CREATED = 0, ST_SUSPENDED = 1, ST_RESUMED = 2;

    private final ExecuteTask task;
    private final Thread thread;
    private final Executor executor;
    private int status;
    private Runnable latch;

    public ContinuationTask(ExecuteTask task, Thread thread, Executor executor) {
      super(1);
      this.task = task;
      this.thread = thread;
      this.executor = executor;
      this.status = ST_CREATED;
    }

    @Override
    public void resume(Runnable callback) {
      synchronized (tasks) {
        if (closed) {
          return;
        }
        switch (status) {
          case ST_SUSPENDED:
            boolean removed = continuations.remove(this);
            assert removed;
            latch = () -> {
              callback.run();
              countDown();
            };
            if (currentExecutor != null) {
              tasks.addFirst(this);
              return;
            }
            currentExecutor = executor;
            currentThread = thread;
            currentTask = task;
            break;
          case ST_CREATED:
            // The current task still owns the queue
            assert currentExecutor == executor;
            assert currentThread == thread;
            assert currentTask == task;
            latch = callback;
            break;
          default:
            throw new IllegalStateException();
        }
        status = ST_RESUMED;
      }
      latch.run();
    }

    public boolean suspend() {
      if (Thread.currentThread() != thread) {
        throw new IllegalStateException();
      }
      synchronized (tasks) {
        if (closed) {
          return false;
        }
        if (currentThread == null || currentThread != thread) {
          throw new IllegalStateException();
        }
        switch (status) {
          case ST_RESUMED:
            countDown();
            return false;
          case ST_SUSPENDED:
            throw new IllegalStateException();
        }
        status = ST_SUSPENDED;
        boolean added = continuations.add(this);
        assert added;
        currentThread = null;
        currentTask = null;
      }
      executor.execute(runner);
      return true;
    }
  }

  public CountDownLatch suspend() {
    return suspend(cont -> {});
  }

  public CountDownLatch suspend(Consumer abc) {
    ContinuationTask continuationTask = continuationTask();
    abc.accept(continuationTask);
    if (continuationTask.suspend()) {
      return continuationTask;
    } else {
      // Closed
      return null;
    }
  }

  /**
   * Execute another task
   */
  private static class ExecuteTask implements Task {
    private final Runnable runnable;
    private final Executor exec;
    public ExecuteTask(Runnable runnable, Executor exec) {
      this.runnable = runnable;
      this.exec = exec;
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy