package com.oracle.svm.driver;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.graalvm.nativeimage.ProcessProperties;
import org.graalvm.word.WordFactory;
import com.oracle.svm.core.SubstrateUtil;
import com.oracle.svm.core.option.SubstrateOptionsParser;
import com.oracle.svm.core.util.ClasspathUtils;
import com.oracle.svm.hosted.server.NativeImageBuildClient;
import com.oracle.svm.hosted.server.NativeImageBuildServer;
import com.oracle.svm.hosted.server.SubstrateServerMessage.ServerCommand;
final class NativeImageServer extends NativeImage {
private static final String serverDirPrefix = "server-id-";
private static final String machineDirPrefix = "machine-id-";
private static final String sessionDirPrefix = "session-id-";
private static final String defaultLockFileName = ".lock";
private static final String pKeyMaxServers = "MaxServers";
private static final String machineProperties = "machine.properties";
private boolean useServer = false;
private boolean verboseServer = false;
private String sessionName = null;
private volatile Server building = null;
private final List<FileChannel> openFileChannels = new ArrayList<>();
private final ServerOptionHandler serverOptionHandler;
private NativeImageServer(BuildConfiguration buildConfiguration) {
super(buildConfiguration);
serverOptionHandler = new ServerOptionHandler(this);
registerOptionHandler(serverOptionHandler);
}
static NativeImage create(BuildConfiguration config) {
if (NativeImageServerHelper.isInConfiguration()) {
return new NativeImageServer(config);
}
return new NativeImage(config);
}
@SuppressWarnings("serial")
private static final class ServerInstanceError extends RuntimeException {
}
private final class Server {
private static final String serverProperties = "server.properties";
private static final String buildRequestLog = "build-request.log";
private static final String pKeyPort = "Port";
private static final String pKeyPID = "PID";
private static final String pKeyJavaArgs = "JavaArgs";
private static final String pKeyBCP = "BootClasspath";
private static final String pKeyCP = "Classpath";
final Path serverDir;
final Instant since;
Instant lastBuildRequest;
final int port;
final int pid;
final LinkedHashSet<String> serverJavaArgs;
final LinkedHashSet<Path> serverBootClasspath;
final LinkedHashSet<Path> serverClasspath;
private Server(Path serverDir) throws Exception {
this.serverDir = serverDir;
Path serverPropertiesPath = serverDir.resolve(serverProperties);
Map<String, String> properties = loadProperties(serverPropertiesPath);
this.pid = Integer.parseInt(properties.get(pKeyPID));
this.port = Integer.parseInt(properties.get(pKeyPort));
if (this.port == 0) {
ProcessProperties.destroyForcibly(this.pid);
deleteAllFiles(this.serverDir);
throw new ServerInstanceError();
}
this.serverJavaArgs = new LinkedHashSet<>(Arrays.asList(properties.get(pKeyJavaArgs).split(" ")));
this.serverBootClasspath = readClasspath(properties.get(pKeyBCP));
this.serverClasspath = readClasspath(properties.get(pKeyCP));
BasicFileAttributes sinceAttrs = Files.readAttributes(serverPropertiesPath, BasicFileAttributes.class);
this.since = sinceAttrs.creationTime().toInstant();
updateLastBuildRequest();
}
private void updateLastBuildRequest() throws IOException {
Path buildRequestLogPath = serverDir.resolve(buildRequestLog);
if (Files.isReadable(buildRequestLogPath)) {
BasicFileAttributes buildAttrs = Files.readAttributes(buildRequestLogPath, BasicFileAttributes.class);
lastBuildRequest = buildAttrs.lastModifiedTime().toInstant().plusSeconds(1);
} else {
lastBuildRequest = since;
}
}
private LinkedHashSet<Path> readClasspath(String rawClasspathString) {
LinkedHashSet<Path> result = new LinkedHashSet<>();
for (String pathStr : rawClasspathString.split(" ")) {
result.add(ClasspathUtils.stringToClasspath(pathStr));
}
return result;
}
private int sendRequest(Consumer<byte[]> out, Consumer<byte[]> err, ServerCommand serverCommand, String... args) {
List<String> argList = Arrays.asList(args);
showVerboseMessage(verboseServer, "Sending to server [");
showVerboseMessage(verboseServer, serverCommand.toString());
if (argList.size() > 0) {
showVerboseMessage(verboseServer, String.join(" \\\n", argList));
}
showVerboseMessage(verboseServer, "]");
int exitCode = NativeImageBuildClient.sendRequest(serverCommand, String.join("\n", argList).getBytes(), port, out, err);
showVerboseMessage(verboseServer, "Server returns: " + exitCode);
return exitCode;
}
int sendBuildRequest(LinkedHashSet<Path> imageCP, LinkedHashSet<String> imageArgs) {
int[] requestStatus = {1};
withLockDirFileChannel(serverDir, lockFileChannel -> {
boolean abortedOnce = false;
boolean finished = false;
while (!finished) {
try (FileLock lock = lockFileChannel.tryLock()) {
if (lock != null) {
finished = true;
if (abortedOnce) {
showMessage("DONE.");
}
} else {
if (!abortedOnce) {
showMessagePart("A previous build is in progress. Aborting previous build...");
abortTask();
abortedOnce = true;
}
try {
Thread.sleep(3_000);
} catch (InterruptedException e) {
throw showError("Woke up from waiting for previous build to abort", e);
}
continue;
}
List<String> command = new ArrayList<>();
command.add(NativeImageBuildServer.TASK_PREFIX + "com.oracle.svm.hosted.NativeImageGeneratorRunner");
LinkedHashSet<Path> imagecp = new LinkedHashSet<>(serverClasspath);
imagecp.addAll(imageCP);
command.addAll(createImageBuilderArgs(imageArgs, imagecp));
showVerboseMessage(isVerbose(), "SendBuildRequest [");
showVerboseMessage(isVerbose(), String.join("\n", command));
showVerboseMessage(isVerbose(), "]");
try {
String logFileEntry = command.stream().collect(Collectors.joining(" ", Instant.now().toString() + ": ", "\n"));
Files.write(serverDir.resolve(buildRequestLog), logFileEntry.getBytes(), StandardOpenOption.CREATE, StandardOpenOption.APPEND);
updateLastBuildRequest();
} catch (IOException e) {
throw showError("Could not read/write into build-request log file", e);
}
requestStatus[0] = sendRequest(
byteStreamToByteConsumer(System.out),
byteStreamToByteConsumer(System.err),
ServerCommand.BUILD_IMAGE,
command.toArray(new String[command.size()]));
} catch (IOException e) {
throw showError("Error while trying to lock ServerDir " + serverDir, e);
}
}
});
return requestStatus[0];
}
boolean isAlive() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
boolean alive = sendRequest(byteStreamToByteConsumer(baos), byteStreamToByteConsumer(baos), ServerCommand.GET_VERSION) == NativeImageBuildClient.EXIT_SUCCESS;
showVerboseMessage(verboseServer, "Server version response: " + new String(baos.toByteArray()));
return alive;
}
void abortTask() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
sendRequest(byteStreamToByteConsumer(baos), byteStreamToByteConsumer(baos), ServerCommand.ABORT_BUILD);
showVerboseMessage(verboseServer, "Server abort response:" + new String(baos.toByteArray()));
}
synchronized void shutdown() {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
sendRequest(byteStreamToByteConsumer(baos), byteStreamToByteConsumer(baos), ServerCommand.STOP_SERVER);
showVerboseMessage(verboseServer, "Server stop response:" + new String(baos.toByteArray()));
long terminationTimeout = System.currentTimeMillis() + 20_000;
long killTimeout = terminationTimeout + 40_000;
long killedTimeout = killTimeout + 2_000;
showVerboseMessage(verboseServer, "Waiting for " + this + " to shutdown");
boolean sentSIGTERM = false;
boolean sentSIGKILL = false;
do {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw showError("Woke up from waiting for " + this + " to shutdown", e);
}
long now = System.currentTimeMillis();
if (!sentSIGTERM && terminationTimeout < now) {
showWarning(this + " keeps responding to port " + port + " even after sending STOP_SERVER");
ProcessProperties.destroy(pid);
sentSIGTERM = true;
} else if (!sentSIGKILL && killTimeout < now) {
showWarning(this + " keeps responding to port " + port + " even after destroying");
ProcessProperties.destroyForcibly(pid);
sentSIGKILL = true;
} else if (killedTimeout < now) {
throw showError(this + " keeps responding to port " + port + " even after destroying forcefully");
}
} while (isAlive());
deleteAllFiles(serverDir);
}
String getServerInfo() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getName());
sb.append("\nServerDir: ").append(serverDir);
sb.append("\nRunning for: ").append(getDurationString(since));
sb.append("\nLast build: ");
if (since.equals(lastBuildRequest)) {
sb.append("None");
} else {
sb.append(getDurationString(lastBuildRequest));
}
sb.append("\nPID: ").append(pid);
sb.append("\nPort: ").append(port);
sb.append("\nJavaArgs: ").append(String.join(" ", serverJavaArgs));
sb.append("\nBootClasspath: ").append(serverBootClasspath.stream().map(Path::toString).collect(Collectors.joining(File.pathSeparator)));
sb.append("\nClasspath: ").append(serverClasspath.stream().map(Path::toString).collect(Collectors.joining(File.pathSeparator)));
return sb.append('\n').toString();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getSimpleName());
sb.append("(");
sb.append("pid: ").append(pid);
sb.append(", ");
sb.append("port: ").append(port);
sb.append(")");
if (since.equals(lastBuildRequest)) {
sb.append('*');
}
return sb.toString();
}
private String getLivenessInfo(boolean alive) {
if (alive) {
return " running for: " + getDurationString(since);
} else {
return " DEAD";
}
}
private String getLastBuildInfo() {
if (lastBuildRequest.equals(since)) {
return " <No builds>";
} else {
return " last build: " + getDurationString(lastBuildRequest);
}
}
}
private static Consumer<byte[]> byteStreamToByteConsumer(OutputStream stream) {
return b -> {
try {
stream.write(b);
} catch (IOException e) {
throw new RuntimeException("Byte stream write failed.");
}
};
}
private String getSessionID() {
if (sessionName != null) {
return sessionDirPrefix + sessionName;
}
return sessionDirPrefix + Long.toHexString(Unistd.getsid(Math.toIntExact(ProcessProperties.getProcessID())));
}
private static String getMachineID() {
try {
return Files.lines(Paths.get("/etc/machine-id")).collect(Collectors.joining("", machineDirPrefix, ""));
} catch (Exception e) {
return machineDirPrefix + "hostid-" + Long.toHexString(Unistd.gethostid());
}
}
private Path getMachineDir() {
Path machineDir = getUserConfigDir().resolve(getMachineID());
ensureDirectoryExists(machineDir);
return machineDir;
}
private Path getSessionDir() {
Path sessionDir = getMachineDir().resolve((getSessionID()));
ensureDirectoryExists(sessionDir);
return sessionDir;
}
private static String getDurationString(Instant since) {
long seconds = ChronoUnit.SECONDS.between(since, Instant.now());
long hours = TimeUnit.SECONDS.toHours(seconds);
seconds -= TimeUnit.HOURS.toSeconds(hours);
long minutes = TimeUnit.SECONDS.toMinutes(seconds);
seconds -= TimeUnit.MINUTES.toSeconds(minutes);
return String.format("%02d:%02d:%02d", hours, minutes, seconds);
}
@SuppressWarnings("try")
private Server getServerInstance(LinkedHashSet<Path> classpath, LinkedHashSet<Path> bootClasspath, List<String> javaArgs) {
Server[] result = {null};
withFileChannel(getMachineDir().resolve("create-server.lock"), lockFileChannel -> {
try (FileLock lock = lockFileChannel(lockFileChannel)) {
List<Server> aliveServers = cleanupServers(false, true, true);
String maxServersStr = loadProperties(getMachineDir().resolve(machineProperties)).get(pKeyMaxServers);
if (maxServersStr == null || maxServersStr.isEmpty()) {
maxServersStr = getUserConfigProperties().get(pKeyMaxServers);
}
int maxServers;
if (maxServersStr == null || maxServersStr.isEmpty()) {
maxServers = 2;
} else {
maxServers = Math.max(1, Integer.parseInt(maxServersStr));
}
String xmxValueStr = getXmxValue(maxServers);
replaceArg(javaArgs, oXmx, xmxValueStr);
String xmsValueStr = getXmsValue();
long xmxValue = SubstrateOptionsParser.parseLong(xmxValueStr);
long xmsValue = SubstrateOptionsParser.parseLong(xmsValueStr);
if (WordFactory.unsigned(xmsValue).aboveThan(WordFactory.unsigned(xmxValue))) {
xmsValueStr = Long.toUnsignedString(xmxValue);
}
replaceArg(javaArgs, oXms, xmsValueStr);
Path sessionDir = getSessionDir();
List<Collection<Path>> builderPaths = new ArrayList<>(Arrays.asList(classpath, bootClasspath));
if (config.useJavaModules()) {
builderPaths.addAll(Arrays.asList(config.getBuilderModulePath(), config.getBuilderUpgradeModulePath()));
}
Path javaExePath = canonicalize(config.getJavaExecutable());
String serverUID = imageServerUID(javaExePath, javaArgs, builderPaths);
Path serverDir = sessionDir.resolve(serverDirPrefix + serverUID);
Optional<Server> reusableServer = aliveServers.stream().filter(s -> s.serverDir.equals(serverDir)).findFirst();
if (reusableServer.isPresent()) {
Server server = reusableServer.get();
if (!server.isAlive()) {
throw showError("Found defunct image-build server:" + server.getServerInfo());
}
showVerboseMessage(verboseServer, "Reuse existing image-build server: " + server);
result[0] = server;
} else {
if (aliveServers.size() >= maxServers) {
showVerboseMessage(verboseServer, "Image-build server limit reached -> remove least recently used");
Server victim = findVictim(aliveServers);
if (victim != null) {
showMessage("Shutdown " + victim);
victim.shutdown();
} else {
showWarning("Image-build server limit exceeded. Use options --server{-list,-shutdown[-all]} to fix the problem.");
}
}
Server server = startServer(javaExePath, serverDir, 0, classpath, bootClasspath, javaArgs);
if (server == null) {
showWarning("Creating image-build server failed. Fallback to one-shot image building ...");
}
result[0] = server;
}
} catch (IOException e) {
throw showError("ServerInstance-creation locking failed", e);
}
});
return result[0];
}
private static Server findVictim(List<Server> aliveServers) {
return aliveServers.stream()
.filter(Server::isAlive)
.min(Comparator.comparing(s -> s.lastBuildRequest))
.orElse(null);
}
private List<Path> getSessionDirs(boolean machineWide) {
List<Path> sessionDirs = Collections.singletonList(getSessionDir());
if (machineWide) {
try {
sessionDirs = Files.list(getMachineDir())
.filter(Files::isDirectory)
.filter(sessionDir -> sessionDir.getFileName().toString().startsWith(sessionDirPrefix))
.collect(Collectors.toList());
} catch (IOException e) {
throw showError("Accessing MachineDir " + getMachineDir() + " failed", e);
}
}
return sessionDirs;
}
private List<Server> findServers(List<Path> sessionDirs) {
ArrayList<Server> servers = new ArrayList<>();
for (Path sessionDir : sessionDirs) {
try {
Files.list(sessionDir).filter(Files::isDirectory).forEach(serverDir -> {
if (serverDir.getFileName().toString().startsWith(serverDirPrefix)) {
try {
servers.add(new Server(serverDir));
} catch (Exception e) {
showVerboseMessage(verboseServer, "Found corrupt ServerDir " + serverDir);
deleteAllFiles(serverDir);
}
}
});
} catch (IOException e) {
throw showError("Accessing SessionDir " + sessionDir + " failed", e);
}
}
return servers;
}
void listServers(boolean machineWide, boolean details) {
List<Server> servers = findServers(getSessionDirs(machineWide));
for (Server server : servers) {
String sessionInfo = machineWide ? "Session " + server.serverDir.getParent().getFileName() + " " : "";
showMessage(sessionInfo + server + server.getLivenessInfo(server.isAlive()) + server.getLastBuildInfo());
if (details) {
showMessage("Details:");
showMessage(server.getServerInfo());
}
}
}
void wipeMachineDir() {
deleteAllFiles(getMachineDir());
}
@SuppressWarnings("try")
List<Server> cleanupServers(boolean serverShutdown, boolean machineWide, boolean quiet) {
List<Path> sessionDirs = getSessionDirs(machineWide);
for (Path sessionDir : sessionDirs) {
if (isDeletedPath(sessionDir)) {
deleteAllFiles(sessionDir);
}
}
List<Server> aliveServers = new ArrayList<>();
for (Path sessionDir : sessionDirs) {
withLockDirFileChannel(sessionDir, lockFileChannel -> {
try (FileLock lock = lockFileChannel.lock()) {
List<Server> servers = findServers(Collections.singletonList(sessionDir));
for (Server server : servers) {
boolean alive = server.isAlive();
if (!alive || serverShutdown) {
if (!quiet) {
showMessage("Cleanup " + server + server.getLivenessInfo(alive));
}
server.shutdown();
} else {
aliveServers.add(server);
}
}
} catch (IOException e) {
throw showError("Locking SessionDir " + sessionDir + " failed", e);
}
});
}
return aliveServers;
}
private Server startServer(Path javaExePath, Path serverDir, int serverPort, LinkedHashSet<Path> classpath, LinkedHashSet<Path> bootClasspath, List<String> javaArgs) {
ProcessBuilder pb = new ProcessBuilder();
pb.directory(serverDir.toFile());
pb.redirectErrorStream(true);
List<String> command = pb.command();
command.add(javaExePath.toString());
if (!bootClasspath.isEmpty()) {
command.add(bootClasspath.stream().map(Path::toString).collect(Collectors.joining(File.pathSeparator, "-Xbootclasspath/a:", "")));
}
command.addAll(Arrays.asList("-cp", classpath.stream().map(Path::toString).collect(Collectors.joining(File.pathSeparator))));
command.addAll(javaArgs);
command.add("-Dgraal.LogFile=%e");
command.add("com.oracle.svm.hosted.server.NativeImageBuildServer");
command.add(NativeImageBuildServer.PORT_PREFIX + serverPort);
Path logFilePath = serverDir.resolve("server.log");
command.add(NativeImageBuildServer.LOG_PREFIX + logFilePath);
showVerboseMessage(isVerbose(), "StartServer [");
showVerboseMessage(isVerbose(), SubstrateUtil.getShellCommandString(command, true));
showVerboseMessage(isVerbose(), "]");
int childPid = NativeImageServerHelper.daemonize(() -> {
try {
ensureDirectoryExists(serverDir);
showVerboseMessage(verboseServer, "Starting new server ...");
Process process = pb.start();
long serverPID = ProcessProperties.getProcessID(process);
showVerboseMessage(verboseServer, "New image-build server pid: " + serverPID);
int selectedPort = serverPort;
if (selectedPort == 0) {
try (BufferedReader serverStdout = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) {
String line;
int readLineTries = 60;
ArrayList<String> lines = new ArrayList<>(readLineTries);
while ((line = serverStdout.readLine()) != null && --readLineTries > 0) {
lines.add(line);
if (line.startsWith(NativeImageBuildServer.PORT_LOG_MESSAGE_PREFIX)) {
String portStr = line.substring(NativeImageBuildServer.PORT_LOG_MESSAGE_PREFIX.length());
try {
selectedPort = Integer.parseInt(portStr);
break;
} catch (NumberFormatException ex) {
}
}
}
if (selectedPort == 0) {
String serverOutputMessage = "";
if (!lines.isEmpty()) {
serverOutputMessage = "\nServer stdout/stderr:\n" + String.join("\n", lines);
}
throw showError("Could not determine port for sending image-build requests." + serverOutputMessage);
}
showVerboseMessage(verboseServer, "Image-build server selected port " + selectedPort);
}
}
writeServerFile(serverDir, selectedPort, serverPID, classpath, bootClasspath, javaArgs);
} catch (Throwable e) {
deleteAllFiles(serverDir);
throw showError("Starting image-build server instance failed", e);
}
});
int exitStatus = ProcessProperties.waitForProcessExit(childPid);
showVerboseMessage(verboseServer, "Exit status forked child process: " + exitStatus);
if (exitStatus == 0) {
Server server;
try {
server = new Server(serverDir);
} catch (Exception e) {
showVerboseMessage(verboseServer, "Image-build server unusable.");
return null;
}
for (int i = 0; i < 6; i += 1) {
if (server.isAlive()) {
showVerboseMessage(verboseServer, "Image-build server found.");
return server;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
break;
}
}
showVerboseMessage(verboseServer, "Image-build server not responding.");
server.shutdown();
}
return null;
}
private static void writeServerFile(Path serverDir, int port, long pid, LinkedHashSet<Path> classpath, LinkedHashSet<Path> bootClasspath, List<String> javaArgs) throws Exception {
Properties sp = new Properties();
sp.setProperty(Server.pKeyPort, String.valueOf(port));
sp.setProperty(Server.pKeyPID, String.valueOf(pid));
sp.setProperty(Server.pKeyJavaArgs, String.join(" ", javaArgs));
sp.setProperty(Server.pKeyBCP, bootClasspath.stream().map(ClasspathUtils::classpathToString).collect(Collectors.joining(" ")));
sp.setProperty(Server.pKeyCP, classpath.stream().map(ClasspathUtils::classpathToString).collect(Collectors.joining(" ")));
Path serverPropertiesPath = serverDir.resolve(Server.serverProperties);
try (OutputStream os = Files.newOutputStream(serverPropertiesPath)) {
sp.store(os, "");
}
}
private void withLockDirFileChannel(Path lockDir, Consumer<FileChannel> consumer) {
withFileChannel(lockDir.resolve(defaultLockFileName), consumer);
}
private void withFileChannel(Path filePath, Consumer<FileChannel> consumer) {
try {
FileChannel fileChannel;
synchronized (openFileChannels) {
fileChannel = FileChannel.open(filePath, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE);
openFileChannels.add(fileChannel);
}
consumer.accept(fileChannel);
synchronized (openFileChannels) {
openFileChannels.remove(fileChannel);
fileChannel.close();
}
} catch (IOException e) {
throw showError("Using FileChannel for " + filePath + " failed", e);
}
}
private static FileLock lockFileChannel(FileChannel channel) throws IOException {
Thread lockWatcher = new Thread(() -> {
try {
Thread.sleep(TimeUnit.MINUTES.toMillis(10));
try {
showWarning("Timeout while waiting for FileChannel.lock");
channel.close();
} catch (IOException e) {
throw showError("LockWatcher closing FileChannel of LockFile failed", e);
}
} catch (InterruptedException e) {
}
});
lockWatcher.start();
FileLock lock = channel.lock();
lockWatcher.interrupt();
return lock;
}
private final class AbortBuildSignalHandler implements sun.misc.SignalHandler {
private int attemptCount = 0;
@Override
public void handle(sun.misc.Signal signal) {
Server current = building;
if (attemptCount >= 3 || current == null) {
killServer();
closeFileChannels();
System.exit(1);
} else {
attemptCount += 1;
current.abortTask();
}
}
private void closeFileChannels() {
showVerboseMessage(isVerbose(), "CleanupHandler Begin");
synchronized (openFileChannels) {
for (FileChannel fileChannel : openFileChannels) {
try {
showVerboseMessage(isVerbose(), "Closing open FileChannel: " + fileChannel);
fileChannel.close();
} catch (Exception e) {
throw showError("Closing FileChannel failed", e);
}
}
}
showVerboseMessage(isVerbose(), "CleanupHandler End");
}
private void killServer() {
showVerboseMessage(isVerbose(), "Caught interrupt. Kill Server.");
Server current = building;
if (current != null) {
current.shutdown();
}
}
}
@Override
protected int buildImage(List<String> javaArgs, LinkedHashSet<Path> bcp, LinkedHashSet<Path> cp, LinkedHashSet<String> imageArgs, LinkedHashSet<Path> imagecp) {
boolean printFlags = imageArgs.stream().anyMatch(arg -> arg.contains(enablePrintFlags) || arg.contains(enablePrintFlagsWithExtraHelp));
if (useServer && !printFlags && !useDebugAttach()) {
AbortBuildSignalHandler signalHandler = new AbortBuildSignalHandler();
sun.misc.Signal.handle(new sun.misc.Signal("TERM"), signalHandler);
sun.misc.Signal.handle(new sun.misc.Signal("INT"), signalHandler);
Server server = getServerInstance(cp, bcp, javaArgs);
if (server != null) {
showVerboseMessage(verboseServer, "\n" + server.getServerInfo() + "\n");
showMessage("Build on " + server);
building = server;
int status = server.sendBuildRequest(imagecp, imageArgs);
if (!server.isAlive()) {
cleanupServers(false, false, true);
}
return status;
}
}
return super.buildImage(javaArgs, bcp, cp, imageArgs, imagecp);
}
private static String imageServerUID(Path javaExecutable, List<String> vmArgs, List<Collection<Path>> builderPaths) {
MessageDigest digest;
try {
digest = MessageDigest.getInstance("SHA-512");
} catch (NoSuchAlgorithmException e) {
throw showError("SHA-512 digest is not available", e);
}
digest.update(javaExecutable.toString().getBytes());
for (Collection<Path> paths : builderPaths) {
for (Path path : paths) {
digest.update(path.toString().getBytes());
updateHash(digest, path);
}
}
for (String string : vmArgs) {
digest.update(string.getBytes());
}
byte[] digestBytes = digest.digest();
StringBuilder sb = new StringBuilder(digestBytes.length * 2);
for (byte b : digestBytes) {
sb.append(String.format("%02x", b & 0xff));
}
return sb.toString();
}
private static void updateHash(MessageDigest md, Path pathElement) {
try {
if (!(Files.isReadable(pathElement) && pathElement.getFileName().toString().endsWith(".jar"))) {
throw showError("Build server classpath must only contain valid jar-files: " + pathElement);
}
md.update(Files.readAllBytes(pathElement));
} catch (IOException e) {
throw showError("Problem reading classpath entries: " + e.getMessage());
}
}
void setUseServer(boolean val) {
useServer = val;
}
boolean useServer() {
return useServer;
}
@Override
protected void setDryRun(boolean val) {
super.setDryRun(val);
if (val) {
useServer = false;
}
}
void setVerboseServer(boolean val) {
verboseServer = val;
}
boolean verboseServer() {
return verboseServer;
}
void setSessionName(String val) {
if (val != null && val.isEmpty()) {
throw showError("Empty string not allowed as session-name");
}
sessionName = val;
}
}