/* * This file is part of LanternServer, licensed under the MIT License (MIT). * * Copyright (c) LanternPowered <https://www.lanternpowered.org> * Copyright (c) SpongePowered <https://www.spongepowered.org> * Copyright (c) contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the Software), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.lanternpowered.server.scheduler; import org.lanternpowered.server.game.Lantern; import org.spongepowered.api.scheduler.Task; import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; final class AsyncScheduler extends SchedulerBase { // Adjustable timeout for pending Tasks private long minimumTimeout = Long.MAX_VALUE; private long lastProcessingTimestamp; // Locking mechanism private final Lock lock = new ReentrantLock(); private final Condition condition = this.lock.newCondition(); // The dynamic thread pooling executor of asynchronous tasks. private ExecutorService executor; AsyncScheduler() { super(ScheduledTask.TaskSynchronicity.ASYNCHRONOUS); Thread thread = new Thread(AsyncScheduler.this::mainLoop); thread.setName("Lantern Async Scheduler Thread"); thread.setDaemon(true); thread.start(); } void shutdown() { this.executor.shutdown(); } private void mainLoop() { this.executor = Executors.newCachedThreadPool(); this.lastProcessingTimestamp = System.nanoTime(); while (true) { this.recalibrateMinimumTimeout(); this.runTick(); } } private void recalibrateMinimumTimeout() { this.lock.lock(); try { Set<Task> tasks = this.getScheduledTasks(); this.minimumTimeout = Long.MAX_VALUE; long now = System.nanoTime(); for (Task tmpTask : tasks) { ScheduledTask task = (ScheduledTask) tmpTask; // Recalibrate the wait delay for processing tasks before new // tasks cause the scheduler to process pending tasks. if (task.offset == 0 && task.period == 0) { this.minimumTimeout = 0; } // The time since the task last executed or was added to the map long timeSinceLast = now - task.getTimestamp(); if (task.offset > 0 && task.getState() == ScheduledTask.ScheduledTaskState.WAITING) { // There is an offset and the task hasn't run yet this.minimumTimeout = Math.min(task.offset - timeSinceLast, this.minimumTimeout); } if (task.period > 0 && task.getState().isActive) { // The task repeats and has run after the initial delay this.minimumTimeout = Math.min(task.period - timeSinceLast, this.minimumTimeout); } if (this.minimumTimeout <= 0) { break; } } if (!tasks.isEmpty()) { long latency = System.nanoTime() - this.lastProcessingTimestamp; this.minimumTimeout -= (latency <= 0) ? 0 : latency; this.minimumTimeout = (this.minimumTimeout < 0) ? 0 : this.minimumTimeout; } } finally { this.lock.unlock(); } } @Override protected void preTick() { this.lock.lock(); try { this.condition.await(this.minimumTimeout, TimeUnit.NANOSECONDS); } catch (InterruptedException ignored) { // The taskMap has been modified; there is work to do. // Continue on without handling the Exception. } catch (IllegalMonitorStateException e) { Lantern.getLogger().error("The scheduler internal state machine suffered a catastrophic error", e); } } @Override protected void postTick() { this.lastProcessingTimestamp = System.nanoTime(); } @Override protected void finallyPostTick() { this.lock.unlock(); } @Override protected void executeTaskRunnable(Runnable runnable) { this.executor.submit(runnable); } @Override protected void addTask(ScheduledTask task) { this.lock.lock(); try { super.addTask(task); this.condition.signalAll(); } finally { this.lock.unlock(); } } public Executor getExecutor() { return this.executor; } }