geforkt von SteamWar/PersistentBungeeCore
Merge pull request 'Checkpointing support' (#27) from checkpoint into master
Reviewed-on: SteamWar/PersistentBungeeCore#27 Reviewed-by: YoyoNow <jwsteam@nidido.de>
Dieser Commit ist enthalten in:
Commit
4ec3c641be
@ -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 {
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,26 +27,29 @@ 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();
|
||||||
|
|
||||||
public static Subserver getSubserver(ProxiedPlayer p){
|
@Getter
|
||||||
|
private static final List<Subserver> serverList = new LinkedList<>();
|
||||||
|
private static final Map<ServerInfo, Subserver> infoToServer = new HashMap<>();
|
||||||
|
|
||||||
|
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--){
|
||||||
if(serverList.get(i).onServer(p))
|
if(serverList.get(i).onServer(p))
|
||||||
@ -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);
|
||||||
@ -134,11 +135,16 @@ public class Subserver implements Runnable {
|
|||||||
writer.println(command);
|
writer.println(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stop(){
|
public void stop() {
|
||||||
execute("stop");
|
if(checkpoint)
|
||||||
|
process.children().forEach(ProcessHandle::destroy);
|
||||||
|
else
|
||||||
|
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,98 +152,99 @@ 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;
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
sendProgress(9);
|
if(!started)
|
||||||
|
return;
|
||||||
|
|
||||||
|
sendProgress(8);
|
||||||
|
|
||||||
Thread.sleep(300);
|
Thread.sleep(300);
|
||||||
|
|
||||||
sendProgress(10);
|
sendProgress(10);
|
||||||
for(ProxiedPlayer cachedPlayer : cachedPlayers){
|
for(ProxiedPlayer cachedPlayer : cachedPlayers) {
|
||||||
sendPlayer(cachedPlayer);
|
sendPlayer(cachedPlayer);
|
||||||
}
|
}
|
||||||
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
In neuem Issue referenzieren
Einen Benutzer sperren