diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/CheckpointUtils.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/CheckpointUtils.java index f274acfc..56e71994 100644 --- a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/CheckpointUtils.java +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/CheckpointUtils.java @@ -19,34 +19,12 @@ 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 sun.misc.Signal; - -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() {} public static void signalHandler() { try { - J9Wrapper.signalHandler(); + CheckpointUtilsJ9.signalHandler(); } catch (NoClassDefFoundError e) { //ignore } @@ -54,113 +32,9 @@ public class CheckpointUtils { public static void freeze() { try { - J9Wrapper.freeze(); + CheckpointUtilsJ9.freeze(); } catch (NoClassDefFoundError e) { //ignore } } - - private static class J9Wrapper { - private static void signalHandler() { - Signal.handle(new Signal("USR1"), signal -> Bukkit.getScheduler().runTask(Core.getInstance(), CheckpointUtils::freeze)); - } - - private static void freeze() { - String checkpointFile = System.getProperty("checkpoint"); - if(!CRIUSupport.isCheckpointAllowed() || checkpointFile == null) { - Bukkit.shutdown(); - return; - } - - Bukkit.getOnlinePlayers().forEach(player -> player.kickPlayer(null)); - - List networkManagers = TinyProtocol.networkManagers.get(TinyProtocol.getServerConnection(Core.getInstance())); - if(!Bukkit.getOnlinePlayers().isEmpty() || !networkManagers.isEmpty()) { - Core.getInstance().getLogger().log(Level.INFO, "Waiting for players to disconnect for checkpointing"); - Bukkit.getScheduler().runTaskLater(Core.getInstance(), CheckpointUtils::freeze, 1); - return; - } - - Path path = FileSystems.getDefault().getPath(checkpointFile); - - try { - freezeInternal(path); - } catch (Exception e) { - String message = e.getMessage() != null ? e.getMessage() : ""; - if(message.contains("Connected TCP socket")) { - Core.getInstance().getLogger().log(Level.INFO, "Connected TCP socket, waiting for checkpointing"); - Bukkit.getScheduler().runTaskLater(Core.getInstance(), CheckpointUtils::freeze, 1); - return; - } - - Bukkit.shutdown(); - - if(!message.contains("Can't dump ghost file") && !message.contains("Can't create link remap")) // File/Jar has been updated - 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 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); - private static void freezeInternal(Path path) throws Exception { - Bukkit.getPluginManager().callEvent(new CRIUSleepEvent()); - Bukkit.getWorlds().forEach(FlatteningWrapper.impl::syncSave); - 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(); - - System.runFinalization(); - System.gc(); - - // 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) { - Path logfile = path.resolve("criu.log"); - if(logfile.toFile().exists()) - throw new IllegalStateException("Could not create checkpoint, criu log:\n" + new String(Files.readAllBytes(logfile)), e); - - 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(); - - Bukkit.getPluginManager().callEvent(new CRIUWakeupEvent()); - Core.getInstance().getLogger().log(Level.INFO, "Checkpoint restored"); - } - } } diff --git a/SpigotCore/SpigotCore_Main/src/de/steamwar/core/CheckpointUtilsJ9.java b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/CheckpointUtilsJ9.java new file mode 100644 index 00000000..cde82084 --- /dev/null +++ b/SpigotCore/SpigotCore_Main/src/de/steamwar/core/CheckpointUtilsJ9.java @@ -0,0 +1,148 @@ +/* + * 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 sun.misc.Signal; + +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; + +class CheckpointUtilsJ9 { + private CheckpointUtilsJ9() {} + + static void signalHandler() { + Signal.handle(new Signal("USR1"), signal -> Bukkit.getScheduler().runTask(Core.getInstance(), CheckpointUtils::freeze)); + } + + static void freeze() { + String checkpointFile = System.getProperty("checkpoint"); + if(!CRIUSupport.isCheckpointAllowed() || checkpointFile == null) { + Bukkit.shutdown(); + return; + } + + Bukkit.getOnlinePlayers().forEach(player -> player.kickPlayer(null)); + + List networkManagers = TinyProtocol.networkManagers.get(TinyProtocol.getServerConnection(Core.getInstance())); + if(!Bukkit.getOnlinePlayers().isEmpty() || !networkManagers.isEmpty()) { + Core.getInstance().getLogger().log(Level.INFO, "Waiting for players to disconnect for checkpointing"); + Bukkit.getScheduler().runTaskLater(Core.getInstance(), CheckpointUtils::freeze, 1); + return; + } + + Path path = FileSystems.getDefault().getPath(checkpointFile); + + try { + freezeInternal(path); + } catch (Exception e) { + String message = e.getMessage() != null ? e.getMessage() : ""; + if(message.contains("Connected TCP socket")) { + Core.getInstance().getLogger().log(Level.INFO, "Connected TCP socket, waiting for checkpointing"); + Bukkit.getScheduler().runTaskLater(Core.getInstance(), CheckpointUtils::freeze, 1); + return; + } + + Bukkit.shutdown(); + + if(!message.contains("Can't dump ghost file") && !message.contains("Can't create link remap")) // File/Jar has been updated + 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 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); + private static void freezeInternal(Path path) throws Exception { + Bukkit.getPluginManager().callEvent(new CRIUSleepEvent()); + Bukkit.getWorlds().forEach(FlatteningWrapper.impl::syncSave); + 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(); + + System.runFinalization(); + System.gc(); + + // 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) { + Path logfile = path.resolve("criu.log"); + if(logfile.toFile().exists()) + throw new IllegalStateException("Could not create checkpoint, criu log:\n" + new String(Files.readAllBytes(logfile)), e); + + 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(); + + Bukkit.getPluginManager().callEvent(new CRIUWakeupEvent()); + Core.getInstance().getLogger().log(Level.INFO, "Checkpoint restored"); + } +}