diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index 6e2765202..095fd5e86 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -59,9 +59,14 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.UUID; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.IntFunction; import java.util.stream.Collectors; import net.kyori.text.Component; import net.kyori.text.TextComponent; @@ -379,14 +384,35 @@ public class VelocityServer implements ProxyServer { Runnable shutdownProcess = () -> { logger.info("Shutting down the proxy..."); - for (ConnectedPlayer player : ImmutableList.copyOf(connectionsByUuid.values())) { + // Shutdown the connection manager, this should be + // done first to refuse new connections + cm.shutdown(); + + ImmutableList players = ImmutableList.copyOf(connectionsByUuid.values()); + for (ConnectedPlayer player : players) { player.disconnect(TextComponent.of("Proxy shutting down.")); } - this.cm.shutdown(); - try { - if (!eventManager.shutdown() || !scheduler.shutdown()) { + boolean timedOut = false; + + try { + // Wait for the connections finish tearing down, this + // makes sure that all the disconnect events are being fired + + CompletableFuture playersTeardownFuture = CompletableFuture.allOf(players.stream() + .map(ConnectedPlayer::getTeardownFuture) + .toArray((IntFunction[]>) CompletableFuture[]::new)); + + playersTeardownFuture.get(10, TimeUnit.SECONDS); + } catch (TimeoutException | ExecutionException e) { + timedOut = true; + } + + timedOut = !eventManager.shutdown() || timedOut; + timedOut = !scheduler.shutdown() || timedOut; + + if (timedOut) { logger.error("Your plugins took over 10 seconds to shut down."); } } catch (InterruptedException e) { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java index 9a8acb732..4d37cd324 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java @@ -58,6 +58,7 @@ import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; +import java.util.concurrent.ExecutionException; import java.util.concurrent.ThreadLocalRandom; import net.kyori.text.Component; import net.kyori.text.TextComponent; @@ -97,6 +98,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { private final VelocityServer server; private ClientConnectionPhase connectionPhase; private final Collection knownChannels; + private final CompletableFuture teardownFuture = new CompletableFuture<>(); private @MonotonicNonNull List serversToTry = null; @@ -554,7 +556,12 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { connectedServer.disconnect(); } server.unregisterConnection(this); - server.getEventManager().fireAndForget(new DisconnectEvent(this)); + server.getEventManager().fire(new DisconnectEvent(this)) + .thenRun(() -> this.teardownFuture.complete(null)); + } + + public CompletableFuture getTeardownFuture() { + return teardownFuture; } @Override