package jj.execution;
import java.time.Clock;
import java.util.List;
import java.util.concurrent.DelayQueue;
import javax.inject.Inject;
import javax.inject.Singleton;
import jj.JJServerLifecycle;
import jj.event.Publisher;
import jj.logging.Emergency;
import jj.util.Closer;
/**
* exposes some execution related information and
* provides the API for execution services in the
* system. DO NOT DEPEND ON THIS DIRECTLY, use the
* interface
*
* @author jason
*
*/
@Singleton
class TaskRunnerImpl implements TaskRunner {
private final Executors executors;
private final CurrentTask currentTask;
private final Publisher publisher;
private final Clock clock;
private final JJServerLifecycle lifecycle;
private final DelayQueue<TaskTracker> queuedTasks = new DelayQueue<>();
private final ServerTask monitor = new ServerTask(getClass().getSimpleName() + " execution monitor") {
private void log(TaskTracker taskTracker, JJTask<?> task) {
publisher.publish(new Emergency(
"{} has been waiting {} milliseconds to execute. something is broken", task, clock.millis() + taskTracker.enqueuedTime()
));
}
@Override
public void run() throws Exception {
while (true) {
final TaskTracker taskTracker = queuedTasks.take();
final JJTask<?> task = taskTracker.task();
if (taskTracker.startTime() == 0
&& task != null
&& ((!(task instanceof DelayedTask<?>)) || !((DelayedTask<?>) task).cancelKey().canceled())
) {
log(taskTracker, task);
}
}
}
};
@Inject
TaskRunnerImpl(
Executors bundle,
CurrentTask currentTask,
Publisher publisher,
Clock clock,
JJServerLifecycle lifecycle
) {
this.executors = bundle;
this.currentTask = currentTask;
this.publisher = publisher;
this.clock = clock;
this.lifecycle = lifecycle;
execute(monitor);
}
@Override
public <ExecutorType> Promise execute(final JJTask<ExecutorType> task) {
final Promise promise = task.promise().taskRunner(this);
final TaskTracker tracker = new TaskTracker(clock, task);
tracker.enqueue();
queuedTasks.add(tracker);
executors.executeTask(task, () -> {
String oldName = Thread.currentThread().getName();
String threadName = oldName + " - " + task.name();
Thread.currentThread().setName(threadName);
boolean interrupted = false;
queuedTasks.remove(tracker);
tracker.start();
try (Closer closer = currentTask.enterScope(task)) {
task.runningThread = Thread.currentThread();
task.run();
} catch (InterruptedException ie) {
Thread.interrupted(); // clear the status in case the thread can get reused
interrupted = true;
} catch (AssertionError ae) {
System.err.println("ASSERTION TRIPPED");
ae.printStackTrace();
lifecycle.stop();
} catch (OutOfMemoryError e) {
throw e; // just in case
} catch (Throwable t) {
if (!task.errored(t)) {
publisher.publish(new Emergency("Task [" + task.name() + "] ended in exception", t));
tracker.endedInError();
}
} finally {
task.runningThread = null;
tracker.end();
// interruption means don't bother keeping promises
if (!interrupted) {
List<JJTask<?>> tasks = promise.done();
if (tasks != null) {
tasks.forEach(this::execute);
}
}
publisher.publish(tracker);
Thread.currentThread().setName(oldName);
}
});
return promise;
}
}