Prevent players being disconnected two times during a clean stop, also made shutdown tasks being executed in a single thread

This commit is contained in:
Felix Cravic 2020-12-02 21:28:36 +01:00
parent 5de6e466ee
commit d61a598c94
7 changed files with 89 additions and 82 deletions

View File

@ -124,6 +124,7 @@ public final class MinecraftServer {
// Data
private static boolean initialized;
private static boolean started;
private static boolean stopping;
private static int chunkViewDistance = 8;
private static int entityViewDistance = 5;
@ -432,6 +433,15 @@ public final class MinecraftServer {
return started;
}
/**
* Gets if the server is currently being shutdown using {@link #stopCleanly()}.
*
* @return true if the server is being stopped
*/
public static boolean isStopping() {
return stopping;
}
/**
* Gets the chunk view distance of the server.
*
@ -721,9 +731,11 @@ public final class MinecraftServer {
* Stops this server properly (saves if needed, kicking players, etc.)
*/
public static void stopCleanly() {
stopping = true;
LOGGER.info("Stopping Minestom server.");
updateManager.stop();
schedulerManager.shutdown();
connectionManager.shutdown();
nettyServer.stop();
storageManager.getLoadedLocations().forEach(StorageLocation::close);
LOGGER.info("Shutting down all thread pools.");

View File

@ -596,7 +596,7 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer, P
sendSynchronization();
}
if (shouldRemove()) {
if (shouldRemove() && !MinecraftServer.isStopping()) {
remove();
}
}

View File

@ -1,21 +1,5 @@
package net.minestom.server.entity;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import net.minestom.server.MinecraftServer;
import net.minestom.server.advancements.AdvancementTab;
import net.minestom.server.attribute.Attribute;
@ -37,16 +21,7 @@ import net.minestom.server.event.inventory.InventoryOpenEvent;
import net.minestom.server.event.item.ItemDropEvent;
import net.minestom.server.event.item.ItemUpdateStateEvent;
import net.minestom.server.event.item.PickupExperienceEvent;
import net.minestom.server.event.player.PlayerChunkLoadEvent;
import net.minestom.server.event.player.PlayerChunkUnloadEvent;
import net.minestom.server.event.player.PlayerDisconnectEvent;
import net.minestom.server.event.player.PlayerEatEvent;
import net.minestom.server.event.player.PlayerPreLoginEvent;
import net.minestom.server.event.player.PlayerRespawnEvent;
import net.minestom.server.event.player.PlayerSkinInitEvent;
import net.minestom.server.event.player.PlayerSpawnEvent;
import net.minestom.server.event.player.PlayerTickEvent;
import net.minestom.server.event.player.UpdateTagListEvent;
import net.minestom.server.event.player.*;
import net.minestom.server.gamedata.tags.TagManager;
import net.minestom.server.instance.Chunk;
import net.minestom.server.instance.Instance;
@ -61,49 +36,7 @@ import net.minestom.server.network.PlayerProvider;
import net.minestom.server.network.packet.client.ClientPlayPacket;
import net.minestom.server.network.packet.client.play.ClientChatMessagePacket;
import net.minestom.server.network.packet.server.ServerPacket;
import net.minestom.server.network.packet.server.play.BlockBreakAnimationPacket;
import net.minestom.server.network.packet.server.play.CameraPacket;
import net.minestom.server.network.packet.server.play.ChangeGameStatePacket;
import net.minestom.server.network.packet.server.play.ChatMessagePacket;
import net.minestom.server.network.packet.server.play.CloseWindowPacket;
import net.minestom.server.network.packet.server.play.CombatEventPacket;
import net.minestom.server.network.packet.server.play.DeclareCommandsPacket;
import net.minestom.server.network.packet.server.play.DeclareRecipesPacket;
import net.minestom.server.network.packet.server.play.DestroyEntitiesPacket;
import net.minestom.server.network.packet.server.play.DisconnectPacket;
import net.minestom.server.network.packet.server.play.EffectPacket;
import net.minestom.server.network.packet.server.play.EntityEquipmentPacket;
import net.minestom.server.network.packet.server.play.EntityHeadLookPacket;
import net.minestom.server.network.packet.server.play.EntityMetaDataPacket;
import net.minestom.server.network.packet.server.play.EntityPositionAndRotationPacket;
import net.minestom.server.network.packet.server.play.EntityPositionPacket;
import net.minestom.server.network.packet.server.play.EntityRotationPacket;
import net.minestom.server.network.packet.server.play.EntitySoundEffect;
import net.minestom.server.network.packet.server.play.FacePlayerPacket;
import net.minestom.server.network.packet.server.play.HeldItemChangePacket;
import net.minestom.server.network.packet.server.play.JoinGamePacket;
import net.minestom.server.network.packet.server.play.KeepAlivePacket;
import net.minestom.server.network.packet.server.play.NamedSoundEffectPacket;
import net.minestom.server.network.packet.server.play.OpenWindowPacket;
import net.minestom.server.network.packet.server.play.PlayerAbilitiesPacket;
import net.minestom.server.network.packet.server.play.PlayerInfoPacket;
import net.minestom.server.network.packet.server.play.PlayerListHeaderAndFooterPacket;
import net.minestom.server.network.packet.server.play.PlayerPositionAndLookPacket;
import net.minestom.server.network.packet.server.play.PluginMessagePacket;
import net.minestom.server.network.packet.server.play.ResourcePackSendPacket;
import net.minestom.server.network.packet.server.play.RespawnPacket;
import net.minestom.server.network.packet.server.play.ServerDifficultyPacket;
import net.minestom.server.network.packet.server.play.SetExperiencePacket;
import net.minestom.server.network.packet.server.play.SoundEffectPacket;
import net.minestom.server.network.packet.server.play.SpawnPlayerPacket;
import net.minestom.server.network.packet.server.play.SpawnPositionPacket;
import net.minestom.server.network.packet.server.play.StopSoundPacket;
import net.minestom.server.network.packet.server.play.TagsPacket;
import net.minestom.server.network.packet.server.play.TitlePacket;
import net.minestom.server.network.packet.server.play.UnloadChunkPacket;
import net.minestom.server.network.packet.server.play.UnlockRecipesPacket;
import net.minestom.server.network.packet.server.play.UpdateHealthPacket;
import net.minestom.server.network.packet.server.play.UpdateViewPositionPacket;
import net.minestom.server.network.packet.server.play.*;
import net.minestom.server.network.player.NettyPlayerConnection;
import net.minestom.server.network.player.PlayerConnection;
import net.minestom.server.recipe.Recipe;
@ -128,6 +61,14 @@ import net.minestom.server.utils.time.TimeUnit;
import net.minestom.server.utils.time.UpdateOption;
import net.minestom.server.utils.validate.Check;
import net.minestom.server.world.DimensionType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
/**
* Those are the major actors of the server,
@ -970,23 +911,23 @@ public class Player extends LivingEntity implements CommandSender {
namedSoundEffectPacket.pitch = pitch;
playerConnection.sendPacket(namedSoundEffectPacket);
}
/**
* Plays a sound directly to the player (constant volume).
*
* @param sound the sound to play
* @param sound the sound to play
* @param soundCategory the sound category
* @param volume the volume of the sound (1 is 100%)
* @param pitch the pitch of the sound, between 0.5 and 2.0
*/
public void playSound(@NotNull Sound sound, @NotNull SoundCategory soundCategory, float volume, float pitch) {
EntitySoundEffect entitySoundEffect = new EntitySoundEffect();
entitySoundEffect.entityId = getEntityId();
entitySoundEffect.soundId = sound.getId();
entitySoundEffect.soundCategory = soundCategory;
entitySoundEffect.volume = volume;
entitySoundEffect.pitch = pitch;
playerConnection.sendPacket(entitySoundEffect);
EntitySoundEffect entitySoundEffect = new EntitySoundEffect();
entitySoundEffect.entityId = getEntityId();
entitySoundEffect.soundId = sound.getId();
entitySoundEffect.soundCategory = soundCategory;
entitySoundEffect.volume = volume;
entitySoundEffect.pitch = pitch;
playerConnection.sendPacket(entitySoundEffect);
}
/**

View File

@ -1,5 +1,9 @@
package net.minestom.server.network;
import io.netty.channel.Channel;
import net.minestom.server.MinecraftServer;
import net.minestom.server.chat.ChatColor;
import net.minestom.server.chat.ColoredText;
import net.minestom.server.chat.JsonMessage;
import net.minestom.server.entity.Player;
import net.minestom.server.entity.fakeplayer.FakePlayer;
@ -8,6 +12,8 @@ import net.minestom.server.listener.manager.ServerPacketConsumer;
import net.minestom.server.network.packet.client.login.LoginStartPacket;
import net.minestom.server.network.packet.server.login.LoginSuccessPacket;
import net.minestom.server.network.packet.server.play.ChatMessagePacket;
import net.minestom.server.network.packet.server.play.DisconnectPacket;
import net.minestom.server.network.player.NettyPlayerConnection;
import net.minestom.server.network.player.PlayerConnection;
import net.minestom.server.utils.PacketUtils;
import net.minestom.server.utils.callback.validator.PlayerValidator;
@ -38,6 +44,8 @@ public final class ConnectionManager {
// The consumers to call once a player connect, mostly used to init events
private final List<Consumer<Player>> playerInitializations = new CopyOnWriteArrayList<>();
private ColoredText shutdownText = ColoredText.of(ChatColor.RED, "The server is shutting down.");
/**
* Gets the {@link Player} linked to a {@link PlayerConnection}.
*
@ -255,6 +263,26 @@ public final class ConnectionManager {
this.playerInitializations.add(playerInitialization);
}
/**
* Gets the kick reason when the server is shutdown using {@link MinecraftServer#stopCleanly()}.
*
* @return the kick reason in case on a shutdown
*/
@NotNull
public ColoredText getShutdownText() {
return shutdownText;
}
/**
* Changes the kick reason in case of a shutdown.
*
* @param shutdownText the new shutdown kick reason
* @see #getShutdownText()
*/
public void setShutdownText(@NotNull ColoredText shutdownText) {
this.shutdownText = shutdownText;
}
/**
* Adds a new {@link Player} in the players list.
* Is currently used at
@ -316,4 +344,23 @@ public final class ConnectionManager {
connection.setConnectionState(ConnectionState.PLAY);
return createPlayer(uuid, username, connection);
}
/**
* Shutdowns the connection manager by kicking all the currently connected players
*/
public void shutdown() {
DisconnectPacket disconnectPacket = new DisconnectPacket();
disconnectPacket.message = getShutdownText();
for (Player player : getOnlinePlayers()) {
final PlayerConnection playerConnection = player.getPlayerConnection();
if (playerConnection instanceof NettyPlayerConnection) {
final NettyPlayerConnection nettyPlayerConnection = (NettyPlayerConnection) playerConnection;
final Channel channel = nettyPlayerConnection.getChannel();
channel.writeAndFlush(disconnectPacket);
channel.close();
}
}
this.players.clear();
this.connectionPlayerMap.clear();
}
}

View File

@ -230,8 +230,6 @@ public final class NettyServer {
* Stops the server and the various services.
*/
public void stop() {
// FIXME: fix "java.util.concurrent.RejectedExecutionException: event executor terminated"
// TODO: probably because we are doing IO after #stop() is executed
try {
this.serverChannel.close().sync();
this.worker.shutdownGracefully().await();

View File

@ -98,7 +98,7 @@ public final class SchedulerManager {
public void shutdown() {
MinecraftServer.LOGGER.info("Executing all shutdown tasks..");
for (Task task : this.getShutdownTasks()) {
task.schedule();
task.runRunnable();
}
MinecraftServer.LOGGER.info("Shutting down the scheduled execution service and batches pool.");
this.timerExecutionService.shutdown();

View File

@ -73,6 +73,15 @@ public class Task implements Runnable {
});
}
/**
* Executes the internal runnable.
* <p>
* Should probably use {@link #schedule()} instead.
*/
public void runRunnable() {
this.runnable.run();
}
/**
* Sets up the task for correct execution.
*/