diff --git a/api/src/main/java/com/velocitypowered/api/event/proxy/ProxyPreShutdownEvent.java b/api/src/main/java/com/velocitypowered/api/event/proxy/ProxyPreShutdownEvent.java new file mode 100644 index 00000000..893942c0 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/proxy/ProxyPreShutdownEvent.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2018-2025 Velocity Contributors + * + * The Velocity API is licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in the api top-level directory. + */ + +package com.velocitypowered.api.event.proxy; + +import com.google.common.annotations.Beta; +import com.velocitypowered.api.event.annotation.AwaitingEvent; + +/** + * This event is fired by the proxy after it has stopped accepting new connections, + * but before players are disconnected. + * This is the last point at which you can interact with currently connected players, + * for example to transfer them to another proxy or perform other cleanup tasks. + * + * @implNote Velocity will wait for all event listeners to complete before disconnecting players, + * but note that the event will time out after the configured value of the + * velocity.pre-shutdown-timeout system property, default 10 seconds, + * in seconds to prevent shutdown from hanging indefinitely + * @since 3.4.0 + */ +@Beta +@AwaitingEvent +public final class ProxyPreShutdownEvent { + + @Override + public String toString() { + return "ProxyPreShutdownEvent"; + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index 7d55ae23..ccfc5f14 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -24,6 +24,7 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.velocitypowered.api.command.BrigadierCommand; import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; +import com.velocitypowered.api.event.proxy.ProxyPreShutdownEvent; import com.velocitypowered.api.event.proxy.ProxyReloadEvent; import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; import com.velocitypowered.api.network.ProtocolVersion; @@ -150,6 +151,8 @@ public class VelocityServer implements ProxyServer, ForwardingAudience { ) .registerTypeHierarchyAdapter(Favicon.class, FaviconSerializer.INSTANCE) .create(); + private static final int PRE_SHUTDOWN_TIMEOUT = + Integer.getInteger("velocity.pre-shutdown-timeout", 10); private final ConnectionManager cm; private final ProxyOptions options; @@ -579,6 +582,20 @@ public class VelocityServer implements ProxyServer, ForwardingAudience { // done first to refuse new connections cm.shutdown(); + try { + eventManager.fire(new ProxyPreShutdownEvent()) + .toCompletableFuture() + .get(PRE_SHUTDOWN_TIMEOUT, TimeUnit.SECONDS); + } catch (TimeoutException ignored) { + logger.warn("Your plugins took over {} seconds during pre shutdown.", + PRE_SHUTDOWN_TIMEOUT); + } catch (ExecutionException ee) { + logger.error("Exception in ProxyPreShutdownEvent handler; continuing shutdown.", ee); + } catch (InterruptedException ignored) { + Thread.currentThread().interrupt(); + logger.warn("Interrupted while waiting for ProxyPreShutdownEvent; continuing shutdown."); + } + ImmutableList players = ImmutableList.copyOf(connectionsByUuid.values()); for (ConnectedPlayer player : players) { player.disconnect(reason);