8297444: Refactor the javacserver build tool
Reviewed-by: erikj, cstein
This commit is contained in:
parent
e846b0438c
commit
257aa15f15
@ -219,31 +219,35 @@ define SetupJavaCompilationBody
|
||||
# Use java server if it is enabled, and the user does not want a specialized
|
||||
# class path.
|
||||
ifeq ($$(ENABLE_JAVAC_SERVER)+$$($1_CLASSPATH), true+)
|
||||
$1_JAVAC := $$(INTERIM_LANGTOOLS_ARGS) -cp $(BUILDTOOLS_OUTPUTDIR)/langtools_javacserver_classes javacserver.Main
|
||||
|
||||
# Create a configuration file with the needed information for the javac
|
||||
# server to function properly.
|
||||
$1_JAVAC_SERVER_CONFIG := $$($1_BIN)$$($1_MODULE_SUBDIR)/_the.$$($1_SAFE_NAME)-server.conf
|
||||
$1_JAVAC_SERVER_CONFIG := $$($1_BIN)$$($1_MODULE_SUBDIR)/_the.$$($1_SAFE_NAME)-javacserver.conf
|
||||
|
||||
# Arguments needed to launch the javacserver client, as well as for the
|
||||
# client to launch the server.
|
||||
$1_JAVAC_SERVER_ARGS := $$(INTERIM_LANGTOOLS_ARGS) \
|
||||
-cp $(BUILDTOOLS_OUTPUTDIR)/langtools_javacserver_classes
|
||||
|
||||
# The portfile contains the tcp/ip on which the server listens
|
||||
# and the cookie necessary to talk to the server.
|
||||
$1_JAVAC_PORT_FILE := $$(call FixPath, $$(JAVAC_SERVER_DIR)/server.port)
|
||||
|
||||
# The servercmd specifies how to launch the server. This will be executed
|
||||
# by the client, if needed.
|
||||
$1_JAVAC_SERVER_CMD := $$(call FixPath, $$(JAVA) $$($1_JAVA_FLAGS) $$($1_JAVAC))
|
||||
# The javacmd tells the client how to run java to launch the server.
|
||||
$1_JAVAC_SERVER_JAVA_CMD := $$(call FixPath, $$(JAVA) $$($1_JAVA_FLAGS) \
|
||||
$$($1_JAVAC_SERVER_ARGS))
|
||||
|
||||
$1_CONFIG_VARDEPS := $$($1_JAVAC_PORT_FILE) $$($1_JAVAC_SERVER_CMD)
|
||||
$1_CONFIG_VARDEPS := $$($1_JAVAC_PORT_FILE) $$($1_JAVAC_SERVER_JAVA_CMD)
|
||||
$1_CONFIG_VARDEPS_FILE := $$(call DependOnVariable, $1_CONFIG_VARDEPS, \
|
||||
$$($1_BIN)$$($1_MODULE_SUBDIR)/_the.$1.config_vardeps)
|
||||
|
||||
# Write these values to a config file
|
||||
$$($1_JAVAC_SERVER_CONFIG): $$($1_CONFIG_VARDEPS_FILE)
|
||||
$(ECHO) portfile=$$($1_JAVAC_PORT_FILE) > $$@
|
||||
$(ECHO) servercmd=$$($1_JAVAC_SERVER_CMD) >> $$@
|
||||
$(ECHO) javacmd=$$($1_JAVAC_SERVER_JAVA_CMD) >> $$@
|
||||
|
||||
# Always use small java to launch client
|
||||
$1_JAVAC_CMD := $$(JAVA_SMALL) $$($1_JAVA_FLAGS) $$($1_JAVAC) \
|
||||
--server:conf=$$($1_JAVAC_SERVER_CONFIG)
|
||||
$1_JAVAC_CMD := $$(JAVA_SMALL) $$($1_JAVA_FLAGS) $$($1_JAVAC_SERVER_ARGS) \
|
||||
javacserver.Main --conf=$$($1_JAVAC_SERVER_CONFIG)
|
||||
else
|
||||
# No javac server
|
||||
$1_JAVAC := $$(INTERIM_LANGTOOLS_ARGS) -m jdk.compiler.interim/com.sun.tools.javac.Main
|
||||
|
@ -25,34 +25,13 @@
|
||||
|
||||
package javacserver;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import javacserver.client.ClientMain;
|
||||
import javacserver.server.ServerMain;
|
||||
|
||||
import static javacserver.options.Option.STARTSERVER;
|
||||
import javacserver.client.Client;
|
||||
|
||||
/**
|
||||
* The application entry point of the smart javac wrapper tool.
|
||||
*
|
||||
* <p><b>This is NOT part of any supported API.
|
||||
* If you write code that depends on this, you do so at your own risk.
|
||||
* This code and its internal interfaces are subject to change or
|
||||
* deletion without notice.</b>
|
||||
* The application entry point of the javacserver build tool.
|
||||
*/
|
||||
public class Main {
|
||||
|
||||
public static void main(String... args) {
|
||||
System.exit(go(args));
|
||||
}
|
||||
|
||||
public static int go(String[] args) {
|
||||
|
||||
// Server or client mode?
|
||||
boolean serverMode = Arrays.asList(args)
|
||||
.stream()
|
||||
.anyMatch(arg -> arg.startsWith(STARTSERVER.arg));
|
||||
|
||||
return serverMode ? ServerMain.run(args) : ClientMain.run(args);
|
||||
public static void main(String... args) {
|
||||
Client.main(args);
|
||||
}
|
||||
}
|
||||
|
@ -1,148 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2012, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code 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 General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package javacserver;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Utilities.
|
||||
*
|
||||
* <p><b>This is NOT part of any supported API.
|
||||
* If you write code that depends on this, you do so at your own risk.
|
||||
* This code and its internal interfaces are subject to change or
|
||||
* deletion without notice.</b>
|
||||
*/
|
||||
public class Util {
|
||||
|
||||
|
||||
|
||||
public static String extractStringOption(String opName, String s) {
|
||||
return extractStringOption(opName, s, null);
|
||||
}
|
||||
|
||||
private static String extractStringOptionWithDelimiter(String opName, String s, String deflt, char delimiter) {
|
||||
int p = s.indexOf(opName+"=");
|
||||
if (p == -1) return deflt;
|
||||
p+=opName.length()+1;
|
||||
int pe = s.indexOf(delimiter, p);
|
||||
if (pe == -1) pe = s.length();
|
||||
return s.substring(p, pe);
|
||||
}
|
||||
|
||||
public static String extractStringOption(String opName, String s, String deflt) {
|
||||
return extractStringOptionWithDelimiter(opName, s, deflt, ',');
|
||||
}
|
||||
|
||||
public static String extractStringOptionLine(String opName, String s, String deflt) {
|
||||
return extractStringOptionWithDelimiter(opName, s, deflt, '\n').strip();
|
||||
}
|
||||
|
||||
public static int extractIntOption(String opName, String s, int deflt) {
|
||||
int p = s.indexOf(opName+"=");
|
||||
if (p == -1) return deflt;
|
||||
p+=opName.length()+1;
|
||||
int pe = s.indexOf(',', p);
|
||||
if (pe == -1) pe = s.length();
|
||||
int v = 0;
|
||||
try {
|
||||
v = Integer.parseInt(s.substring(p, pe));
|
||||
} catch (Exception e) {}
|
||||
return v;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convenience method to create a set with strings.
|
||||
*/
|
||||
public static Set<String> set(String... ss) {
|
||||
Set<String> set = new HashSet<>();
|
||||
set.addAll(Arrays.asList(ss));
|
||||
return set;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize windows drive letter paths to upper case to enable string
|
||||
* comparison.
|
||||
*
|
||||
* @param file File name to normalize
|
||||
* @return The normalized string if file has a drive letter at the beginning,
|
||||
* otherwise the original string.
|
||||
*/
|
||||
public static String normalizeDriveLetter(String file) {
|
||||
if (file.length() > 2 && file.charAt(1) == ':') {
|
||||
return Character.toUpperCase(file.charAt(0)) + file.substring(1);
|
||||
} else if (file.length() > 3 && file.charAt(0) == '*'
|
||||
&& file.charAt(2) == ':') {
|
||||
// Handle a wildcard * at the beginning of the string.
|
||||
return file.substring(0, 1) + Character.toUpperCase(file.charAt(1))
|
||||
+ file.substring(2);
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
|
||||
public static <E> Set<E> union(Set<? extends E> s1,
|
||||
Set<? extends E> s2) {
|
||||
Set<E> union = new HashSet<>();
|
||||
union.addAll(s1);
|
||||
union.addAll(s2);
|
||||
return union;
|
||||
}
|
||||
|
||||
public static <E> Set<E> subtract(Set<? extends E> orig,
|
||||
Set<? extends E> toSubtract) {
|
||||
Set<E> difference = new HashSet<>(orig);
|
||||
difference.removeAll(toSubtract);
|
||||
return difference;
|
||||
}
|
||||
|
||||
public static String getStackTrace(Throwable t) {
|
||||
StringWriter sw = new StringWriter();
|
||||
t.printStackTrace(new PrintWriter(sw));
|
||||
return sw.toString();
|
||||
}
|
||||
|
||||
public static <E> Set<E> intersection(Collection<? extends E> c1,
|
||||
Collection<? extends E> c2) {
|
||||
Set<E> intersection = new HashSet<E>(c1);
|
||||
intersection.retainAll(c2);
|
||||
return intersection;
|
||||
}
|
||||
|
||||
|
||||
public static Stream<String> getLines(String str) {
|
||||
return str.isEmpty()
|
||||
? Stream.empty()
|
||||
: Stream.of(str.split(Pattern.quote(System.lineSeparator())));
|
||||
}
|
||||
}
|
191
make/langtools/tools/javacserver/client/Client.java
Normal file
191
make/langtools/tools/javacserver/client/Client.java
Normal file
@ -0,0 +1,191 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code 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 General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package javacserver.client;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.Reader;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import javacserver.server.Server;
|
||||
import javacserver.shared.PortFileInaccessibleException;
|
||||
import javacserver.shared.Protocol;
|
||||
import javacserver.shared.Result;
|
||||
import javacserver.util.AutoFlushWriter;
|
||||
import javacserver.util.Log;
|
||||
|
||||
/**
|
||||
* The javacserver client. This is called from the makefiles, and is responsible for passing the command
|
||||
* line on to a server instance running javac, starting a new server if needed.
|
||||
*/
|
||||
public class Client {
|
||||
private static final Log.Level LOG_LEVEL = Log.Level.INFO;
|
||||
|
||||
// Wait 2 seconds for response, before giving up on javac server.
|
||||
private static final int CONNECTION_TIMEOUT = 2000;
|
||||
private static final int MAX_CONNECT_ATTEMPTS = 3;
|
||||
private static final int WAIT_BETWEEN_CONNECT_ATTEMPTS = 2000;
|
||||
|
||||
private final ClientConfiguration conf;
|
||||
|
||||
public Client(ClientConfiguration conf) {
|
||||
this.conf = conf;
|
||||
}
|
||||
|
||||
public static void main(String... args) {
|
||||
Log.setLogForCurrentThread(new Log(
|
||||
new AutoFlushWriter(new OutputStreamWriter(System.out)),
|
||||
new AutoFlushWriter(new OutputStreamWriter(System.err))));
|
||||
Log.setLogLevel(LOG_LEVEL);
|
||||
|
||||
ClientConfiguration conf = ClientConfiguration.fromCommandLineArguments(args);
|
||||
if (conf == null) {
|
||||
System.exit(Result.CMDERR.exitCode);
|
||||
}
|
||||
|
||||
Client client = new Client(conf);
|
||||
int exitCode = client.dispatchToServer();
|
||||
|
||||
System.exit(exitCode);
|
||||
}
|
||||
|
||||
private int dispatchToServer() {
|
||||
try {
|
||||
// Check if server seems to be already running
|
||||
if (!conf.portFile().hasValidValues()) {
|
||||
// Fork a new server and wait for it to start
|
||||
startNewServer();
|
||||
}
|
||||
|
||||
try (Socket socket = tryConnect()) {
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||
PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
|
||||
|
||||
Protocol.sendCommand(out, conf.javacArgs());
|
||||
int exitCode = Protocol.readResponse(in);
|
||||
|
||||
return exitCode;
|
||||
}
|
||||
} catch (PortFileInaccessibleException e) {
|
||||
Log.error("Port file inaccessible.");
|
||||
return Result.ERROR.exitCode;
|
||||
} catch (IOException ioe) {
|
||||
Log.error("IOException caught during compilation: " + ioe.getMessage());
|
||||
Log.debug(ioe);
|
||||
return Result.ERROR.exitCode;
|
||||
} catch (InterruptedException ie) {
|
||||
Thread.currentThread().interrupt(); // Restore interrupt
|
||||
Log.error("Compilation interrupted.");
|
||||
Log.debug(ie);
|
||||
return Result.ERROR.exitCode;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Makes MAX_CONNECT_ATTEMPTS attempts to connect to server.
|
||||
*/
|
||||
private Socket tryConnect() throws IOException, InterruptedException {
|
||||
int attempt = 0;
|
||||
|
||||
while (true) {
|
||||
Log.debug("Trying to connect. Attempt " + (++attempt) + " of " + MAX_CONNECT_ATTEMPTS);
|
||||
try {
|
||||
Socket socket = new Socket();
|
||||
InetAddress localhost = InetAddress.getByName(null);
|
||||
InetSocketAddress address = new InetSocketAddress(localhost, conf.portFile().getPort());
|
||||
socket.connect(address, CONNECTION_TIMEOUT);
|
||||
Log.debug("Connected");
|
||||
return socket;
|
||||
} catch (IOException ex) {
|
||||
Log.error("Connection attempt failed: " + ex.getMessage());
|
||||
if (attempt >= MAX_CONNECT_ATTEMPTS) {
|
||||
Log.error("Giving up");
|
||||
throw new IOException("Could not connect to server", ex);
|
||||
}
|
||||
}
|
||||
Thread.sleep(WAIT_BETWEEN_CONNECT_ATTEMPTS);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Fork a server process and wait for server to come around
|
||||
*/
|
||||
private void startNewServer() throws IOException, InterruptedException {
|
||||
List<String> cmd = new ArrayList<>();
|
||||
// conf.javaCommand() is how to start java in the way we want to run
|
||||
// the server
|
||||
cmd.addAll(Arrays.asList(conf.javaCommand().split(" ")));
|
||||
// javacserver.server.Server is the server main class
|
||||
cmd.add(Server.class.getName());
|
||||
// and it expects a port file path
|
||||
cmd.add(conf.portFile().getFilename());
|
||||
|
||||
Process serverProcess;
|
||||
Log.debug("Starting server. Command: " + String.join(" ", cmd));
|
||||
try {
|
||||
// If the cmd for some reason can't be executed (file is not found,
|
||||
// or is not executable for instance) this will throw an
|
||||
// IOException
|
||||
serverProcess = new ProcessBuilder(cmd).redirectErrorStream(true).start();
|
||||
} catch (IOException ex) {
|
||||
// Message is typically something like:
|
||||
// Cannot run program "xyz": error=2, No such file or directory
|
||||
Log.error("Failed to create server process: " + ex.getMessage());
|
||||
Log.debug(ex);
|
||||
throw new IOException(ex);
|
||||
}
|
||||
|
||||
// serverProcess != null at this point.
|
||||
try {
|
||||
// Throws an IOException if no valid values materialize
|
||||
conf.portFile().waitForValidValues();
|
||||
} catch (IOException ex) {
|
||||
// Process was started, but server failed to initialize. This could
|
||||
// for instance be due to the JVM not finding the server class,
|
||||
// or the server running in to some exception early on.
|
||||
Log.error("javacserver server process failed to initialize: " + ex.getMessage());
|
||||
Log.error("Process output:");
|
||||
Reader serverStdoutStderr = new InputStreamReader(serverProcess.getInputStream());
|
||||
try (BufferedReader br = new BufferedReader(serverStdoutStderr)) {
|
||||
br.lines().forEach(Log::error);
|
||||
}
|
||||
Log.error("<End of process output>");
|
||||
try {
|
||||
Log.error("Process exit code: " + serverProcess.exitValue());
|
||||
} catch (IllegalThreadStateException e) {
|
||||
// Server is presumably still running.
|
||||
}
|
||||
throw new IOException("Server failed to initialize: " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
}
|
131
make/langtools/tools/javacserver/client/ClientConfiguration.java
Normal file
131
make/langtools/tools/javacserver/client/ClientConfiguration.java
Normal file
@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code 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 General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package javacserver.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import javacserver.shared.PortFile;
|
||||
import javacserver.util.Log;
|
||||
|
||||
/**
|
||||
* Description of the arguments needed to start a javacserver client, as extracted from
|
||||
* the command line and configuration file.
|
||||
*/
|
||||
public record ClientConfiguration(PortFile portFile, String javaCommand, String[] javacArgs) {
|
||||
static ClientConfiguration fromCommandLineArguments(String... args) {
|
||||
String confFileName = getConfFileName(args);
|
||||
if (confFileName == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String confFileContent = getConfFileContent(confFileName);
|
||||
if (confFileContent == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String portFileName = getPortFileName(confFileContent);
|
||||
if (portFileName == null) {
|
||||
return null;
|
||||
}
|
||||
String javaCommand = getJavaCommandString(confFileContent);
|
||||
if (javaCommand == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
PortFile portFile = new PortFile(portFileName);
|
||||
String[] javacArgs = Arrays.copyOfRange(args, 1, args.length);
|
||||
|
||||
ClientConfiguration conf = new ClientConfiguration(portFile, javaCommand, javacArgs);
|
||||
return conf;
|
||||
}
|
||||
|
||||
private static String getConfFileName(String[] args) {
|
||||
if (args.length < 1) {
|
||||
Log.error("Error: javacserver client: missing --conf=<conf file> argument");
|
||||
return null;
|
||||
}
|
||||
String[] conf = args[0].split("=", 2);
|
||||
if (conf.length != 2 || !conf[0].equalsIgnoreCase("--conf")) {
|
||||
Log.error("Error: javacserver client: first argument must be --conf=<conf file>");
|
||||
return null;
|
||||
}
|
||||
String confFileName = conf[1];
|
||||
if (!Files.exists(Path.of(confFileName))) {
|
||||
Log.error("Error: javacserver client: specified conf file does not exist");
|
||||
return null;
|
||||
}
|
||||
return confFileName;
|
||||
}
|
||||
|
||||
private static String getConfFileContent(String confFile) {
|
||||
try {
|
||||
List<String> confFileLines = Files.readAllLines(Path.of(confFile));
|
||||
String confFileContent = String.join("\n", confFileLines);
|
||||
return confFileContent;
|
||||
} catch (IOException e) {
|
||||
Log.error("Cannot read configuration file " + confFile);
|
||||
Log.debug(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String getJavaCommandString(String confFileContent) {
|
||||
String serverCommandString = getConfValue("javacmd", confFileContent);
|
||||
if (serverCommandString.isEmpty()) {
|
||||
Log.error("Configuration file missing value for 'javacmd'");
|
||||
return null;
|
||||
} else {
|
||||
return serverCommandString;
|
||||
}
|
||||
}
|
||||
|
||||
private static String getPortFileName(String confFileContent) {
|
||||
String portfileName = getConfValue("portfile", confFileContent);
|
||||
if (portfileName.isEmpty()) {
|
||||
Log.error("Configuration file missing value for 'portfile'");
|
||||
return null;
|
||||
} else {
|
||||
return portfileName;
|
||||
}
|
||||
}
|
||||
|
||||
private static String getConfValue(String optionName, String content) {
|
||||
String result;
|
||||
int p = content.indexOf(optionName + "=");
|
||||
if (p == -1) {
|
||||
result = "";
|
||||
} else {
|
||||
p += optionName.length() + 1;
|
||||
int pe = content.indexOf('\n', p);
|
||||
if (pe == -1) pe = content.length();
|
||||
result = content.substring(p, pe);
|
||||
}
|
||||
return result.strip();
|
||||
}
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code 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 General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package javacserver.client;
|
||||
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
|
||||
import javacserver.AutoFlushWriter;
|
||||
import javacserver.Log;
|
||||
import javacserver.Result;
|
||||
import javacserver.comp.SjavacImpl;
|
||||
import javacserver.options.Options;
|
||||
import javacserver.server.Sjavac;
|
||||
|
||||
/**
|
||||
* <p><b>This is NOT part of any supported API.
|
||||
* If you write code that depends on this, you do so at your own risk.
|
||||
* This code and its internal interfaces are subject to change or
|
||||
* deletion without notice.</b>
|
||||
*/
|
||||
public class ClientMain {
|
||||
|
||||
public static int run(String[] args) {
|
||||
return run(args,
|
||||
new AutoFlushWriter(new OutputStreamWriter(System.out)),
|
||||
new AutoFlushWriter(new OutputStreamWriter(System.err)));
|
||||
}
|
||||
|
||||
public static int run(String[] args, Writer out, Writer err) {
|
||||
|
||||
Log.setLogForCurrentThread(new Log(out, err));
|
||||
|
||||
Options options;
|
||||
try {
|
||||
options = Options.parseArgs(args);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.error(e.getMessage());
|
||||
return Result.CMDERR.exitCode;
|
||||
}
|
||||
|
||||
Log.setLogLevel(options.getLogLevel());
|
||||
|
||||
// Prepare sjavac object
|
||||
boolean useServer = options.getServerConf() != null;
|
||||
Sjavac sjavac = useServer ? new SjavacClient(options) : new SjavacImpl();
|
||||
|
||||
// Perform compilation
|
||||
Result result = sjavac.compile(args);
|
||||
|
||||
// If sjavac is running in the foreground we should shut it down at this point
|
||||
if (!useServer) {
|
||||
sjavac.shutdown();
|
||||
}
|
||||
|
||||
return result.exitCode;
|
||||
}
|
||||
}
|
@ -1,280 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code 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 General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package javacserver.client;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.Reader;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import javacserver.Log;
|
||||
import javacserver.Result;
|
||||
import javacserver.Util;
|
||||
import javacserver.options.Options;
|
||||
import javacserver.server.PortFile;
|
||||
import javacserver.server.Sjavac;
|
||||
import javacserver.server.SjavacServer;
|
||||
|
||||
/**
|
||||
* Sjavac implementation that delegates requests to a SjavacServer.
|
||||
*
|
||||
* <p><b>This is NOT part of any supported API.
|
||||
* If you write code that depends on this, you do so at your own risk.
|
||||
* This code and its internal interfaces are subject to change or
|
||||
* deletion without notice.</b>
|
||||
*/
|
||||
public class SjavacClient implements Sjavac {
|
||||
|
||||
private PortFile portFile;
|
||||
|
||||
// The servercmd option specifies how the server part of sjavac is spawned.
|
||||
// It should point to a javacserver.Main that supports --startserver
|
||||
private String serverCommand;
|
||||
|
||||
// Accept 120 seconds of inactivity before quitting.
|
||||
private static final int KEEPALIVE = 120;
|
||||
private static final int POOLSIZE = Runtime.getRuntime().availableProcessors();
|
||||
// Wait 2 seconds for response, before giving up on javac server.
|
||||
private static final int CONNECTION_TIMEOUT = 2000;
|
||||
private static final int MAX_CONNECT_ATTEMPTS = 3;
|
||||
private static final int WAIT_BETWEEN_CONNECT_ATTEMPTS = 2000;
|
||||
|
||||
public SjavacClient(Options options) {
|
||||
String serverConf = options.getServerConf();
|
||||
String configFile = Util.extractStringOption("conf", serverConf, "");
|
||||
|
||||
try {
|
||||
List<String> configFileLines = Files.readAllLines(Path.of(configFile));
|
||||
String configFileContent = String.join("\n", configFileLines);
|
||||
|
||||
String portfileName = Util.extractStringOptionLine("portfile", configFileContent, "");
|
||||
if (portfileName.isEmpty()) {
|
||||
Log.error("Configuration file missing value for 'portfile'");
|
||||
portFile = null;
|
||||
} else {
|
||||
portFile = SjavacServer.getPortFile(portfileName);
|
||||
}
|
||||
|
||||
String serverCommandString = Util.extractStringOptionLine("servercmd", configFileContent, "");
|
||||
if (serverCommandString.isEmpty()) {
|
||||
Log.error("Configuration file missing value for 'servercmd'");
|
||||
serverCommand = null;
|
||||
} else {
|
||||
serverCommand = serverCommandString;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.error("Cannot read configuration file " + configFile);
|
||||
Log.debug(e);
|
||||
portFile = null;
|
||||
serverCommand = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result compile(String[] args) {
|
||||
if (portFile == null || serverCommand == null) {
|
||||
Log.error("Incorrect configuration, portfile and/or servercmd missing");
|
||||
return Result.ERROR;
|
||||
}
|
||||
|
||||
Result result = null;
|
||||
try (Socket socket = tryConnect()) {
|
||||
PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||
|
||||
// Send args array to server
|
||||
out.println(args.length);
|
||||
for (String arg : args)
|
||||
out.println(arg);
|
||||
out.flush();
|
||||
|
||||
// Read server response line by line
|
||||
String line;
|
||||
while (null != (line = in.readLine())) {
|
||||
if (!line.contains(":")) {
|
||||
throw new AssertionError("Could not parse protocol line: >>\"" + line + "\"<<");
|
||||
}
|
||||
String[] typeAndContent = line.split(":", 2);
|
||||
String type = typeAndContent[0];
|
||||
String content = typeAndContent[1];
|
||||
|
||||
try {
|
||||
if (Log.isDebugging()) {
|
||||
// Distinguish server generated output if debugging.
|
||||
content = "[sjavac-server] " + content;
|
||||
}
|
||||
Log.log(Log.Level.valueOf(type), content);
|
||||
continue;
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Parsing of 'type' as log level failed.
|
||||
}
|
||||
|
||||
if (type.equals(SjavacServer.LINE_TYPE_RC)) {
|
||||
result = Result.valueOf(content);
|
||||
}
|
||||
}
|
||||
} catch (PortFileInaccessibleException e) {
|
||||
Log.error("Port file inaccessible.");
|
||||
result = Result.ERROR;
|
||||
} catch (IOException ioe) {
|
||||
Log.error("IOException caught during compilation: " + ioe.getMessage());
|
||||
Log.debug(ioe);
|
||||
result = Result.ERROR;
|
||||
} catch (InterruptedException ie) {
|
||||
Thread.currentThread().interrupt(); // Restore interrupt
|
||||
Log.error("Compilation interrupted.");
|
||||
Log.debug(ie);
|
||||
result = Result.ERROR;
|
||||
}
|
||||
|
||||
if (result == null) {
|
||||
// No LINE_TYPE_RC was found.
|
||||
result = Result.ERROR;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Makes MAX_CONNECT_ATTEMPTS attempts to connect to server.
|
||||
*/
|
||||
private Socket tryConnect() throws IOException, InterruptedException {
|
||||
makeSureServerIsRunning();
|
||||
int attempt = 0;
|
||||
while (true) {
|
||||
Log.debug("Trying to connect. Attempt " + (++attempt) + " of " + MAX_CONNECT_ATTEMPTS);
|
||||
try {
|
||||
return makeConnectionAttempt();
|
||||
} catch (IOException ex) {
|
||||
Log.error("Connection attempt failed: " + ex.getMessage());
|
||||
if (attempt >= MAX_CONNECT_ATTEMPTS) {
|
||||
Log.error("Giving up");
|
||||
throw new IOException("Could not connect to server", ex);
|
||||
}
|
||||
}
|
||||
Thread.sleep(WAIT_BETWEEN_CONNECT_ATTEMPTS);
|
||||
}
|
||||
}
|
||||
|
||||
private Socket makeConnectionAttempt() throws IOException {
|
||||
Socket socket = new Socket();
|
||||
InetAddress localhost = InetAddress.getByName(null);
|
||||
InetSocketAddress address = new InetSocketAddress(localhost, portFile.getPort());
|
||||
socket.connect(address, CONNECTION_TIMEOUT);
|
||||
Log.debug("Connected");
|
||||
return socket;
|
||||
}
|
||||
|
||||
/*
|
||||
* Will return immediately if a server already seems to be running,
|
||||
* otherwise fork a new server and block until it seems to be running.
|
||||
*/
|
||||
private void makeSureServerIsRunning()
|
||||
throws IOException, InterruptedException {
|
||||
|
||||
if (portFile.exists()) {
|
||||
portFile.lock();
|
||||
portFile.getValues();
|
||||
portFile.unlock();
|
||||
|
||||
if (portFile.containsPortInfo()) {
|
||||
// Server seems to already be running
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Fork a new server and wait for it to start
|
||||
startNewServer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
// Nothing to clean up
|
||||
}
|
||||
|
||||
/*
|
||||
* Fork a server process process and wait for server to come around
|
||||
*/
|
||||
public void startNewServer()
|
||||
throws IOException, InterruptedException {
|
||||
List<String> cmd = new ArrayList<>();
|
||||
cmd.addAll(Arrays.asList(serverCommand.split(" ")));
|
||||
cmd.add("--startserver:"
|
||||
+ "portfile=" + portFile.getFilename()
|
||||
+ ",poolsize=" + POOLSIZE
|
||||
+ ",keepalive="+ KEEPALIVE);
|
||||
|
||||
Process serverProcess;
|
||||
Log.debug("Starting server. Command: " + String.join(" ", cmd));
|
||||
try {
|
||||
// If the cmd for some reason can't be executed (file is not found,
|
||||
// or is not executable for instance) this will throw an
|
||||
// IOException and p == null.
|
||||
serverProcess = new ProcessBuilder(cmd)
|
||||
.redirectErrorStream(true)
|
||||
.start();
|
||||
} catch (IOException ex) {
|
||||
// Message is typically something like:
|
||||
// Cannot run program "xyz": error=2, No such file or directory
|
||||
Log.error("Failed to create server process: " + ex.getMessage());
|
||||
Log.debug(ex);
|
||||
throw new IOException(ex);
|
||||
}
|
||||
|
||||
// serverProcess != null at this point.
|
||||
try {
|
||||
// Throws an IOException if no valid values materialize
|
||||
portFile.waitForValidValues();
|
||||
} catch (IOException ex) {
|
||||
// Process was started, but server failed to initialize. This could
|
||||
// for instance be due to the JVM not finding the server class,
|
||||
// or the server running in to some exception early on.
|
||||
Log.error("Sjavac server failed to initialize: " + ex.getMessage());
|
||||
Log.error("Process output:");
|
||||
Reader serverStdoutStderr = new InputStreamReader(serverProcess.getInputStream());
|
||||
try (BufferedReader br = new BufferedReader(serverStdoutStderr)) {
|
||||
br.lines().forEach(Log::error);
|
||||
}
|
||||
Log.error("<End of process output>");
|
||||
try {
|
||||
Log.error("Process exit code: " + serverProcess.exitValue());
|
||||
} catch (IllegalThreadStateException e) {
|
||||
// Server is presumably still running.
|
||||
}
|
||||
throw new IOException("Server failed to initialize: " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code 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 General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package javacserver.comp;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.sun.tools.javac.Main;
|
||||
|
||||
import javacserver.Log;
|
||||
import javacserver.Result;
|
||||
import javacserver.Util;
|
||||
import javacserver.options.Option;
|
||||
import javacserver.server.Sjavac;
|
||||
|
||||
/**
|
||||
* The sjavac implementation that interacts with javac and performs the actual
|
||||
* compilation.
|
||||
*
|
||||
* <p><b>This is NOT part of any supported API.
|
||||
* If you write code that depends on this, you do so at your own risk.
|
||||
* This code and its internal interfaces are subject to change or
|
||||
* deletion without notice.</b>
|
||||
*/
|
||||
public class SjavacImpl implements Sjavac {
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecated")
|
||||
public Result compile(String[] args) {
|
||||
// Direct logging to our byte array stream.
|
||||
StringWriter strWriter = new StringWriter();
|
||||
PrintWriter printWriter = new PrintWriter(strWriter);
|
||||
|
||||
// Prepare arguments
|
||||
String[] passThroughArgs = Stream.of(args)
|
||||
.filter(arg -> !arg.startsWith(Option.SERVER.arg))
|
||||
.toArray(String[]::new);
|
||||
// Compile
|
||||
int exitcode = Main.compile(passThroughArgs, printWriter);
|
||||
Result result = Result.of(exitcode);
|
||||
|
||||
// Process compiler output (which is always errors)
|
||||
printWriter.flush();
|
||||
Util.getLines(strWriter.toString()).forEach(Log::error);
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
// Nothing to clean up
|
||||
}
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code 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 General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package javacserver.options;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* <p><b>This is NOT part of any supported API.
|
||||
* If you write code that depends on this, you do so at your own risk.
|
||||
* This code and its internal interfaces are subject to change or
|
||||
* deletion without notice.</b>
|
||||
*/
|
||||
public class ArgumentIterator implements Iterator<String> {
|
||||
|
||||
/** The underlying argument iterator */
|
||||
private Iterator<String> iter;
|
||||
|
||||
/** Extra state used to implement peek and current */
|
||||
private String current;
|
||||
private String buffered;
|
||||
|
||||
public ArgumentIterator(Iterable<String> iter) {
|
||||
this.iter = iter.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return buffered != null || iter.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String next() {
|
||||
fillBuffer();
|
||||
current = buffered;
|
||||
buffered = null;
|
||||
return current;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the last element returned by next() (or {@code null} if next has
|
||||
* never been invoked on this iterator).
|
||||
*/
|
||||
public String current() {
|
||||
return current;
|
||||
}
|
||||
|
||||
/** Can't remove current element, since we may have buffered it. */
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the next element without advancing the iterator
|
||||
*/
|
||||
public String peek() {
|
||||
fillBuffer();
|
||||
return buffered;
|
||||
}
|
||||
|
||||
private void fillBuffer() {
|
||||
if (buffered == null && iter.hasNext())
|
||||
buffered = iter.next();
|
||||
}
|
||||
|
||||
}
|
@ -1,294 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 1999, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code 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 General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package javacserver.options;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Various utility methods for processing Java tool command line arguments.
|
||||
*
|
||||
* <p><b>This is NOT part of any supported API.
|
||||
* If you write code that depends on this, you do so at your own risk.
|
||||
* This code and its internal interfaces are subject to change or
|
||||
* deletion without notice.</b>
|
||||
*/
|
||||
public class CommandLine {
|
||||
/**
|
||||
* Process Win32-style command files for the specified command line
|
||||
* arguments and return the resulting arguments. A command file argument
|
||||
* is of the form '@file' where 'file' is the name of the file whose
|
||||
* contents are to be parsed for additional arguments. The contents of
|
||||
* the command file are parsed using StreamTokenizer and the original
|
||||
* '@file' argument replaced with the resulting tokens. Recursive command
|
||||
* files are not supported. The '@' character itself can be quoted with
|
||||
* the sequence '@@'.
|
||||
* @param args the arguments that may contain @files
|
||||
* @return the arguments, with @files expanded
|
||||
* @throws IOException if there is a problem reading any of the @files
|
||||
*/
|
||||
public static List<String> parse(List<String> args) throws IOException {
|
||||
List<String> newArgs = new ArrayList<>();
|
||||
appendParsedCommandArgs(newArgs, args);
|
||||
return newArgs;
|
||||
}
|
||||
|
||||
private static void appendParsedCommandArgs(List<String> newArgs, List<String> args) throws IOException {
|
||||
for (String arg : args) {
|
||||
if (arg.length() > 1 && arg.charAt(0) == '@') {
|
||||
arg = arg.substring(1);
|
||||
if (arg.charAt(0) == '@') {
|
||||
newArgs.add(arg);
|
||||
} else {
|
||||
loadCmdFile(arg, newArgs);
|
||||
}
|
||||
} else {
|
||||
newArgs.add(arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the given environment variable and appends any Win32-style
|
||||
* command files for the specified command line arguments and return
|
||||
* the resulting arguments. A command file argument
|
||||
* is of the form '@file' where 'file' is the name of the file whose
|
||||
* contents are to be parsed for additional arguments. The contents of
|
||||
* the command file are parsed using StreamTokenizer and the original
|
||||
* '@file' argument replaced with the resulting tokens. Recursive command
|
||||
* files are not supported. The '@' character itself can be quoted with
|
||||
* the sequence '@@'.
|
||||
* @param envVariable the env variable to process
|
||||
* @param args the arguments that may contain @files
|
||||
* @return the arguments, with environment variable's content and expansion of @files
|
||||
* @throws IOException if there is a problem reading any of the @files
|
||||
* @throws UnmatchedQuote
|
||||
*/
|
||||
public static List<String> parse(String envVariable, List<String> args)
|
||||
throws IOException, UnmatchedQuote {
|
||||
|
||||
List<String> inArgs = new ArrayList<>();
|
||||
appendParsedEnvVariables(inArgs, envVariable);
|
||||
inArgs.addAll(args);
|
||||
List<String> newArgs = new ArrayList<>();
|
||||
appendParsedCommandArgs(newArgs, inArgs);
|
||||
return newArgs;
|
||||
}
|
||||
|
||||
private static void loadCmdFile(String name, List<String> args) throws IOException {
|
||||
try (Reader r = Files.newBufferedReader(Paths.get(name), Charset.defaultCharset())) {
|
||||
Tokenizer t = new Tokenizer(r);
|
||||
String s;
|
||||
while ((s = t.nextToken()) != null) {
|
||||
args.add(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class Tokenizer {
|
||||
private final Reader in;
|
||||
private int ch;
|
||||
|
||||
public Tokenizer(Reader in) throws IOException {
|
||||
this.in = in;
|
||||
ch = in.read();
|
||||
}
|
||||
|
||||
public String nextToken() throws IOException {
|
||||
skipWhite();
|
||||
if (ch == -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
char quoteChar = 0;
|
||||
|
||||
while (ch != -1) {
|
||||
switch (ch) {
|
||||
case ' ':
|
||||
case '\t':
|
||||
case '\f':
|
||||
if (quoteChar == 0) {
|
||||
return sb.toString();
|
||||
}
|
||||
sb.append((char) ch);
|
||||
break;
|
||||
|
||||
case '\n':
|
||||
case '\r':
|
||||
return sb.toString();
|
||||
|
||||
case '\'':
|
||||
case '"':
|
||||
if (quoteChar == 0) {
|
||||
quoteChar = (char) ch;
|
||||
} else if (quoteChar == ch) {
|
||||
quoteChar = 0;
|
||||
} else {
|
||||
sb.append((char) ch);
|
||||
}
|
||||
break;
|
||||
|
||||
case '\\':
|
||||
if (quoteChar != 0) {
|
||||
ch = in.read();
|
||||
switch (ch) {
|
||||
case '\n':
|
||||
case '\r':
|
||||
while (ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t' || ch == '\f') {
|
||||
ch = in.read();
|
||||
}
|
||||
continue;
|
||||
|
||||
case 'n':
|
||||
ch = '\n';
|
||||
break;
|
||||
case 'r':
|
||||
ch = '\r';
|
||||
break;
|
||||
case 't':
|
||||
ch = '\t';
|
||||
break;
|
||||
case 'f':
|
||||
ch = '\f';
|
||||
break;
|
||||
}
|
||||
}
|
||||
sb.append((char) ch);
|
||||
break;
|
||||
|
||||
default:
|
||||
sb.append((char) ch);
|
||||
}
|
||||
|
||||
ch = in.read();
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
void skipWhite() throws IOException {
|
||||
while (ch != -1) {
|
||||
switch (ch) {
|
||||
case ' ':
|
||||
case '\t':
|
||||
case '\n':
|
||||
case '\r':
|
||||
case '\f':
|
||||
break;
|
||||
|
||||
case '#':
|
||||
ch = in.read();
|
||||
while (ch != '\n' && ch != '\r' && ch != -1) {
|
||||
ch = in.read();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
ch = in.read();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("fallthrough")
|
||||
private static void appendParsedEnvVariables(List<String> newArgs, String envVariable)
|
||||
throws UnmatchedQuote {
|
||||
|
||||
if (envVariable == null) {
|
||||
return;
|
||||
}
|
||||
String in = System.getenv(envVariable);
|
||||
if (in == null || in.trim().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final char NUL = (char)0;
|
||||
final int len = in.length();
|
||||
|
||||
int pos = 0;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
char quote = NUL;
|
||||
char ch;
|
||||
|
||||
loop:
|
||||
while (pos < len) {
|
||||
ch = in.charAt(pos);
|
||||
switch (ch) {
|
||||
case '\"': case '\'':
|
||||
if (quote == NUL) {
|
||||
quote = ch;
|
||||
} else if (quote == ch) {
|
||||
quote = NUL;
|
||||
} else {
|
||||
sb.append(ch);
|
||||
}
|
||||
pos++;
|
||||
break;
|
||||
case '\f': case '\n': case '\r': case '\t': case ' ':
|
||||
if (quote == NUL) {
|
||||
newArgs.add(sb.toString());
|
||||
sb.setLength(0);
|
||||
while (ch == '\f' || ch == '\n' || ch == '\r' || ch == '\t' || ch == ' ') {
|
||||
pos++;
|
||||
if (pos >= len) {
|
||||
break loop;
|
||||
}
|
||||
ch = in.charAt(pos);
|
||||
}
|
||||
break;
|
||||
}
|
||||
// fall through
|
||||
default:
|
||||
sb.append(ch);
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
if (sb.length() != 0) {
|
||||
newArgs.add(sb.toString());
|
||||
}
|
||||
if (quote != NUL) {
|
||||
throw new UnmatchedQuote(envVariable);
|
||||
}
|
||||
}
|
||||
|
||||
public static class UnmatchedQuote extends Exception {
|
||||
private static final long serialVersionUID = 0;
|
||||
|
||||
public final String variableName;
|
||||
|
||||
UnmatchedQuote(String variable) {
|
||||
this.variableName = variable;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,100 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code 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 General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package javacserver.options;
|
||||
|
||||
/**
|
||||
* Sjavac options can be classified as:
|
||||
*
|
||||
* (1) relevant only for sjavac, such as --server
|
||||
* (2) relevant for sjavac and javac, such as -d, or
|
||||
* (3) relevant only for javac, such as -g.
|
||||
*
|
||||
* This enum represents all options from (1) and (2). Note that instances of
|
||||
* this enum only entail static information about the option. For storage of
|
||||
* option values, refer to Options.
|
||||
*
|
||||
* <p><b>This is NOT part of any supported API.
|
||||
* If you write code that depends on this, you do so at your own risk.
|
||||
* This code and its internal interfaces are subject to change or
|
||||
* deletion without notice.</b>
|
||||
*/
|
||||
public enum Option {
|
||||
SERVER("--server:", "Specify server configuration file of running server") {
|
||||
@Override
|
||||
protected void processMatching(ArgumentIterator iter, Options.ArgDecoderOptionHelper helper) {
|
||||
helper.serverConf(iter.current().substring(arg.length()));
|
||||
}
|
||||
},
|
||||
STARTSERVER("--startserver:", "Start server and use the given configuration file") {
|
||||
@Override
|
||||
protected void processMatching(ArgumentIterator iter, Options.ArgDecoderOptionHelper helper) {
|
||||
helper.startServerConf(iter.current().substring(arg.length()));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
public final String arg;
|
||||
|
||||
final String description;
|
||||
|
||||
private Option(String arg, String description) {
|
||||
this.arg = arg;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
// Future cleanup: Change the "=" syntax to ":" syntax to be consistent and
|
||||
// to follow the javac-option style.
|
||||
|
||||
public boolean hasOption() {
|
||||
return arg.endsWith(":") || arg.endsWith("=");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Process current argument of argIter.
|
||||
*
|
||||
* It's final, since the option customization is typically done in
|
||||
* processMatching.
|
||||
*
|
||||
* @param argIter Iterator to read current and succeeding arguments from.
|
||||
* @param helper The helper to report back to.
|
||||
* @return true iff the argument was processed by this option.
|
||||
*/
|
||||
public final boolean processCurrent(ArgumentIterator argIter,
|
||||
Options.ArgDecoderOptionHelper helper) {
|
||||
String fullArg = argIter.current(); // "-tr" or "-log=level"
|
||||
if (hasOption() ? fullArg.startsWith(arg) : fullArg.equals(arg)) {
|
||||
processMatching(argIter, helper);
|
||||
return true;
|
||||
}
|
||||
// Did not match
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Called by process if the current argument matches this option. */
|
||||
protected abstract void processMatching(ArgumentIterator argIter,
|
||||
Options.ArgDecoderOptionHelper helper);
|
||||
}
|
@ -1,121 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code 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 General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package javacserver.options;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Instances of this class represent values for sjavac command line options.
|
||||
*
|
||||
* <p><b>This is NOT part of any supported API.
|
||||
* If you write code that depends on this, you do so at your own risk.
|
||||
* This code and its internal interfaces are subject to change or
|
||||
* deletion without notice.</b>
|
||||
*/
|
||||
public class Options {
|
||||
private String logLevel = "info";
|
||||
|
||||
private boolean startServer = false;
|
||||
|
||||
// Server configuration string
|
||||
private String serverConf;
|
||||
|
||||
/** Get the log level. */
|
||||
public String getLogLevel() {
|
||||
return logLevel;
|
||||
}
|
||||
|
||||
/** Return true iff a new server should be started */
|
||||
public boolean startServerFlag() {
|
||||
return startServer;
|
||||
}
|
||||
|
||||
/** Return the server configuration string. */
|
||||
public String getServerConf() {
|
||||
return serverConf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the given argument array and returns a corresponding Options
|
||||
* instance.
|
||||
*/
|
||||
public static Options parseArgs(String... args) {
|
||||
Options options = new Options();
|
||||
options.new ArgDecoderOptionHelper().traverse(args);
|
||||
return options;
|
||||
}
|
||||
|
||||
// OptionHelper that records the traversed options in this Options instance.
|
||||
public class ArgDecoderOptionHelper {
|
||||
public void reportError(String msg) {
|
||||
throw new IllegalArgumentException(msg);
|
||||
}
|
||||
|
||||
public void serverConf(String conf) {
|
||||
if (serverConf != null)
|
||||
reportError("Can not specify more than one server configuration.");
|
||||
else
|
||||
serverConf = conf;
|
||||
}
|
||||
|
||||
public void startServerConf(String conf) {
|
||||
if (serverConf != null)
|
||||
reportError("Can not specify more than one server configuration.");
|
||||
else {
|
||||
startServer = true;
|
||||
serverConf = conf;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverses an array of arguments and performs the appropriate callbacks.
|
||||
*
|
||||
* @param args the arguments to traverse.
|
||||
*/
|
||||
void traverse(String[] args) {
|
||||
Iterable<String> allArgs;
|
||||
try {
|
||||
allArgs = CommandLine.parse(List.of(args)); // Detect @file and load it as a command line.
|
||||
} catch (java.io.IOException e) {
|
||||
throw new IllegalArgumentException("Problem reading @"+e.getMessage());
|
||||
}
|
||||
ArgumentIterator argIter = new ArgumentIterator(allArgs);
|
||||
|
||||
nextArg:
|
||||
while (argIter.hasNext()) {
|
||||
|
||||
String arg = argIter.next();
|
||||
|
||||
if (arg.startsWith("-")) {
|
||||
for (Option opt : Option.values()) {
|
||||
if (opt.processCurrent(argIter, this))
|
||||
continue nextArg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -23,54 +23,38 @@
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package javacserver.comp;
|
||||
package javacserver.server;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javacserver.Log;
|
||||
import javacserver.Result;
|
||||
import javacserver.server.Sjavac;
|
||||
import javacserver.util.Log;
|
||||
|
||||
/**
|
||||
* An sjavac implementation that limits the number of concurrent calls by
|
||||
* wrapping invocations in Callables and delegating them to a FixedThreadPool.
|
||||
*
|
||||
* <p><b>This is NOT part of any supported API.
|
||||
* If you write code that depends on this, you do so at your own risk.
|
||||
* This code and its internal interfaces are subject to change or
|
||||
* deletion without notice.</b>
|
||||
* Use a fixed thread pool to limit the amount of concurrent javac compilation
|
||||
* that can happen.
|
||||
*/
|
||||
public class PooledSjavac implements Sjavac {
|
||||
public class CompilerThreadPool {
|
||||
private static final int POOLSIZE = Runtime.getRuntime().availableProcessors();
|
||||
|
||||
final Sjavac delegate;
|
||||
final ExecutorService pool;
|
||||
private final ExecutorService pool;
|
||||
|
||||
public PooledSjavac(Sjavac delegate, int poolsize) {
|
||||
Objects.requireNonNull(delegate);
|
||||
this.delegate = delegate;
|
||||
pool = Executors.newFixedThreadPool(poolsize);
|
||||
public CompilerThreadPool() {
|
||||
this.pool = Executors.newFixedThreadPool(POOLSIZE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result compile(String[] args) {
|
||||
public int dispatchCompilation(String[] args) {
|
||||
Log log = Log.get();
|
||||
try {
|
||||
return pool.submit(() -> {
|
||||
Log.setLogForCurrentThread(log);
|
||||
return delegate.compile(args);
|
||||
}).get();
|
||||
return pool.submit(() -> Server.runCompiler(log, args)).get();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException("Error during compile", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
Log.debug("Shutting down PooledSjavac");
|
||||
Log.debug("Shutting down javacserver thread pool");
|
||||
pool.shutdown(); // Disable new tasks from being submitted
|
||||
try {
|
||||
// Wait a while for existing tasks to terminate
|
||||
@ -78,16 +62,17 @@ public class PooledSjavac implements Sjavac {
|
||||
pool.shutdownNow(); // Cancel currently executing tasks
|
||||
// Wait a while for tasks to respond to being cancelled
|
||||
if (!pool.awaitTermination(60, TimeUnit.SECONDS))
|
||||
Log.error("ThreadPool did not terminate");
|
||||
Log.error("Thread pool did not terminate");
|
||||
}
|
||||
} catch (InterruptedException ie) {
|
||||
// (Re-)Cancel if current thread also interrupted
|
||||
pool.shutdownNow();
|
||||
// Preserve interrupt status
|
||||
Thread.currentThread().interrupt();
|
||||
// (Re-)Cancel if current thread also interrupted
|
||||
pool.shutdownNow();
|
||||
// Preserve interrupt status
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
delegate.shutdown();
|
||||
}
|
||||
|
||||
public int poolSize() {
|
||||
return POOLSIZE;
|
||||
}
|
||||
}
|
@ -27,53 +27,30 @@ package javacserver.server;
|
||||
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import javacserver.Log;
|
||||
import javacserver.Result;
|
||||
import java.util.function.Consumer;
|
||||
import javacserver.util.RunnableTimerTask;
|
||||
|
||||
/**
|
||||
* An sjavac implementation that keeps track of idleness and shuts down the
|
||||
* given Terminable upon idleness timeout.
|
||||
*
|
||||
* An idleness timeout kicks in {@code idleTimeout} milliseconds after the last
|
||||
* request is completed.
|
||||
*
|
||||
* <p><b>This is NOT part of any supported API.
|
||||
* If you write code that depends on this, you do so at your own risk.
|
||||
* This code and its internal interfaces are subject to change or
|
||||
* deletion without notice.</b>
|
||||
* Monitors the javacserver daemon, shutting it down if it recieves no new requests
|
||||
* after a certain amount of time.
|
||||
*/
|
||||
public class IdleResetSjavac implements Sjavac {
|
||||
public class IdleMonitor {
|
||||
// Accept 120 seconds of inactivity before quitting.
|
||||
private static final int KEEPALIVE = 120;
|
||||
|
||||
private final Sjavac delegate;
|
||||
private final Terminable toShutdown;
|
||||
private final Consumer<String> onShutdown;
|
||||
private final Timer idlenessTimer = new Timer();
|
||||
private final long idleTimeout;
|
||||
private int outstandingCalls = 0;
|
||||
|
||||
// Class invariant: idlenessTimerTask != null <-> idlenessTimerTask is scheduled
|
||||
private TimerTask idlenessTimerTask;
|
||||
|
||||
public IdleResetSjavac(Sjavac delegate,
|
||||
Terminable toShutdown,
|
||||
long idleTimeout) {
|
||||
this.delegate = delegate;
|
||||
this.toShutdown = toShutdown;
|
||||
this.idleTimeout = idleTimeout;
|
||||
public IdleMonitor(Consumer<String> onShutdown) {
|
||||
this.onShutdown = onShutdown;
|
||||
scheduleTimeout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result compile(String[] args) {
|
||||
startCall();
|
||||
try {
|
||||
return delegate.compile(args);
|
||||
} finally {
|
||||
endCall();
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void startCall() {
|
||||
public synchronized void startCall() {
|
||||
// Was there no outstanding calls before this call?
|
||||
if (++outstandingCalls == 1) {
|
||||
// Then the timer task must have been scheduled
|
||||
@ -85,7 +62,7 @@ public class IdleResetSjavac implements Sjavac {
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void endCall() {
|
||||
public synchronized void endCall() {
|
||||
if (--outstandingCalls == 0) {
|
||||
// No more outstanding calls. Schedule timeout.
|
||||
scheduleTimeout();
|
||||
@ -95,19 +72,14 @@ public class IdleResetSjavac implements Sjavac {
|
||||
private void scheduleTimeout() {
|
||||
if (idlenessTimerTask != null)
|
||||
throw new IllegalStateException("Idle timeout already scheduled");
|
||||
idlenessTimerTask = new TimerTask() {
|
||||
public void run() {
|
||||
Log.setLogForCurrentThread(ServerMain.getErrorLog());
|
||||
toShutdown.shutdown("Server has been idle for " + (idleTimeout / 1000) + " seconds.");
|
||||
}
|
||||
};
|
||||
idlenessTimer.schedule(idlenessTimerTask, idleTimeout);
|
||||
idlenessTimerTask = new RunnableTimerTask(() -> {
|
||||
Server.restoreServerErrorLog();
|
||||
onShutdown.accept("Server has been idle for " + KEEPALIVE + " seconds.");
|
||||
});
|
||||
idlenessTimer.schedule(idlenessTimerTask, KEEPALIVE * 1000);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
idlenessTimer.cancel();
|
||||
delegate.shutdown();
|
||||
}
|
||||
|
||||
}
|
@ -27,68 +27,61 @@ package javacserver.server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import javacserver.Log;
|
||||
import java.util.function.Consumer;
|
||||
import javacserver.shared.PortFile;
|
||||
import javacserver.util.Log;
|
||||
import javacserver.util.RunnableTimerTask;
|
||||
|
||||
/**
|
||||
* Monitors the presence of a port file and shuts down the given SjavacServer
|
||||
* Monitors the presence of a port file and shuts down the server
|
||||
* whenever the port file is deleted or invalidated.
|
||||
*
|
||||
* TODO: JDK-8046882
|
||||
*
|
||||
* <p><b>This is NOT part of any supported API.
|
||||
* If you write code that depends on this, you do so at your own risk.
|
||||
* This code and its internal interfaces are subject to change or
|
||||
* deletion without notice.</b>
|
||||
*/
|
||||
public class PortFileMonitor {
|
||||
|
||||
// Check if the portfile is gone, every 5 seconds.
|
||||
private static final int CHECK_PORTFILE_INTERVAL = 5000;
|
||||
|
||||
private final Timer timer = new Timer();
|
||||
private final PortFile portFile;
|
||||
private final SjavacServer server;
|
||||
private final Consumer<String> onShutdown;
|
||||
|
||||
public PortFileMonitor(PortFile portFile,
|
||||
SjavacServer server) {
|
||||
Consumer<String> onShutdown) {
|
||||
this.portFile = portFile;
|
||||
this.server = server;
|
||||
this.onShutdown = onShutdown;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
Log log = Log.get();
|
||||
TimerTask shutdownCheck = new TimerTask() {
|
||||
public void run() {
|
||||
Log.setLogForCurrentThread(log);
|
||||
Log.debug("Checking port file status...");
|
||||
try {
|
||||
if (!portFile.exists()) {
|
||||
// Time to quit because the portfile was deleted by another
|
||||
// process, probably by the makefile that is done building.
|
||||
server.shutdown("Quitting because portfile was deleted!");
|
||||
} else if (portFile.markedForStop()) {
|
||||
// Time to quit because another process touched the file
|
||||
// server.port.stop to signal that the server should stop.
|
||||
// This is necessary on some operating systems that lock
|
||||
// the port file hard!
|
||||
server.shutdown("Quitting because a portfile.stop file was found!");
|
||||
} else if (!portFile.stillMyValues()) {
|
||||
// Time to quit because another build has started.
|
||||
server.shutdown("Quitting because portfile is now owned by another javac server!");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.error("IOException caught in PortFileMonitor.");
|
||||
Log.debug(e);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
Log.error(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
timer.schedule(new RunnableTimerTask(() -> checkPortFile(log)), 0, CHECK_PORTFILE_INTERVAL);
|
||||
}
|
||||
|
||||
timer.schedule(shutdownCheck, 0, CHECK_PORTFILE_INTERVAL);
|
||||
private void checkPortFile(Log log) {
|
||||
Log.setLogForCurrentThread(log);
|
||||
Log.debug("Checking port file status...");
|
||||
try {
|
||||
if (!portFile.exists()) {
|
||||
// Time to quit because the portfile was deleted by another
|
||||
// process, probably by the makefile that is done building.
|
||||
onShutdown.accept("Quitting because portfile was deleted!");
|
||||
} else if (portFile.markedForStop()) {
|
||||
// Time to quit because another process touched the file
|
||||
// server.port.stop to signal that the server should stop.
|
||||
// This is necessary on some operating systems that lock
|
||||
// the port file hard!
|
||||
onShutdown.accept("Quitting because a portfile.stop file was found!");
|
||||
} else if (!portFile.stillMyValues()) {
|
||||
// Time to quit because another build has started.
|
||||
onShutdown.accept("Quitting because portfile is now owned by another javac server!");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.error("IOException caught in PortFileMonitor.");
|
||||
Log.debug(e);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
Log.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
|
@ -1,128 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code 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 General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package javacserver.server;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.Socket;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import javacserver.Log;
|
||||
import javacserver.Result;
|
||||
import javacserver.Util;
|
||||
|
||||
import static javacserver.server.SjavacServer.LINE_TYPE_RC;
|
||||
|
||||
|
||||
/**
|
||||
* A RequestHandler handles requests performed over a socket. Specifically it
|
||||
* - Reads the command string specifying which method is to be invoked
|
||||
* - Reads the appropriate arguments
|
||||
* - Delegates the actual invocation to the given sjavac implementation
|
||||
* - Writes the result back to the socket output stream
|
||||
*
|
||||
* None of the work performed by this class is really bound by the CPU. It
|
||||
* should be completely fine to have a large number of RequestHandlers active.
|
||||
* To limit the number of concurrent compilations, use PooledSjavac.
|
||||
*
|
||||
* <p><b>This is NOT part of any supported API.
|
||||
* If you write code that depends on this, you do so at your own risk.
|
||||
* This code and its internal interfaces are subject to change or
|
||||
* deletion without notice.</b>
|
||||
*/
|
||||
public class RequestHandler extends Thread {
|
||||
|
||||
private final Socket socket;
|
||||
private final Sjavac sjavac;
|
||||
|
||||
public RequestHandler(Socket socket, Sjavac sjavac) {
|
||||
this.socket = socket;
|
||||
this.sjavac = sjavac;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
try (BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||
PrintWriter out = new PrintWriter(socket.getOutputStream(), true)) {
|
||||
|
||||
// Set up logging for this thread. Stream back logging messages to
|
||||
// client on the format format "level:msg".
|
||||
Log.setLogForCurrentThread(new Log(out, out) {
|
||||
@Override
|
||||
protected boolean isLevelLogged(Level l) {
|
||||
// Make sure it is up to the client to decide whether or
|
||||
// not this message should be displayed.
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void printLogMsg(Level msgLevel, String msg) {
|
||||
// Follow sjavac server/client protocol: Send one line
|
||||
// at a time and prefix with message with "level:".
|
||||
Util.getLines(msg)
|
||||
.map(line -> msgLevel + ":" + line)
|
||||
.forEach(line -> super.printLogMsg(msgLevel, line));
|
||||
}
|
||||
});
|
||||
|
||||
// Read argument array
|
||||
int n = Integer.parseInt(in.readLine());
|
||||
String[] args = new String[n];
|
||||
for (int i = 0; i < n; i++) {
|
||||
args[i] = in.readLine();
|
||||
}
|
||||
|
||||
// If there has been any internal errors, notify client
|
||||
checkInternalErrorLog();
|
||||
|
||||
// Perform compilation
|
||||
Result rc = sjavac.compile(args);
|
||||
|
||||
// Send return code back to client
|
||||
out.println(LINE_TYPE_RC + ":" + rc.name());
|
||||
|
||||
// Check for internal errors again.
|
||||
checkInternalErrorLog();
|
||||
} catch (Exception ex) {
|
||||
// Not much to be done at this point. The client side request
|
||||
// code will most likely throw an IOException and the
|
||||
// compilation will fail.
|
||||
Log.error(ex);
|
||||
} finally {
|
||||
Log.setLogForCurrentThread(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkInternalErrorLog() {
|
||||
Path errorLog = ServerMain.getErrorLog().getLogDestination();
|
||||
if (errorLog != null) {
|
||||
Log.error("Server has encountered an internal error. See " + errorLog.toAbsolutePath()
|
||||
+ " for details.");
|
||||
}
|
||||
}
|
||||
}
|
281
make/langtools/tools/javacserver/server/Server.java
Normal file
281
make/langtools/tools/javacserver/server/Server.java
Normal file
@ -0,0 +1,281 @@
|
||||
/*
|
||||
* Copyright (c) 2011, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code 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 General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package javacserver.server;
|
||||
|
||||
import com.sun.tools.javac.Main;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.PrintStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import javacserver.shared.PortFile;
|
||||
import javacserver.shared.Protocol;
|
||||
import javacserver.shared.Result;
|
||||
import javacserver.util.LazyInitFileLog;
|
||||
import javacserver.util.Log;
|
||||
import javacserver.util.LoggingOutputStream;
|
||||
import javacserver.util.Util;
|
||||
|
||||
/**
|
||||
* Start a new server main thread, that will listen to incoming connection requests from the client,
|
||||
* and dispatch these on to worker threads in a thread pool, running javac.
|
||||
*/
|
||||
public class Server {
|
||||
private ServerSocket serverSocket;
|
||||
private PortFile portFile;
|
||||
private PortFileMonitor portFileMonitor;
|
||||
private IdleMonitor idleMonitor;
|
||||
private CompilerThreadPool compilerThreadPool;
|
||||
|
||||
// Set to false break accept loop
|
||||
final AtomicBoolean keepAcceptingRequests = new AtomicBoolean();
|
||||
|
||||
// For logging server internal (non request specific) errors.
|
||||
private static LazyInitFileLog errorLog;
|
||||
|
||||
public static void main(String... args) {
|
||||
initLogging();
|
||||
|
||||
try {
|
||||
PortFile portFile = getPortFileFromArguments(args);
|
||||
if (portFile == null) {
|
||||
System.exit(Result.CMDERR.exitCode);
|
||||
return;
|
||||
}
|
||||
|
||||
Server server = new Server(portFile);
|
||||
if (!server.start()) {
|
||||
System.exit(Result.ERROR.exitCode);
|
||||
} else {
|
||||
System.exit(Result.OK.exitCode);
|
||||
}
|
||||
} catch (IOException | InterruptedException ex) {
|
||||
ex.printStackTrace();
|
||||
System.exit(Result.ERROR.exitCode);
|
||||
}
|
||||
}
|
||||
|
||||
private static void initLogging() {
|
||||
// Under normal operation, all logging messages generated server-side
|
||||
// are due to compilation requests. These logging messages should
|
||||
// be relayed back to the requesting client rather than written to the
|
||||
// server log. The only messages that should be written to the server
|
||||
// log (in production mode) should be errors,
|
||||
errorLog = new LazyInitFileLog("server.log");
|
||||
Log.setLogForCurrentThread(errorLog);
|
||||
Log.setLogLevel(Log.Level.ERROR); // should be set to ERROR.
|
||||
|
||||
// Make sure no exceptions go under the radar
|
||||
Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
|
||||
restoreServerErrorLog();
|
||||
Log.error(e);
|
||||
});
|
||||
|
||||
// Inevitably someone will try to print messages using System.{out,err}.
|
||||
// Make sure this output also ends up in the log.
|
||||
System.setOut(new PrintStream(new LoggingOutputStream(System.out, Log.Level.INFO, "[stdout] ")));
|
||||
System.setErr(new PrintStream(new LoggingOutputStream(System.err, Log.Level.ERROR, "[stderr] ")));
|
||||
}
|
||||
|
||||
private static PortFile getPortFileFromArguments(String[] args) {
|
||||
if (args.length != 1) {
|
||||
Log.error("javacserver daemon incorrectly called");
|
||||
return null;
|
||||
}
|
||||
String portfilename = args[0];
|
||||
PortFile portFile = new PortFile(portfilename);
|
||||
return portFile;
|
||||
}
|
||||
|
||||
public Server(PortFile portFile) throws FileNotFoundException {
|
||||
this.portFile = portFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the daemon, unless another one is already running, in which it returns
|
||||
* false and exits immediately.
|
||||
*/
|
||||
private boolean start() throws IOException, InterruptedException {
|
||||
// The port file is locked and the server port and cookie is written into it.
|
||||
portFile.lock();
|
||||
portFile.getValues();
|
||||
if (portFile.containsPortInfo()) {
|
||||
Log.debug("javacserver daemon not started because portfile exists!");
|
||||
portFile.unlock();
|
||||
return false;
|
||||
}
|
||||
|
||||
serverSocket = new ServerSocket();
|
||||
InetAddress localhost = InetAddress.getByName(null);
|
||||
serverSocket.bind(new InetSocketAddress(localhost, 0));
|
||||
|
||||
// At this point the server accepts connections, so it is now safe
|
||||
// to publish the port / cookie information
|
||||
|
||||
// The secret cookie shared between server and client through the port file.
|
||||
// Used to prevent clients from believing that they are communicating with
|
||||
// an old server when a new server has started and reused the same port as
|
||||
// an old server.
|
||||
long myCookie = new Random().nextLong();
|
||||
portFile.setValues(serverSocket.getLocalPort(), myCookie);
|
||||
portFile.unlock();
|
||||
|
||||
portFileMonitor = new PortFileMonitor(portFile, this::shutdownServer);
|
||||
portFileMonitor.start();
|
||||
compilerThreadPool = new CompilerThreadPool();
|
||||
idleMonitor = new IdleMonitor(this::shutdownServer);
|
||||
|
||||
Log.debug("javacserver daemon started. Accepting connections...");
|
||||
Log.debug(" port: " + serverSocket.getLocalPort());
|
||||
Log.debug(" time: " + new java.util.Date());
|
||||
Log.debug(" poolsize: " + compilerThreadPool.poolSize());
|
||||
|
||||
keepAcceptingRequests.set(true);
|
||||
do {
|
||||
try {
|
||||
Socket socket = serverSocket.accept();
|
||||
// Handle each incoming request in a separate thread. This is just for socket communication,
|
||||
// the actual compilation will be done by the threadpool.
|
||||
Thread requestHandler = new Thread(() -> handleRequest(socket));
|
||||
requestHandler.start();
|
||||
} catch (SocketException se) {
|
||||
// Caused by serverSocket.close() and indicates shutdown
|
||||
}
|
||||
} while (keepAcceptingRequests.get());
|
||||
|
||||
Log.debug("Shutting down.");
|
||||
|
||||
// No more connections accepted. If any client managed to connect after
|
||||
// the accept() was interrupted but before the server socket is closed
|
||||
// here, any attempt to read or write to the socket will result in an
|
||||
// IOException on the client side.
|
||||
|
||||
// Shut down
|
||||
idleMonitor.shutdown();
|
||||
compilerThreadPool.shutdown();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void handleRequest(Socket socket) {
|
||||
try (BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||
PrintWriter out = new PrintWriter(socket.getOutputStream(), true)) {
|
||||
try {
|
||||
idleMonitor.startCall();
|
||||
|
||||
// Set up logging for this thread. Stream back logging messages to
|
||||
// client on the format "level:msg".
|
||||
Log.setLogForCurrentThread(new Protocol.ProtocolLog(out));
|
||||
|
||||
String[] args = Protocol.readCommand(in);
|
||||
|
||||
// If there has been any internal errors, notify client
|
||||
checkInternalErrorLog();
|
||||
|
||||
// Perform compilation. This will call runCompiler() on a
|
||||
// thread in the thread pool
|
||||
int exitCode = compilerThreadPool.dispatchCompilation(args);
|
||||
Protocol.sendExitCode(out, exitCode);
|
||||
|
||||
// Check for internal errors again.
|
||||
checkInternalErrorLog();
|
||||
} finally {
|
||||
idleMonitor.endCall();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
// Not much to be done at this point. The client side request
|
||||
// code will most likely throw an IOException and the
|
||||
// compilation will fail.
|
||||
Log.error(ex);
|
||||
} finally {
|
||||
Log.setLogForCurrentThread(null);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecated")
|
||||
public static int runCompiler(Log log, String[] args) {
|
||||
Log.setLogForCurrentThread(log);
|
||||
|
||||
// Direct logging to our byte array stream.
|
||||
StringWriter strWriter = new StringWriter();
|
||||
PrintWriter printWriter = new PrintWriter(strWriter);
|
||||
|
||||
// Compile
|
||||
int exitcode = Main.compile(args, printWriter);
|
||||
|
||||
// Process compiler output (which is always errors)
|
||||
printWriter.flush();
|
||||
Util.getLines(strWriter.toString()).forEach(Log::error);
|
||||
|
||||
return exitcode;
|
||||
}
|
||||
|
||||
private void checkInternalErrorLog() {
|
||||
Path errorLogPath = errorLog.getLogDestination();
|
||||
if (errorLogPath != null) {
|
||||
Log.error("Server has encountered an internal error. See " + errorLogPath.toAbsolutePath()
|
||||
+ " for details.");
|
||||
}
|
||||
}
|
||||
|
||||
public static void restoreServerErrorLog() {
|
||||
Log.setLogForCurrentThread(errorLog);
|
||||
}
|
||||
|
||||
public void shutdownServer(String quitMsg) {
|
||||
if (!keepAcceptingRequests.compareAndSet(true, false)) {
|
||||
// Already stopped, no need to shut down again
|
||||
return;
|
||||
}
|
||||
|
||||
Log.debug("Quitting: " + quitMsg);
|
||||
|
||||
portFileMonitor.shutdown(); // No longer any need to monitor port file
|
||||
|
||||
// Unpublish port before shutting down socket to minimize the number of
|
||||
// failed connection attempts
|
||||
try {
|
||||
portFile.delete();
|
||||
} catch (IOException | InterruptedException e) {
|
||||
Log.error(e);
|
||||
}
|
||||
try {
|
||||
serverSocket.close();
|
||||
} catch (IOException e) {
|
||||
Log.error(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,92 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code 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 General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package javacserver.server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
|
||||
import javacserver.Log;
|
||||
import javacserver.Result;
|
||||
import javacserver.server.log.LazyInitFileLog;
|
||||
import javacserver.server.log.LoggingOutputStream;
|
||||
|
||||
import static javacserver.Log.Level.ERROR;
|
||||
import static javacserver.Log.Level.INFO;
|
||||
|
||||
/**
|
||||
* <p><b>This is NOT part of any supported API.
|
||||
* If you write code that depends on this, you do so at your own risk.
|
||||
* This code and its internal interfaces are subject to change or
|
||||
* deletion without notice.</b>
|
||||
*/
|
||||
public class ServerMain {
|
||||
|
||||
// For logging server internal (non request specific) errors.
|
||||
private static LazyInitFileLog errorLog;
|
||||
|
||||
public static int run(String[] args) {
|
||||
|
||||
// Under normal operation, all logging messages generated server-side
|
||||
// are due to compilation requests. These logging messages should
|
||||
// be relayed back to the requesting client rather than written to the
|
||||
// server log. The only messages that should be written to the server
|
||||
// log (in production mode) should be errors,
|
||||
Log.setLogForCurrentThread(errorLog = new LazyInitFileLog("server.log"));
|
||||
Log.setLogLevel(ERROR); // should be set to ERROR.
|
||||
|
||||
// Make sure no exceptions go under the radar
|
||||
Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
|
||||
Log.setLogForCurrentThread(errorLog);
|
||||
Log.error(e);
|
||||
});
|
||||
|
||||
// Inevitably someone will try to print messages using System.{out,err}.
|
||||
// Make sure this output also ends up in the log.
|
||||
System.setOut(new PrintStream(new LoggingOutputStream(System.out, INFO, "[stdout] ")));
|
||||
System.setErr(new PrintStream(new LoggingOutputStream(System.err, ERROR, "[stderr] ")));
|
||||
|
||||
// Any options other than --startserver?
|
||||
if (args.length > 1) {
|
||||
Log.error("When spawning a background server, only a single --startserver argument is allowed.");
|
||||
return Result.CMDERR.exitCode;
|
||||
}
|
||||
|
||||
int exitCode;
|
||||
try {
|
||||
SjavacServer server = new SjavacServer(args[0]);
|
||||
exitCode = server.startServer();
|
||||
} catch (IOException | InterruptedException ex) {
|
||||
ex.printStackTrace();
|
||||
exitCode = Result.ERROR.exitCode;
|
||||
}
|
||||
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
public static LazyInitFileLog getErrorLog() {
|
||||
return errorLog;
|
||||
}
|
||||
}
|
@ -1,248 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2011, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code 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 General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package javacserver.server;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import javacserver.Log;
|
||||
import javacserver.Result;
|
||||
import javacserver.Util;
|
||||
import javacserver.comp.PooledSjavac;
|
||||
import javacserver.comp.SjavacImpl;
|
||||
|
||||
/**
|
||||
* The JavacServer class contains methods both to setup a server that responds to requests and methods to connect to this server.
|
||||
*
|
||||
* <p><b>This is NOT part of any supported API.
|
||||
* If you write code that depends on this, you do so at your own risk.
|
||||
* This code and its internal interfaces are subject to change or
|
||||
* deletion without notice.</b>
|
||||
*/
|
||||
public class SjavacServer implements Terminable {
|
||||
|
||||
// Prefix of line containing return code.
|
||||
public static final String LINE_TYPE_RC = "RC";
|
||||
|
||||
private final String portfilename;
|
||||
private final int poolsize;
|
||||
private final int keepalive;
|
||||
|
||||
// The secret cookie shared between server and client through the port file.
|
||||
// Used to prevent clients from believing that they are communicating with
|
||||
// an old server when a new server has started and reused the same port as
|
||||
// an old server.
|
||||
private final long myCookie;
|
||||
|
||||
// Accumulated build time, not counting idle time, used for logging purposes
|
||||
private long totalBuildTime;
|
||||
|
||||
// The sjavac implementation to delegate requests to
|
||||
Sjavac sjavac;
|
||||
|
||||
private ServerSocket serverSocket;
|
||||
|
||||
private PortFile portFile;
|
||||
private PortFileMonitor portFileMonitor;
|
||||
|
||||
// Set to false break accept loop
|
||||
final AtomicBoolean keepAcceptingRequests = new AtomicBoolean();
|
||||
|
||||
// For the client, all port files fetched, one per started javac server.
|
||||
// Though usually only one javac server is started by a client.
|
||||
private static Map<String, PortFile> allPortFiles;
|
||||
|
||||
public SjavacServer(String settings) throws FileNotFoundException {
|
||||
this(Util.extractStringOption("portfile", settings),
|
||||
Util.extractIntOption("poolsize", settings, Runtime.getRuntime().availableProcessors()),
|
||||
Util.extractIntOption("keepalive", settings, 120));
|
||||
}
|
||||
|
||||
public SjavacServer(String portfilename,
|
||||
int poolsize,
|
||||
int keepalive)
|
||||
throws FileNotFoundException {
|
||||
this.portfilename = portfilename;
|
||||
this.poolsize = poolsize;
|
||||
this.keepalive = keepalive;
|
||||
this.myCookie = new Random().nextLong();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Acquire the port file. Synchronized since several threads inside an smart javac wrapper client acquires the same port file at the same time.
|
||||
*/
|
||||
public static synchronized PortFile getPortFile(String filename) {
|
||||
if (allPortFiles == null) {
|
||||
allPortFiles = new HashMap<>();
|
||||
}
|
||||
PortFile pf = allPortFiles.get(filename);
|
||||
|
||||
// Port file known. Does it still exist?
|
||||
if (pf != null) {
|
||||
try {
|
||||
if (!pf.exists())
|
||||
pf = null;
|
||||
} catch (IOException ioex) {
|
||||
ioex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
if (pf == null) {
|
||||
pf = new PortFile(filename);
|
||||
allPortFiles.put(filename, pf);
|
||||
}
|
||||
return pf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cookie used for this server.
|
||||
*/
|
||||
long getCookie() {
|
||||
return myCookie;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the port used for this server.
|
||||
*/
|
||||
int getPort() {
|
||||
return serverSocket.getLocalPort();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sum up the total build time for this javac server.
|
||||
*/
|
||||
public void addBuildTime(long inc) {
|
||||
totalBuildTime += inc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a server using a settings string. Typically: "--startserver:portfile=/tmp/myserver,poolsize=3" and the string "portfile=/tmp/myserver,poolsize=3"
|
||||
* is sent as the settings parameter. Returns 0 on success, -1 on failure.
|
||||
*/
|
||||
public int startServer() throws IOException, InterruptedException {
|
||||
long serverStart = System.currentTimeMillis();
|
||||
|
||||
// The port file is locked and the server port and cookie is written into it.
|
||||
portFile = getPortFile(portfilename);
|
||||
|
||||
synchronized (portFile) {
|
||||
portFile.lock();
|
||||
portFile.getValues();
|
||||
if (portFile.containsPortInfo()) {
|
||||
Log.debug("Javac server not started because portfile exists!");
|
||||
portFile.unlock();
|
||||
return Result.ERROR.exitCode;
|
||||
}
|
||||
|
||||
// .-----------. .--------. .------.
|
||||
// socket -->| IdleReset |-->| Pooled |-->| Impl |--> javac
|
||||
// '-----------' '--------' '------'
|
||||
sjavac = new SjavacImpl();
|
||||
sjavac = new PooledSjavac(sjavac, poolsize);
|
||||
sjavac = new IdleResetSjavac(sjavac,
|
||||
this,
|
||||
keepalive * 1000);
|
||||
|
||||
serverSocket = new ServerSocket();
|
||||
InetAddress localhost = InetAddress.getByName(null);
|
||||
serverSocket.bind(new InetSocketAddress(localhost, 0));
|
||||
|
||||
// At this point the server accepts connections, so it is now safe
|
||||
// to publish the port / cookie information
|
||||
portFile.setValues(getPort(), getCookie());
|
||||
portFile.unlock();
|
||||
}
|
||||
|
||||
portFileMonitor = new PortFileMonitor(portFile, this);
|
||||
portFileMonitor.start();
|
||||
|
||||
Log.debug("Sjavac server started. Accepting connections...");
|
||||
Log.debug(" port: " + getPort());
|
||||
Log.debug(" time: " + new java.util.Date());
|
||||
Log.debug(" poolsize: " + poolsize);
|
||||
|
||||
|
||||
keepAcceptingRequests.set(true);
|
||||
do {
|
||||
try {
|
||||
Socket socket = serverSocket.accept();
|
||||
new RequestHandler(socket, sjavac).start();
|
||||
} catch (SocketException se) {
|
||||
// Caused by serverSocket.close() and indicates shutdown
|
||||
}
|
||||
} while (keepAcceptingRequests.get());
|
||||
|
||||
Log.debug("Shutting down.");
|
||||
|
||||
// No more connections accepted. If any client managed to connect after
|
||||
// the accept() was interrupted but before the server socket is closed
|
||||
// here, any attempt to read or write to the socket will result in an
|
||||
// IOException on the client side.
|
||||
|
||||
long realTime = System.currentTimeMillis() - serverStart;
|
||||
Log.debug("Total wall clock time " + realTime + "ms build time " + totalBuildTime + "ms");
|
||||
|
||||
// Shut down
|
||||
sjavac.shutdown();
|
||||
|
||||
return Result.OK.exitCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown(String quitMsg) {
|
||||
if (!keepAcceptingRequests.compareAndSet(true, false)) {
|
||||
// Already stopped, no need to shut down again
|
||||
return;
|
||||
}
|
||||
|
||||
Log.debug("Quitting: " + quitMsg);
|
||||
|
||||
portFileMonitor.shutdown(); // No longer any need to monitor port file
|
||||
|
||||
// Unpublish port before shutting down socket to minimize the number of
|
||||
// failed connection attempts
|
||||
try {
|
||||
portFile.delete();
|
||||
} catch (IOException | InterruptedException e) {
|
||||
Log.error(e);
|
||||
}
|
||||
try {
|
||||
serverSocket.close();
|
||||
} catch (IOException e) {
|
||||
Log.error(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -23,7 +23,7 @@
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package javacserver.server;
|
||||
package javacserver.shared;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
@ -34,23 +34,15 @@ import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.FileLock;
|
||||
import java.nio.channels.FileLockInterruptionException;
|
||||
import java.util.concurrent.Semaphore;
|
||||
|
||||
import javacserver.Log;
|
||||
import javacserver.client.PortFileInaccessibleException;
|
||||
import javacserver.util.Log;
|
||||
|
||||
/**
|
||||
* The PortFile class mediates access to a short binary file containing the tcp/ip port (for the localhost)
|
||||
* and a cookie necessary for the server answering on that port. The file can be locked using file system
|
||||
* primitives to avoid race conditions when several javac clients are started at the same. Note that file
|
||||
* system locking is not always supported on a all operating systems and/or file systems.
|
||||
*
|
||||
* <p><b>This is NOT part of any supported API.
|
||||
* If you write code that depends on this, you do so at your own risk.
|
||||
* This code and its internal interfaces are subject to change or
|
||||
* deletion without notice.</b>
|
||||
* system locking is not always supported on all operating systems and/or file systems.
|
||||
*/
|
||||
public class PortFile {
|
||||
|
||||
// Port file format:
|
||||
// byte ordering: high byte first = big endian
|
||||
// Magic nr, 4 byte int, first in file.
|
||||
@ -225,6 +217,19 @@ public class PortFile {
|
||||
lockSem.release();
|
||||
}
|
||||
|
||||
public boolean hasValidValues() throws IOException, InterruptedException {
|
||||
if (exists()) {
|
||||
lock();
|
||||
getValues();
|
||||
unlock();
|
||||
|
||||
if (containsPortInfo()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the port file to contain values that look valid.
|
||||
*/
|
||||
@ -277,7 +282,7 @@ public class PortFile {
|
||||
continue;
|
||||
}
|
||||
catch (ClosedChannelException e) {
|
||||
// The channel has been closed since sjavac is exiting.
|
||||
// The channel has been closed since the server is exiting.
|
||||
return false;
|
||||
}
|
||||
}
|
@ -23,7 +23,7 @@
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package javacserver.client;
|
||||
package javacserver.shared;
|
||||
|
||||
import java.io.IOException;
|
||||
|
144
make/langtools/tools/javacserver/shared/Protocol.java
Normal file
144
make/langtools/tools/javacserver/shared/Protocol.java
Normal file
@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code 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 General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package javacserver.shared;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import javacserver.util.Log;
|
||||
import javacserver.util.Util;
|
||||
|
||||
/**
|
||||
* Implementation of the wire protocol used by the javacserver client and daemon to communicate.
|
||||
* Basically, the client sends the argument to javac, one line per string. The server responds
|
||||
* with log lines (if there is any output), and the exit code from javac.
|
||||
*/
|
||||
public class Protocol {
|
||||
// Prefix of line containing return code.
|
||||
private static final String LINE_TYPE_RC = "RC";
|
||||
|
||||
public static void sendCommand(PrintWriter out, String[] args) throws IOException {
|
||||
// Send args array to server
|
||||
out.println(args.length);
|
||||
for (String arg : args)
|
||||
out.println(arg);
|
||||
out.flush();
|
||||
}
|
||||
|
||||
public static String[] readCommand(BufferedReader in) throws IOException {
|
||||
// Read argument array
|
||||
int n = Integer.parseInt(in.readLine());
|
||||
String[] args = new String[n];
|
||||
for (int i = 0; i < n; i++) {
|
||||
args[i] = in.readLine();
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
public static void sendExitCode(PrintWriter out, int exitCode) {
|
||||
// Send return code back to client
|
||||
out.println(LINE_TYPE_RC + ":" + exitCode);
|
||||
}
|
||||
|
||||
public static int readResponse(BufferedReader in) throws IOException {
|
||||
// Read server response line by line
|
||||
String line;
|
||||
while (null != (line = in.readLine())) {
|
||||
Line parsedLine = new Line(line);
|
||||
|
||||
try {
|
||||
String content = parsedLine.getContent();
|
||||
if (Log.isDebugging()) {
|
||||
// Distinguish server generated output if debugging.
|
||||
content = "[javacserver] " + content;
|
||||
}
|
||||
Log.log(Log.Level.valueOf(parsedLine.getType()), content);
|
||||
continue;
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Parsing of 'type' as log level failed.
|
||||
}
|
||||
|
||||
if (parsedLine.isExitCode()) {
|
||||
return parsedLine.getExitCode();
|
||||
}
|
||||
}
|
||||
// No exit code was found.
|
||||
return Result.ERROR.exitCode;
|
||||
}
|
||||
|
||||
public static class Line {
|
||||
private final String type;
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public boolean isExitCode() {
|
||||
return type.equals(LINE_TYPE_RC);
|
||||
}
|
||||
|
||||
public int getExitCode() {
|
||||
return Integer.parseInt(content);
|
||||
}
|
||||
|
||||
private final String content;
|
||||
|
||||
public Line(String line) {
|
||||
if (!line.contains(":")) {
|
||||
throw new AssertionError("Could not parse protocol line: >>\"" + line + "\"<<");
|
||||
}
|
||||
String[] typeAndContent = line.split(":", 2);
|
||||
type = typeAndContent[0];
|
||||
content = typeAndContent[1];
|
||||
}
|
||||
}
|
||||
|
||||
public static class ProtocolLog extends Log {
|
||||
public ProtocolLog(PrintWriter out) {
|
||||
super(out, out);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isLevelLogged(Level l) {
|
||||
// Make sure it is up to the client to decide whether or
|
||||
// not this message should be displayed.
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void printLogMsg(Level msgLevel, String msg) {
|
||||
// Follow the server/client protocol: Send one line
|
||||
// at a time and prefix with message with "level:".
|
||||
Util.getLines(msg)
|
||||
.map(line -> msgLevel + ":" + line)
|
||||
.forEach(line -> super.printLogMsg(msgLevel, line));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1999, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -23,14 +23,19 @@
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package javacserver.server;
|
||||
package javacserver.shared;
|
||||
|
||||
/**
|
||||
* <p><b>This is NOT part of any supported API.
|
||||
* If you write code that depends on this, you do so at your own risk.
|
||||
* This code and its internal interfaces are subject to change or
|
||||
* deletion without notice.</b>
|
||||
* Result codes.
|
||||
*/
|
||||
public interface Terminable {
|
||||
void shutdown(String quitMsg);
|
||||
public enum Result {
|
||||
OK(0), // Compilation completed with no errors.
|
||||
ERROR(1), // Completed but reported errors.
|
||||
CMDERR(2); // Bad command-line arguments
|
||||
|
||||
public final int exitCode;
|
||||
|
||||
Result(int exitCode) {
|
||||
this.exitCode = exitCode;
|
||||
}
|
||||
}
|
@ -23,14 +23,13 @@
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package javacserver;
|
||||
package javacserver.util;
|
||||
|
||||
import java.io.FilterWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
|
||||
public class AutoFlushWriter extends FilterWriter {
|
||||
|
||||
public AutoFlushWriter(Writer out) {
|
||||
super(out);
|
||||
}
|
@ -23,7 +23,7 @@
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package javacserver.server.log;
|
||||
package javacserver.util;
|
||||
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
@ -32,10 +32,7 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import javacserver.Log;
|
||||
|
||||
public class LazyInitFileLog extends Log {
|
||||
|
||||
String baseFilename;
|
||||
Path destination = null;
|
||||
|
@ -23,17 +23,16 @@
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package javacserver;
|
||||
package javacserver.util;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.io.Writer;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Utility class only for sjavac logging.
|
||||
* Utility class only for javacserver logging.
|
||||
*
|
||||
* Logging in sjavac has special requirements when running in server/client
|
||||
* Logging in javacserver has special requirements when running in server/client
|
||||
* mode. Most of the log messages is generated server-side, but the server
|
||||
* is typically spawned by the client in the background, so the user usually
|
||||
* does not see the server stdout/stderr. For this reason log messages needs
|
||||
@ -42,16 +41,9 @@ import java.util.Locale;
|
||||
* instance so that each connected client can have its own instance that
|
||||
* relays messages back to the requesting client.
|
||||
*
|
||||
* On the client-side (or when running sjavac without server-mode) there will
|
||||
* typically just be one Log instance.
|
||||
*
|
||||
* <p><b>This is NOT part of any supported API.
|
||||
* If you write code that depends on this, you do so at your own risk.
|
||||
* This code and its internal interfaces are subject to change or
|
||||
* deletion without notice.</b>
|
||||
* On the client-side there will typically just be one Log instance.
|
||||
*/
|
||||
public class Log {
|
||||
|
||||
public enum Level {
|
||||
ERROR,
|
||||
WARN,
|
||||
@ -61,7 +53,7 @@ public class Log {
|
||||
}
|
||||
|
||||
private static Log stdOutErr = new Log(new PrintWriter(System.out), new PrintWriter(System.err));
|
||||
private static ThreadLocal<Log> loggers = new ThreadLocal<>();
|
||||
private static ThreadLocal<Log> logger = new ThreadLocal<>();
|
||||
|
||||
protected PrintWriter err; // Used for error and warning messages
|
||||
protected PrintWriter out; // Used for other messages
|
||||
@ -73,31 +65,19 @@ public class Log {
|
||||
}
|
||||
|
||||
public static void setLogForCurrentThread(Log log) {
|
||||
loggers.set(log);
|
||||
}
|
||||
|
||||
public static void setLogLevel(String l) {
|
||||
setLogLevel(Level.valueOf(l.toUpperCase(Locale.US)));
|
||||
logger.set(log);
|
||||
}
|
||||
|
||||
public static void setLogLevel(Level l) {
|
||||
get().level = l;
|
||||
}
|
||||
|
||||
public static void trace(String msg) {
|
||||
log(Level.TRACE, msg);
|
||||
}
|
||||
|
||||
public static void debug(String msg) {
|
||||
log(Level.DEBUG, msg);
|
||||
}
|
||||
|
||||
public static void info(String msg) {
|
||||
log(Level.INFO, msg);
|
||||
}
|
||||
|
||||
public static void warn(String msg) {
|
||||
log(Level.WARN, msg);
|
||||
public static void debug(Throwable t) {
|
||||
log(Level.DEBUG, t);
|
||||
}
|
||||
|
||||
public static void error(String msg) {
|
||||
@ -112,10 +92,6 @@ public class Log {
|
||||
get().printLogMsg(l, msg);
|
||||
}
|
||||
|
||||
public static void debug(Throwable t) {
|
||||
log(Level.DEBUG, t);
|
||||
}
|
||||
|
||||
public static void log(Level l, Throwable t) {
|
||||
StringWriter sw = new StringWriter();
|
||||
t.printStackTrace(new PrintWriter(sw, true));
|
||||
@ -131,7 +107,7 @@ public class Log {
|
||||
}
|
||||
|
||||
public static Log get() {
|
||||
Log log = loggers.get();
|
||||
Log log = logger.get();
|
||||
return log != null ? log : stdOutErr;
|
||||
}
|
||||
|
@ -23,17 +23,14 @@
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package javacserver.server.log;
|
||||
package javacserver.util;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FilterOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import javacserver.Log;
|
||||
|
||||
public class LoggingOutputStream extends FilterOutputStream {
|
||||
|
||||
private static final byte[] LINE_SEP = System.lineSeparator().getBytes();
|
||||
|
||||
private final Log.Level level;
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1999, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -23,34 +23,22 @@
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package javacserver;
|
||||
package javacserver.util;
|
||||
|
||||
/** Result codes.
|
||||
import java.util.TimerTask;
|
||||
|
||||
/**
|
||||
* Wrapper class since TimerTask is not up to modern standards
|
||||
*/
|
||||
public enum Result {
|
||||
OK(0), // Compilation completed with no errors.
|
||||
ERROR(1), // Completed but reported errors.
|
||||
CMDERR(2), // Bad command-line arguments
|
||||
SYSERR(3), // System error or resource exhaustion.
|
||||
ABNORMAL(4); // Compiler terminated abnormally
|
||||
public class RunnableTimerTask extends TimerTask {
|
||||
private final Runnable task;
|
||||
|
||||
Result(int exitCode) {
|
||||
this.exitCode = exitCode;
|
||||
public RunnableTimerTask(Runnable task) {
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
public static Result of(int exitcode) {
|
||||
for (Result result : values()) {
|
||||
if (result.exitCode == exitcode) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return ABNORMAL;
|
||||
@Override
|
||||
public void run() {
|
||||
task.run();
|
||||
}
|
||||
|
||||
public boolean isOK() {
|
||||
return (exitCode == 0);
|
||||
}
|
||||
|
||||
public final int exitCode;
|
||||
}
|
@ -23,20 +23,18 @@
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package javacserver.server;
|
||||
package javacserver.util;
|
||||
|
||||
import javacserver.Result;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Interface of the SjavacImpl, the sjavac client and all wrappers such as
|
||||
* PooledSjavac etc.
|
||||
*
|
||||
* <p><b>This is NOT part of any supported API.
|
||||
* If you write code that depends on this, you do so at your own risk.
|
||||
* This code and its internal interfaces are subject to change or
|
||||
* deletion without notice.</b>
|
||||
*/
|
||||
public interface Sjavac {
|
||||
Result compile(String[] args);
|
||||
void shutdown();
|
||||
public class Util {
|
||||
/**
|
||||
* Return a stream of strings, where the input string is split at line separators.
|
||||
*/
|
||||
public static Stream<String> getLines(String str) {
|
||||
return str.isEmpty()
|
||||
? Stream.empty()
|
||||
: Stream.of(str.split(Pattern.quote(System.lineSeparator())));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user