McManager/src/main/java/de/ph87/mc/server/ServerProcessHelper.java

129 lines
4.1 KiB
Java

package de.ph87.mc.server;
import jakarta.annotation.Nullable;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@Slf4j
@Service
@RequiredArgsConstructor
public class ServerProcessHelper {
public static final String[] CMDLINE = {"java", "-jar", "server.jar"};
public static final String CMDLINE_STR = String.join(" ", CMDLINE);
private final ApplicationEventPublisher applicationEventPublisher;
public void updatePid(@NonNull final Server server) {
server.setPid(_readAndVerifyPid(server));
if (server.getPid() == null) {
deletePidFile(server);
}
}
@Nullable
private Long _readAndVerifyPid(@NonNull final Server server) {
if (!server.pidFile.exists()) {
return null;
}
final long pid;
try (final FileInputStream stream = new FileInputStream(server.pidFile)) {
pid = Long.parseLong(new String(stream.readAllBytes(), StandardCharsets.UTF_8));
} catch (IOException | NumberFormatException e) {
log.error("Failed to read pid-file: file={}, error={}", server.pidFile, e.getMessage());
return null;
}
if (!validateProcFile(server, pid)) {
return null;
}
return pid;
}
private static boolean validateProcFile(@NonNull final Server server, final long pid) {
final File procFile = new File("/proc/%d/cmdline".formatted(pid));
if (!procFile.exists()) {
log.warn("Server not running: {}", server.name);
return false;
}
try (final FileInputStream stream = new FileInputStream(procFile)) {
final String cmdline = new String(stream.readAllBytes(), StandardCharsets.UTF_8).replace((char) 0, ' ').trim();
if (!CMDLINE_STR.equals(cmdline)) {
log.error("cmdline of running Server does not match: pid={}, running={}, expected={}", pid, cmdline, CMDLINE_STR);
return false;
}
} catch (IOException | NumberFormatException e) {
log.error("Failed to read proc-file: file={}, error={}", procFile, e.getMessage());
return false;
}
return true;
}
private void deletePidFile(@NonNull final Server server) {
server.setPid(null);
if (server.pidFile.delete()) {
log.info("PID-file removed: {}", server.pidFile);
applicationEventPublisher.publishEvent(server);
}
}
public void startProcess(@NonNull final Server server) {
if (server.isRunning()) {
return;
}
log.info("Starting Server: {}", server.name);
final ProcessBuilder builder = new ProcessBuilder(CMDLINE);
builder.directory(server.directory);
try {
final Process process = builder.start();
server.setPid(process.pid());
writePid(server, process.pid());
} catch (IOException e) {
log.error("Failed to start server: error={}, name={}", e.getMessage(), server.name);
}
}
public void stopProcess(@NonNull final Server server) {
if (!server.isRunning()) {
return;
}
new Thread(() -> {
try {
log.info("Stopping Server: {}", server.name);
new ProcessBuilder("kill", "-15", server.getPid() + "").start();
while (server.getPid() != null && validateProcFile(server, server.getPid())) {
//noinspection BusyWait
Thread.sleep(1000);
}
deletePidFile(server);
} catch (IOException | InterruptedException e) {
log.error("Failed to stop server: error={}, name={}", e.getMessage(), server.name);
}
}).start();
}
private void writePid(@NonNull final Server server, final long pid) throws IOException {
final File file = server.pidFile;
try (final FileOutputStream stream = new FileOutputStream(file)) {
stream.write("%d".formatted(pid).getBytes(StandardCharsets.UTF_8));
}
log.info("PID-file written: file={} = {}", server.pidFile, pid);
applicationEventPublisher.publishEvent(server);
}
}