Introduce SchedulerBackend to fix VelocitySchedulerTest intermittent failure (#1728)

This commit is contained in:
Wouter Gritter
2026-02-11 22:21:03 +01:00
committed by GitHub
parent 2535751cd9
commit c2fd3c07ac
5 changed files with 433 additions and 32 deletions

View File

@@ -0,0 +1,70 @@
/*
* Copyright (C) 2018-2026 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.scheduler;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
/**
* A {@link SchedulerBackend} backed by a real {@link ScheduledExecutorService}.
*/
public class ExecutorSchedulerBackend implements SchedulerBackend {
private final ScheduledExecutorService executor;
/**
* Creates a ExecutorSchedulerBackend with a default executor.
*/
public ExecutorSchedulerBackend() {
this(Executors.newSingleThreadScheduledExecutor(
new ThreadFactoryBuilder()
.setDaemon(true)
.setNameFormat("Velocity Task Scheduler Timer")
.build()
));
}
/**
* Creates a ExecutorSchedulerBackend with a given executor.
*
* @param executor The executor to use.
*/
public ExecutorSchedulerBackend(ScheduledExecutorService executor) {
this.executor = checkNotNull(executor, "executor");
}
@Override
public ScheduledFuture<?> schedule(Runnable task, long delay, TimeUnit unit) {
return executor.schedule(task, delay, unit);
}
@Override
public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, long initialDelay, long period, TimeUnit unit) {
return executor.scheduleAtFixedRate(task, initialDelay, period, unit);
}
@Override
public void shutdown() {
executor.shutdown();
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright (C) 2018-2026 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.scheduler;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
/**
* Backend interface used by {@link VelocityScheduler} to schedule timer callbacks.
*
* <p>This is an internal abstraction that allows tests to replace the real-time scheduler
* with a deterministic implementation.
*/
interface SchedulerBackend {
/**
* Schedules a task to run once after the given delay.
*
* @param task the task to run
* @param delay the delay
* @param unit the delay unit
* @return a future representing the scheduled task
*/
ScheduledFuture<?> schedule(Runnable task, long delay, TimeUnit unit);
/**
* Schedules a task to run at a fixed rate.
*
* @param task the task to run
* @param initialDelay the initial delay
* @param period the period between runs
* @param unit the time unit
* @return a future representing the scheduled task
*/
ScheduledFuture<?> scheduleAtFixedRate(Runnable task, long initialDelay, long period, TimeUnit unit);
/**
* Shuts down the backend.
*/
void shutdown();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2018-2023 Velocity Contributors
* Copyright (C) 2018-2026 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -23,7 +23,6 @@ import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.velocitypowered.api.plugin.PluginContainer;
import com.velocitypowered.api.plugin.PluginManager;
import com.velocitypowered.api.scheduler.ScheduledTask;
@@ -40,7 +39,6 @@ import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
@@ -60,20 +58,23 @@ import org.jetbrains.annotations.VisibleForTesting;
public class VelocityScheduler implements Scheduler {
private final PluginManager pluginManager;
private final ScheduledExecutorService timerExecutionService;
private final SchedulerBackend backend;
private final Multimap<Object, ScheduledTask> tasksByPlugin = Multimaps.synchronizedMultimap(
Multimaps.newSetMultimap(new IdentityHashMap<>(), HashSet::new));
/**
* Initalizes the scheduler.
* Initializes the scheduler.
*
* @param pluginManager the Velocity plugin manager
*/
public VelocityScheduler(PluginManager pluginManager) {
this(pluginManager, new ExecutorSchedulerBackend());
}
@VisibleForTesting
VelocityScheduler(PluginManager pluginManager, SchedulerBackend backend) {
this.pluginManager = pluginManager;
this.timerExecutionService = Executors
.newSingleThreadScheduledExecutor(new ThreadFactoryBuilder().setDaemon(true)
.setNameFormat("Velocity Task Scheduler Timer").build());
this.backend = backend;
}
@Override
@@ -118,7 +119,7 @@ public class VelocityScheduler implements Scheduler {
for (ScheduledTask task : terminating) {
task.cancel();
}
timerExecutionService.shutdown();
backend.shutdown();
final List<PluginContainer> plugins = new ArrayList<>(this.pluginManager.getPlugins());
final Iterator<PluginContainer> pluginIterator = plugins.iterator();
while (pluginIterator.hasNext()) {
@@ -232,10 +233,9 @@ public class VelocityScheduler implements Scheduler {
void schedule() {
if (repeat == 0) {
this.future = timerExecutionService.schedule(this, delay, TimeUnit.MILLISECONDS);
this.future = backend.schedule(this, delay, TimeUnit.MILLISECONDS);
} else {
this.future = timerExecutionService
.scheduleAtFixedRate(this, delay, repeat, TimeUnit.MILLISECONDS);
this.future = backend.scheduleAtFixedRate(this, delay, repeat, TimeUnit.MILLISECONDS);
}
}