diff --git a/SpigotCore_Main/src/com/comphenix/tinyprotocol/TinyProtocol.java b/SpigotCore_Main/src/com/comphenix/tinyprotocol/TinyProtocol.java index 7957a0f..2528a6f 100644 --- a/SpigotCore_Main/src/com/comphenix/tinyprotocol/TinyProtocol.java +++ b/SpigotCore_Main/src/com/comphenix/tinyprotocol/TinyProtocol.java @@ -28,8 +28,11 @@ public class TinyProtocol implements Listener { private static final Class playerList = Reflection.getClass("{nms.server.players}.PlayerList"); private static final Class minecraftServer = Reflection.getClass("{nms.server}.MinecraftServer"); private static final FieldAccessor getMinecraftServer = Reflection.getField(playerList, minecraftServer, 0); - private static final Class serverConnection = Reflection.getClass("{nms.server.network}.ServerConnection"); + public static final Class serverConnection = Reflection.getClass("{nms.server.network}.ServerConnection"); private static final FieldAccessor getServerConnection = Reflection.getField(minecraftServer, serverConnection, 0); + public static Object getServerConnection(Plugin plugin) { + return getServerConnection.get(getMinecraftServer.get(getPlayerList.get(plugin.getServer()))); + } private static final Class networkManager = Reflection.getClass("{nms.network}.NetworkManager"); private static final FieldAccessor getConnections = Reflection.getField(serverConnection, List.class, 0, networkManager); @@ -51,7 +54,7 @@ public class TinyProtocol implements Listener { private TinyProtocol(final Plugin plugin) { this.plugin = plugin; this.handlerName = "tiny-" + plugin.getName() + "-" + ++id; - this.connections = getConnections.get(getServerConnection.get(getMinecraftServer.get(getPlayerList.get(plugin.getServer())))); + this.connections = getConnections.get(getServerConnection(plugin)); plugin.getServer().getPluginManager().registerEvents(this, plugin); diff --git a/SpigotCore_Main/src/de/steamwar/core/CheckpointUtils.java b/SpigotCore_Main/src/de/steamwar/core/CheckpointUtils.java new file mode 100644 index 0000000..8cedf02 --- /dev/null +++ b/SpigotCore_Main/src/de/steamwar/core/CheckpointUtils.java @@ -0,0 +1,110 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2024 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.core; + +import com.comphenix.tinyprotocol.Reflection; +import com.comphenix.tinyprotocol.TinyProtocol; +import com.viaversion.viaversion.api.Via; +import de.steamwar.sql.internal.Statement; +import io.netty.channel.ChannelFuture; +import org.bukkit.Bukkit; +import org.eclipse.openj9.criu.CRIUSupport; +import org.eclipse.openj9.criu.JVMCRIUException; + +import java.io.DataInputStream; +import java.io.File; +import java.io.IOException; +import java.net.InetAddress; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Comparator; +import java.util.List; +import java.util.logging.Level; +import java.util.stream.Stream; + +public class CheckpointUtils { + private CheckpointUtils() {} + + private static final Reflection.FieldAccessor channelFutures = Reflection.getField(TinyProtocol.serverConnection, List.class, 0, ChannelFuture.class); + private static final Reflection.MethodInvoker bind = Reflection.getMethod(TinyProtocol.serverConnection, null, InetAddress.class, int.class); + + public static void freeze() { + Path path = FileSystems.getDefault().getPath(System.getProperty("checkpoint")); + + try { + freezeInternal(path); + } catch (Exception e) { + Bukkit.shutdown(); + throw new SecurityException(e); + } finally { + // Delete checkpoint + try (Stream stream = Files.walk(path)) { + stream.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); + } catch (IOException e) { + //ignore + } + } + } + + private static void freezeInternal(Path path) throws Exception { + Statement.closeAll(); + + // Close socket + Via.getManager().getInjector().uninject(); + Object serverConnection = TinyProtocol.getServerConnection(Core.getInstance()); + List channels = channelFutures.get(serverConnection); + for(Object future : channels) { + ((ChannelFuture) future).channel().close().syncUninterruptibly(); + } + channels.clear(); + + // Do the checkpoint + path.toFile().mkdirs(); + CRIUSupport criu = new CRIUSupport(path); + criu.setAutoDedup(true); + criu.setFileLocks(true); + criu.setShellJob(true); + criu.setLogFile("criu.log"); + try { + criu.checkpointJVM(); + } catch (JVMCRIUException e) { + Bukkit.shutdown(); + throw e; + } + + // Get new port + int port; + try (DataInputStream stream = new DataInputStream(Files.newInputStream(path.resolve("port").toFile().toPath()))) { + port = stream.readInt(); + } + + // Reopen socket + bind.invoke(serverConnection, InetAddress.getLoopbackAddress(), port); + if(Core.getVersion() > 12) { + for(Object future : channels) { + ((ChannelFuture) future).channel().config().setAutoRead(true); + } + } + Via.getManager().getInjector().inject(); + + Core.getInstance().getLogger().log(Level.INFO, "Checkpoint restored"); + } +}