1
0

Checkpointing support

Dieser Commit ist enthalten in:
Lixfel 2024-01-16 12:50:46 +01:00
Ursprung af89693438
Commit a0f2ce2375
5 geänderte Dateien mit 165 neuen und 126 gelöschten Zeilen

Datei anzeigen

@ -32,8 +32,8 @@ mainClassName = ''
compileJava.options.encoding = 'UTF-8' compileJava.options.encoding = 'UTF-8'
sourceCompatibility = 1.8 sourceCompatibility = 1.11
targetCompatibility = 1.8 targetCompatibility = 1.11
sourceSets { sourceSets {
main { main {

Datei anzeigen

@ -1,5 +1,8 @@
package de.steamwar.bungeecore; package de.steamwar.bungeecore;
import lombok.Getter;
@Getter
public class Arenaserver extends Subserver { public class Arenaserver extends Subserver {
private final String mode; private final String mode;
@ -13,15 +16,4 @@ public class Arenaserver extends Subserver {
this.allowMerge = allowMerge; this.allowMerge = allowMerge;
} }
public String getMode() {
return mode;
}
public String getMap() {
return map;
}
public boolean isAllowMerge() {
return allowMerge;
}
} }

Datei anzeigen

@ -19,26 +19,41 @@
package de.steamwar.bungeecore; package de.steamwar.bungeecore;
import lombok.Getter;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID; import java.util.UUID;
@Getter
public class Bauserver extends Subserver { public class Bauserver extends Subserver {
private static final Map<UUID, Bauserver> servers = new HashMap<>();
public static Bauserver get(UUID owner) {
synchronized (servers) {
return servers.get(owner);
}
}
private final UUID owner; private final UUID owner;
public Bauserver(String serverName, UUID owner, int port, String... command) {
this(serverName, owner, port, () -> {}, command);
}
public Bauserver(String serverName, UUID owner, int port, Runnable shutdownCallback, String... command) {
this(serverName, owner, port, new ProcessBuilder(command), shutdownCallback);
}
public Bauserver(String serverName, UUID owner, int port, ProcessBuilder processBuilder, Runnable shutdownCallback){ public Bauserver(String serverName, UUID owner, int port, ProcessBuilder processBuilder, Runnable shutdownCallback){
super(Servertype.BAUSERVER, serverName, port, processBuilder, shutdownCallback); super(Servertype.BAUSERVER, serverName, port, processBuilder, shutdownCallback);
this.owner = owner; this.owner = owner;
} }
public UUID getOwner(){ @Override
return owner; protected void register() {
super.register();
synchronized (servers) {
servers.put(owner, this);
}
}
@Override
protected void unregister() {
synchronized (servers) {
servers.remove(owner);
}
super.unregister();
} }
} }

Datei anzeigen

@ -19,8 +19,21 @@
package de.steamwar.bungeecore; package de.steamwar.bungeecore;
import lombok.Getter;
import java.util.HashMap;
import java.util.Map;
@Getter
public class Builderserver extends Subserver { public class Builderserver extends Subserver {
private static final Map<String, Builderserver> servers = new HashMap<>();
public static Builderserver get(String map) {
synchronized (servers) {
return servers.get(map);
}
}
private final String map; private final String map;
public Builderserver(String serverName, String map, int port, ProcessBuilder processBuilder, Runnable shutdownCallback){ public Builderserver(String serverName, String map, int port, ProcessBuilder processBuilder, Runnable shutdownCallback){
@ -28,7 +41,19 @@ public class Builderserver extends Subserver {
this.map = map; this.map = map;
} }
public String getMap() { @Override
return map; protected void register() {
super.register();
synchronized (servers) {
servers.put(map, this);
}
}
@Override
protected void unregister() {
synchronized (servers) {
servers.remove(map);
}
super.unregister();
} }
} }

Datei anzeigen

@ -19,6 +19,7 @@
package de.steamwar.bungeecore; package de.steamwar.bungeecore;
import lombok.Getter;
import net.md_5.bungee.api.ChatMessageType; import net.md_5.bungee.api.ChatMessageType;
import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.BaseComponent;
@ -26,25 +27,28 @@ import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.config.ServerInfo; import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.ProxiedPlayer;
import java.io.BufferedReader; import java.io.*;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@SuppressWarnings("deprecation") public class Subserver {
public class Subserver implements Runnable {
private static final List<Subserver> serverList = new LinkedList<>(); @SuppressWarnings("deprecation")
private static final Map<String, ServerInfo> servers = ProxyServer.getInstance().getServers();
private static final Logger logger = ProxyServer.getInstance().getLogger(); private static final Logger logger = ProxyServer.getInstance().getLogger();
@Getter
private static final List<Subserver> serverList = new LinkedList<>();
private static final Map<ServerInfo, Subserver> infoToServer = new HashMap<>();
public static Subserver getSubserver(ProxiedPlayer p) { public static Subserver getSubserver(ProxiedPlayer p) {
synchronized (serverList) { synchronized (serverList) {
for(int i = serverList.size()-1; i >= 0; i--){ for(int i = serverList.size()-1; i >= 0; i--){
@ -54,43 +58,49 @@ public class Subserver implements Runnable {
} }
return null; return null;
} }
public static Subserver getSubserver(ServerInfo server) { public static Subserver getSubserver(ServerInfo server) {
synchronized (serverList) { synchronized (serverList) {
for(Subserver s : serverList) return infoToServer.get(server);
if(s.server == server)
return s;
} }
return null;
} }
public static List<Subserver> getServerList(){
return serverList; static void shutdown(){
while (!serverList.isEmpty()) {
Subserver server = serverList.get(0);
server.stop();
}
} }
private final String serverName; private final String serverName;
private final boolean checkpoint;
private final Runnable shutdownCallback; private final Runnable shutdownCallback;
private final Consumer<Exception> failureCallback;
private final Process process; private final Process process;
private final PrintWriter writer; private final PrintWriter writer;
@Getter
private final ServerInfo server; private final ServerInfo server;
@Getter
private final Servertype type; private final Servertype type;
private final Thread thread; private final Thread thread;
@Getter
private boolean started; private boolean started;
private final List<ProxiedPlayer> cachedPlayers = new LinkedList<>(); private final List<ProxiedPlayer> cachedPlayers = new LinkedList<>();
@Getter
private final Map<ProxiedPlayer, String> tablistNames = new HashMap<>(); private final Map<ProxiedPlayer, String> tablistNames = new HashMap<>();
public Subserver(Servertype type, String serverName, int port, String... command){
this(type, serverName, port, () -> {}, command);
}
public Subserver(Servertype type, String serverName, int port, Runnable shutdownCallback, String... command){
this(type, serverName, port, new ProcessBuilder(command), shutdownCallback);
}
public Subserver(Servertype type, String serverName, int port, ProcessBuilder processBuilder, Runnable shutdownCallback){ public Subserver(Servertype type, String serverName, int port, ProcessBuilder processBuilder, Runnable shutdownCallback){
this(type, serverName, port, processBuilder, shutdownCallback, null);
}
public Subserver(Servertype type, String serverName, int port, ProcessBuilder processBuilder, Runnable shutdownCallback, Consumer<Exception> failureCallback) {
this.started = false; this.started = false;
this.serverName = serverName; this.serverName = serverName;
this.type = type; this.type = type;
this.shutdownCallback = shutdownCallback; this.shutdownCallback = shutdownCallback;
this.failureCallback = failureCallback == null ? this::fatalError : failureCallback;
this.checkpoint = processBuilder.command().contains("criu");
try{ try{
this.process = processBuilder.start(); this.process = processBuilder.start();
@ -103,27 +113,18 @@ public class Subserver implements Runnable {
serverName, address, "SteamWar.de - Subserver", false); serverName, address, "SteamWar.de - Subserver", false);
this.writer = new PrintWriter(process.getOutputStream(), true); this.writer = new PrintWriter(process.getOutputStream(), true);
this.thread = new Thread(this, "Subserver " + serverName); this.thread = new Thread(this::run, "Subserver " + serverName);
this.thread.start(); this.thread.start();
} }
public ServerInfo getServer(){ @Deprecated
return server;
}
public Servertype getType(){
return type;
}
public Map<ProxiedPlayer, String> getTablistNames(){
return tablistNames;
}
public boolean hasStarted(){ public boolean hasStarted(){
return started; return started;
} }
public void sendPlayer(ProxiedPlayer p) { public void sendPlayer(ProxiedPlayer p) {
if(!started){ if(!started){
p.sendMessage(Persistent.PREFIX + "§7Starting server, please wait..."); p.sendMessage(ChatMessageType.ACTION_BAR, generateBar(0));
cachedPlayers.add(p); cachedPlayers.add(p);
}else{ }else{
p.connect(server); p.connect(server);
@ -135,10 +136,15 @@ public class Subserver implements Runnable {
} }
public void stop() { public void stop() {
if(checkpoint)
process.children().forEach(ProcessHandle::destroy);
else
execute("stop"); execute("stop");
try { try {
if(!process.waitFor(1, TimeUnit.MINUTES)) if(!process.waitFor(1, TimeUnit.MINUTES))
process.destroyForcibly(); process.destroyForcibly();
thread.join(); thread.join();
}catch(InterruptedException e){ }catch(InterruptedException e){
logger.log(Level.SEVERE, "Subserver stop interrupted!", e); logger.log(Level.SEVERE, "Subserver stop interrupted!", e);
@ -146,74 +152,71 @@ public class Subserver implements Runnable {
} }
} }
public void waitForTermination(){
try {
if(!process.waitFor(5, TimeUnit.MINUTES) && server.getPlayers().isEmpty()){
process.destroy();
}
thread.join();
}catch(InterruptedException e){
logger.log(Level.SEVERE, "Subserver stop interrupted!", e);
Thread.currentThread().interrupt();
}
}
static void shutdown(){
while (!serverList.isEmpty()) {
Subserver server = serverList.get(0);
server.stop();
}
}
private boolean onServer(ProxiedPlayer p){ private boolean onServer(ProxiedPlayer p){
return cachedPlayers.contains(p) || server.getPlayers().contains(p); return cachedPlayers.contains(p) || server.getPlayers().contains(p);
} }
private void fatalError(){ private void fatalError(Exception e) {
logger.log(Level.SEVERE, e, () -> serverName + " did not run correctly!");
for(ProxiedPlayer cached : cachedPlayers) for(ProxiedPlayer cached : cachedPlayers)
cached.sendMessage(Persistent.PREFIX + "§cUnexpected error during server startup."); cached.sendMessage(TextComponent.fromLegacy(Persistent.PREFIX + "§cUnexpected error during server startup."));
for(ProxiedPlayer player : server.getPlayers()) for(ProxiedPlayer player : server.getPlayers())
player.sendMessage(Persistent.PREFIX + "§cLost connection to server."); player.sendMessage(TextComponent.fromLegacy(Persistent.PREFIX + "§cLost connection to server."));
} }
private void sendProgress(int progress){ private void start(InputStream stream, Predicate<String> test) throws IOException {
StringBuilder sb = new StringBuilder("§e"); try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream))) {
for(int i = 0; i < progress; i++)
sb.append('⬛');
sb.append("§8");
for(int i = progress; i < 10; i++)
sb.append('⬛');
BaseComponent[] tc = TextComponent.fromLegacyText(sb.toString());
for(ProxiedPlayer cached : cachedPlayers)
cached.sendMessage(ChatMessageType.ACTION_BAR, tc);
}
public void run(){
ProxyServer.getInstance().getServers().put(serverName, server);
synchronized (serverList) {
serverList.add(this);
}
try(BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))){
String line = ""; String line = "";
while (!started && (line = reader.readLine()) != null) { while (!started && (line = reader.readLine()) != null) {
started = line.contains("ViaVersion detected server version"); started = test.test(line);
if(line.contains("Loading libraries, please wait"))
sendProgress(0);
else if(line.contains("Starting Minecraft server on"))
sendProgress(3);
else if(line.contains("Preparing start region"))
sendProgress(6);
} }
if(line == null){ if(line == null){
logger.log(Level.SEVERE, "Subserver {0} stopped to early!", serverName); failureCallback.accept(new IOException(serverName + " did not start correctly!"));
fatalError(); started = false;
return; }
}
} }
sendProgress(9); protected void register() {
servers.put(serverName, server);
synchronized (serverList) {
serverList.add(this);
infoToServer.put(server, this);
}
}
protected void unregister() {
synchronized (serverList){
infoToServer.remove(server);
serverList.remove(this);
}
servers.remove(serverName);
}
private void run() {
register();
try {
if(checkpoint) {
start(process.getErrorStream(), line -> line.contains("Restore finished successfully."));
} else {
start(process.getInputStream(), line -> {
if(line.contains("Loading libraries, please wait"))
sendProgress(2);
else if(line.contains("Starting Minecraft server on"))
sendProgress(4);
else if(line.contains("Preparing start region"))
sendProgress(6);
return line.contains("Done (");
});
}
if(!started)
return;
sendProgress(8);
Thread.sleep(300); Thread.sleep(300);
@ -223,21 +226,25 @@ public class Subserver implements Runnable {
} }
cachedPlayers.clear(); cachedPlayers.clear();
reader.close(); // Prevent stdout clogging up
process.waitFor(); process.waitFor();
} catch(IOException e) { } catch(IOException e) {
logger.log(Level.SEVERE, "Server " + serverName + " was interrupted!", e); failureCallback.accept(e);
fatalError();
} catch(InterruptedException e) { } catch(InterruptedException e) {
logger.log(Level.SEVERE, "Server " + serverName + " was interrupted!", e); failureCallback.accept(e);
fatalError();
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
} finally { } finally {
synchronized (serverList){
serverList.remove(this);
}
ProxyServer.getInstance().getServers().remove(serverName);
shutdownCallback.run(); shutdownCallback.run();
unregister();
} }
} }
private BaseComponent generateBar(int progress) {
return TextComponent.fromLegacy("§e" + "".repeat(Math.max(0, progress)) + "§8" + "".repeat(Math.max(0, 10 - progress)));
}
private void sendProgress(int progress) {
BaseComponent tc = generateBar(progress);
for(ProxiedPlayer cached : cachedPlayers)
cached.sendMessage(ChatMessageType.ACTION_BAR, tc);
}
} }