8147569: Error messages from sjavac server does not always get relayed back to client

Refactored how logging works in sjavac.

Reviewed-by: jlahoda
This commit is contained in:
Andreas Lundblad 2016-02-29 13:24:01 +01:00
parent c63980511c
commit 49850dd82f
23 changed files with 482 additions and 332 deletions

View File

@ -75,9 +75,7 @@ public class CleanProperties implements Transformer {
Map<String, PubApi> dependencyPublicApis,
int debugLevel,
boolean incremental,
int numCores,
Writer out,
Writer err) {
int numCores) {
boolean rc = true;
for (String pkgName : pkgSrcs.keySet()) {
String pkgNameF = pkgName.replace('.',File.separatorChar);

View File

@ -42,6 +42,8 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import com.sun.tools.sjavac.comp.CompilationService;
import com.sun.tools.sjavac.options.Options;
@ -89,9 +91,7 @@ public class CompileJavaPackages implements Transformer {
final Map<String, PubApi> dependencyPubapis,
int debugLevel,
boolean incremental,
int numCores,
final Writer out,
final Writer err) {
int numCores) {
Log.debug("Performing CompileJavaPackages transform...");
@ -219,7 +219,9 @@ public class CompileJavaPackages implements Transformer {
}
String chunkId = id + "-" + String.valueOf(i);
Log log = Log.get();
compilationCalls.add(() -> {
Log.setLogForCurrentThread(log);
CompilationSubResult result = sjavac.compile("n/a",
chunkId,
args.prepJavacArgs(),
@ -227,8 +229,8 @@ public class CompileJavaPackages implements Transformer {
cc.srcs,
visibleSources);
synchronized (lock) {
safeWrite(result.stdout, out);
safeWrite(result.stderr, err);
Util.getLines(result.stdout).forEach(Log::info);
Util.getLines(result.stderr).forEach(Log::error);
}
return result;
});
@ -246,8 +248,10 @@ public class CompileJavaPackages implements Transformer {
subResults.add(fut.get());
} catch (ExecutionException ee) {
Log.error("Compilation failed: " + ee.getMessage());
} catch (InterruptedException ee) {
Log.error("Compilation interrupted: " + ee.getMessage());
Log.error(ee);
} catch (InterruptedException ie) {
Log.error("Compilation interrupted: " + ie.getMessage());
Log.error(ie);
Thread.currentThread().interrupt();
}
}
@ -292,16 +296,6 @@ public class CompileJavaPackages implements Transformer {
return rc;
}
private void safeWrite(String str, Writer w) {
if (str.length() > 0) {
try {
w.write(str);
} catch (IOException e) {
Log.error("Could not print compilation output.");
}
}
}
/**
* Split up the sources into compile chunks. If old package dependents information
* is available, sort the order of the chunks into the most dependent first!

View File

@ -83,9 +83,7 @@ public class CompileProperties implements Transformer {
Map<String, PubApi> dependencyPublicApis,
int debugLevel,
boolean incremental,
int numCores,
Writer out,
Writer err) {
int numCores) {
boolean rc = true;
for (String pkgName : pkgSrcs.keySet()) {
String pkgNameF = Util.toFileSystemPath(pkgName);

View File

@ -70,9 +70,7 @@ public class CopyFile implements Transformer {
Map<String, PubApi> dependencyPubapis,
int debugLevel,
boolean incremental,
int numCores,
Writer out,
Writer err)
int numCores)
{
boolean rc = true;
String dest_filename;

View File

@ -123,16 +123,11 @@ public class JavacState {
// Setup transform that always exist.
private CompileJavaPackages compileJavaPackages = new CompileJavaPackages();
// Where to send stdout and stderr.
private Writer out, err;
// Command line options.
private Options options;
JavacState(Options op, boolean removeJavacState, Writer o, Writer e) {
JavacState(Options op, boolean removeJavacState) {
options = op;
out = o;
err = e;
numCores = options.getNumCores();
theArgs = options.getStateArgsString();
binDir = Util.pathToFile(options.getDestDir());
@ -294,8 +289,8 @@ public class JavacState {
/**
* Load a javac_state file.
*/
public static JavacState load(Options options, Writer out, Writer err) {
JavacState db = new JavacState(options, false, out, err);
public static JavacState load(Options options) {
JavacState db = new JavacState(options, false);
Module lastModule = null;
Package lastPackage = null;
Source lastSource = null;
@ -367,22 +362,22 @@ public class JavacState {
noFileFound = true;
} catch (IOException e) {
Log.info("Dropping old javac_state because of errors when reading it.");
db = new JavacState(options, true, out, err);
db = new JavacState(options, true);
foundCorrectVerNr = true;
newCommandLine = false;
syntaxError = false;
}
if (foundCorrectVerNr == false && !noFileFound) {
Log.info("Dropping old javac_state since it is of an old version.");
db = new JavacState(options, true, out, err);
db = new JavacState(options, true);
} else
if (newCommandLine == true && !noFileFound) {
Log.info("Dropping old javac_state since a new command line is used!");
db = new JavacState(options, true, out, err);
db = new JavacState(options, true);
} else
if (syntaxError == true) {
Log.info("Dropping old javac_state since it contains syntax errors.");
db = new JavacState(options, true, out, err);
db = new JavacState(options, true);
}
db.prev.calculateDependents();
return db;
@ -812,9 +807,7 @@ public class JavacState {
dependencyPublicApis,
0,
isIncremental(),
numCores,
out,
err);
numCores);
if (!r)
rc = false;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2016, 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
@ -26,11 +26,24 @@
package com.sun.tools.sjavac;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Locale;
/**
* Utility class only for sjavac logging.
* The log level can be set using for example --log=DEBUG on the sjavac command line.
*
* Logging in sjavac 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
* to relayed back to the client that performed the request that generated the
* log message. To support this use case this class maintains a per-thread log
* 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.
@ -38,61 +51,94 @@ import java.io.Writer;
* deletion without notice.</b>
*/
public class Log {
private static PrintWriter out, err;
public final static int WARN = 1;
public final static int INFO = 2;
public final static int DEBUG = 3;
public final static int TRACE = 4;
private static int level = WARN;
public enum Level {
ERROR,
WARN,
INFO,
DEBUG,
TRACE;
}
private static Log stdOutErr = new Log(new PrintWriter(System.out), new PrintWriter(System.err));
private static ThreadLocal<Log> loggers = new ThreadLocal<>();
protected PrintWriter err; // Used for error and warning messages
protected PrintWriter out; // Used for other messages
protected Level level = Level.INFO;
public Log(Writer out, Writer err) {
this.out = out == null ? null : new PrintWriter(out, true);
this.err = err == null ? null : new PrintWriter(err, true);
}
public static void setLogForCurrentThread(Log log) {
loggers.set(log);
}
public static void setLogLevel(String l) {
setLogLevel(Level.valueOf(l.toUpperCase(Locale.US)));
}
public static void setLogLevel(Level l) {
get().level = l;
}
static public void trace(String msg) {
if (level >= TRACE) {
out.println(msg);
}
log(Level.TRACE, msg);
}
static public void debug(String msg) {
if (level >= DEBUG) {
out.println(msg);
}
log(Level.DEBUG, msg);
}
static public void info(String msg) {
if (level >= INFO) {
out.println(msg);
}
log(Level.INFO, msg);
}
static public void warn(String msg) {
err.println(msg);
log(Level.WARN, msg);
}
static public void error(String msg) {
err.println(msg);
log(Level.ERROR, msg);
}
static public void initializeLog(Writer o, Writer e) {
out = new PrintWriter(o);
err = new PrintWriter(e);
static public void error(Throwable t) {
log(Level.ERROR, t);
}
static public void setLogLevel(String l) {
switch (l) {
case "warn": level = WARN; break;
case "info": level = INFO; break;
case "debug": level = DEBUG; break;
case "trace": level = TRACE; break;
default:
throw new IllegalArgumentException("No such log level \"" + l + "\"");
}
static public void log(Level l, String msg) {
get().printLogMsg(l, msg);
}
static public boolean isTracing() {
return level >= TRACE;
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));
log(l, sw.toString());
}
static public boolean isDebugging() {
return level >= DEBUG;
return get().isLevelLogged(Level.DEBUG);
}
protected boolean isLevelLogged(Level l) {
return l.ordinal() <= level.ordinal();
}
public static Log get() {
Log log = loggers.get();
return log != null ? log : stdOutErr;
}
protected void printLogMsg(Level msgLevel, String msg) {
if (isLevelLogged(msgLevel)) {
PrintWriter pw = msgLevel.ordinal() <= Level.WARN.ordinal() ? err : out;
pw.println(msg);
}
}
}

View File

@ -95,9 +95,7 @@ public interface Transformer {
Map<String, PubApi> dependencyApis,
int debugLevel,
boolean incremental,
int numCores,
Writer out,
Writer err);
int numCores);
void setExtra(String e);
void setExtra(Options args);

View File

@ -36,7 +36,9 @@ import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Utilities.
@ -236,4 +238,10 @@ public class Util {
int dotIndex = fileNameStr.indexOf('.');
return dotIndex == -1 ? "" : fileNameStr.substring(dotIndex);
}
public static Stream<String> getLines(String str) {
return str.isEmpty()
? Stream.empty()
: Stream.of(str.split(Pattern.quote(System.lineSeparator())));
}
}

View File

@ -51,7 +51,7 @@ public class ClientMain {
public static int run(String[] args, Writer out, Writer err) {
Log.initializeLog(out, err);
Log.setLogForCurrentThread(new Log(out, err));
Options options;
try {
@ -61,6 +61,8 @@ public class ClientMain {
return -1;
}
Log.setLogLevel(options.getLogLevel());
Log.debug("==========================================================");
Log.debug("Launching sjavac client with the following parameters:");
Log.debug(" " + options.getStateArgsString());
@ -81,7 +83,7 @@ public class ClientMain {
sjavac = new SjavacImpl();
}
int rc = sjavac.compile(args, out, err);
int rc = sjavac.compile(args);
// If sjavac is running in the foreground we should shut it down at this point
if (!useServer)

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2014, 2016, 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
@ -32,6 +32,7 @@ import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.Writer;
import java.net.InetAddress;
import java.net.InetSocketAddress;
@ -40,6 +41,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Scanner;
import java.util.stream.Stream;
import com.sun.tools.sjavac.Log;
import com.sun.tools.sjavac.Util;
@ -50,6 +52,8 @@ import com.sun.tools.sjavac.server.PortFile;
import com.sun.tools.sjavac.server.Sjavac;
import com.sun.tools.sjavac.server.SjavacServer;
import static java.util.stream.Collectors.joining;
/**
* Sjavac implementation that delegates requests to a SjavacServer.
*
@ -64,8 +68,6 @@ public class SjavacClient implements Sjavac {
// JavaCompiler instance for several compiles using the same id.
private final String id;
private final PortFile portFile;
private final String logfile;
private final String stdouterrfile;
// Default keepalive for server is 120 seconds.
// I.e. it will accept 120 seconds of inactivity before quitting.
@ -102,8 +104,6 @@ public class SjavacClient implements Sjavac {
Log.error("Port file inaccessable: " + e);
throw e;
}
logfile = Util.extractStringOption("logfile", serverConf, portfileName + ".javaclog");
stdouterrfile = Util.extractStringOption("stdouterrfile", serverConf, portfileName + ".stdouterr");
sjavacForkCmd = Util.extractStringOption("sjavac", serverConf, "sjavac");
int poolsize = Util.extractIntOption("poolsize", serverConf);
keepalive = Util.extractIntOption("keepalive", serverConf, 120);
@ -121,7 +121,7 @@ public class SjavacClient implements Sjavac {
}
@Override
public int compile(String[] args, Writer stdout, Writer stderr) {
public int compile(String[] args) {
int result = -1;
try (Socket socket = tryConnect()) {
PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
@ -136,32 +136,33 @@ public class SjavacClient implements Sjavac {
// 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];
switch (type) {
case SjavacServer.LINE_TYPE_STDOUT:
stdout.write(content);
stdout.write('\n');
break;
case SjavacServer.LINE_TYPE_STDERR:
stderr.write(content);
stderr.write('\n');
break;
case SjavacServer.LINE_TYPE_RC:
try {
Log.log(Log.Level.valueOf(type), "[server] " + content);
continue;
} catch (IllegalArgumentException e) {
// Parsing of 'type' as log level failed.
}
if (type.equals(SjavacServer.LINE_TYPE_RC)) {
result = Integer.parseInt(content);
break;
}
}
} catch (IOException ioe) {
Log.error("[CLIENT] Exception caught: " + ioe);
Log.error("IOException caught during compilation: " + ioe.getMessage());
Log.debug(ioe);
result = CompilationSubResult.ERROR_FATAL;
ioe.printStackTrace(new PrintWriter(stderr));
} catch (InterruptedException ie) {
Thread.currentThread().interrupt(); // Restore interrupt
Log.error("[CLIENT] compile interrupted.");
Log.error("Compilation interrupted.");
Log.debug(ie);
result = CompilationSubResult.ERROR_FATAL;
ie.printStackTrace(new PrintWriter(stderr));
}
return result;
}
@ -215,11 +216,8 @@ public class SjavacClient implements Sjavac {
// Fork a new server and wait for it to start
SjavacClient.fork(sjavacForkCmd,
portFile,
logfile,
poolsize,
keepalive,
System.err,
stdouterrfile);
keepalive);
}
@Override
@ -230,51 +228,53 @@ public class SjavacClient implements Sjavac {
/*
* Fork a server process process and wait for server to come around
*/
public static void fork(String sjavacCmd,
PortFile portFile,
String logfile,
int poolsize,
int keepalive,
final PrintStream err,
String stdouterrfile)
public static void fork(String sjavacCmd, PortFile portFile, int poolsize, int keepalive)
throws IOException, InterruptedException {
List<String> cmd = new ArrayList<>();
cmd.addAll(Arrays.asList(OptionHelper.unescapeCmdArg(sjavacCmd).split(" ")));
cmd.add("--startserver:"
+ "portfile=" + portFile.getFilename()
+ ",logfile=" + logfile
+ ",stdouterrfile=" + stdouterrfile
+ ",poolsize=" + poolsize
+ ",keepalive="+ keepalive);
Process p = null;
Process serverProcess;
Log.info("Starting server. Command: " + String.join(" ", cmd));
try {
// If the cmd for some reason can't be executed (file not found, or
// is not executable) this will throw an IOException with a decent
// error message.
p = new ProcessBuilder(cmd)
// 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)
.redirectOutput(new File(stdouterrfile))
.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) {
// Log and rethrow exception
Log.error("Faild to launch server.");
Log.error(" Message: " + ex.getMessage());
String rc = p == null || p.isAlive() ? "n/a" : "" + p.exitValue();
Log.error(" Server process exit code: " + rc);
Log.error("Server log:");
Log.error("------- Server log start -------");
try (Scanner s = new Scanner(new File(stdouterrfile))) {
while (s.hasNextLine())
Log.error(s.nextLine());
// 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("------- Server log end ---------");
throw ex;
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);
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2014, 2016, 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
@ -25,15 +25,14 @@
package com.sun.tools.sjavac.comp;
import java.io.Writer;
import com.sun.tools.sjavac.Log;
import com.sun.tools.sjavac.server.Sjavac;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import com.sun.tools.sjavac.Log;
import com.sun.tools.sjavac.server.Sjavac;
/**
* An sjavac implementation that limits the number of concurrent calls by
* wrapping invocations in Callables and delegating them to a FixedThreadPool.
@ -55,10 +54,12 @@ public class PooledSjavac implements Sjavac {
}
@Override
public int compile(String[] args, Writer out, Writer err) {
public int compile(String[] args) {
Log log = Log.get();
try {
return pool.submit(() -> {
return delegate.compile(args, out, err);
Log.setLogForCurrentThread(log);
return delegate.compile(args);
}).get();
} catch (Exception e) {
e.printStackTrace();

View File

@ -27,6 +27,7 @@ package com.sun.tools.sjavac.comp;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Path;
@ -68,7 +69,7 @@ import javax.tools.JavaFileManager;
public class SjavacImpl implements Sjavac {
@Override
public int compile(String[] args, Writer out, Writer err) {
public int compile(String[] args) {
Options options;
try {
options = Options.parseArgs(args);
@ -77,8 +78,6 @@ public class SjavacImpl implements Sjavac {
return RC_FATAL;
}
Log.setLogLevel(options.getLogLevel());
if (!validateOptions(options))
return RC_FATAL;
@ -100,18 +99,21 @@ public class SjavacImpl implements Sjavac {
if (stateDir == null) {
// Prepare context. Direct logging to our byte array stream.
Context context = new Context();
PrintWriter writer = new PrintWriter(err);
com.sun.tools.javac.util.Log.preRegister(context, writer);
StringWriter strWriter = new StringWriter();
PrintWriter printWriter = new PrintWriter(strWriter);
com.sun.tools.javac.util.Log.preRegister(context, printWriter);
JavacFileManager.preRegister(context);
// Prepare arguments
String[] passThroughArgs = Stream.of(args)
.filter(arg -> !arg.startsWith(Option.SERVER.arg))
.toArray(String[]::new);
// Compile
com.sun.tools.javac.main.Main compiler = new com.sun.tools.javac.main.Main("javac", writer);
Main.Result result = compiler.compile(passThroughArgs, context);
Main.Result result = new Main("javac", printWriter).compile(passThroughArgs, context);
// Process compiler output (which is always errors)
printWriter.flush();
Util.getLines(strWriter.toString()).forEach(Log::error);
// Clean up
JavaFileManager fileManager = context.get(JavaFileManager.class);
@ -126,7 +128,7 @@ public class SjavacImpl implements Sjavac {
} else {
// Load the prev build state database.
JavacState javac_state = JavacState.load(options, out, err);
JavacState javac_state = JavacState.load(options);
// Setup the suffix rules from the command line.
Map<String, Transformer> suffixRules = new HashMap<>();
@ -288,10 +290,12 @@ public class SjavacImpl implements Sjavac {
return rc[0] ? RC_OK : RC_FATAL;
} catch (ProblemException e) {
// For instance make file list mismatch.
Log.error(e.getMessage());
Log.debug(e);
return RC_FATAL;
} catch (Exception e) {
e.printStackTrace(new PrintWriter(err));
Log.error(e);
return RC_FATAL;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2014, 2016, 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
@ -25,6 +25,10 @@
package com.sun.tools.sjavac.server;
import com.sun.tools.sjavac.Log;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.Timer;
import java.util.TimerTask;
@ -62,10 +66,10 @@ public class IdleResetSjavac implements Sjavac {
}
@Override
public int compile(String[] args, Writer out, Writer err) {
public int compile(String[] args) {
startCall();
try {
return delegate.compile(args, out, err);
return delegate.compile(args);
} finally {
endCall();
}
@ -95,6 +99,7 @@ public class IdleResetSjavac implements Sjavac {
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.");
}
};

View File

@ -1,77 +0,0 @@
/*
* Copyright (c) 2015, 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 com.sun.tools.sjavac.server;
import java.io.FilterWriter;
import java.io.IOException;
import java.io.Writer;
/**
* Inserts {@literal prefix} in front of each line written.
*
* A line is considered to be terminated by any one of a line feed, a carriage
* return, or a carriage return followed immediately by a line feed.
*/
public class LinePrefixFilterWriter extends FilterWriter {
private final String prefix;
private boolean atBeginningOfLine = true;
private char lastChar = '\0';
protected LinePrefixFilterWriter(Writer out, String prefix) {
super(out);
this.prefix = prefix;
}
@Override
public void write(String str, int off, int len) throws IOException {
for (int i = 0; i < len; i++) {
write(str.charAt(off + i));
}
}
@Override
public void write(char[] cbuf, int off, int len) throws IOException {
for (int i = 0; i < len; i++) {
write(cbuf[off + i]);
}
}
@Override
public void write(int c) throws IOException {
if (lastChar == '\r' && c == '\n') {
// Second character of CR+LF sequence.
// Do nothing. We already started a new line on last character.
} else {
if (atBeginningOfLine) {
super.write(prefix, 0, prefix.length());
}
super.write(c);
atBeginningOfLine = c == '\r' || c == '\n';
}
lastChar = (char) c;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2014, 2016, 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
@ -25,6 +25,8 @@
package com.sun.tools.sjavac.server;
import com.sun.tools.sjavac.Log;
import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;
@ -56,8 +58,11 @@ public class PortFileMonitor {
}
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
@ -74,12 +79,11 @@ public class PortFileMonitor {
server.shutdown("Quitting because portfile is now owned by another javac server!");
}
} catch (IOException e) {
e.printStackTrace(server.theLog);
server.flushLog();
Log.error("IOException caught in PortFileMonitor.");
Log.debug(e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace(server.theLog);
server.flushLog();
Log.error(e);
}
}
};

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2014, 2016, 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
@ -25,19 +25,16 @@
package com.sun.tools.sjavac.server;
import static com.sun.tools.sjavac.server.SjavacServer.LINE_TYPE_RC;
import static com.sun.tools.sjavac.server.SjavacServer.LINE_TYPE_STDERR;
import static com.sun.tools.sjavac.server.SjavacServer.LINE_TYPE_STDOUT;
import com.sun.tools.sjavac.Log;
import com.sun.tools.sjavac.Util;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.net.Socket;
import java.nio.file.Path;
import com.sun.tools.sjavac.AutoFlushWriter;
import com.sun.tools.sjavac.Log;
import static com.sun.tools.sjavac.server.SjavacServer.LINE_TYPE_RC;
/**
@ -56,7 +53,7 @@ import com.sun.tools.sjavac.Log;
* This code and its internal interfaces are subject to change or
* deletion without notice.</b>
*/
public class RequestHandler implements Runnable {
public class RequestHandler extends Thread {
private final Socket socket;
private final Sjavac sjavac;
@ -68,9 +65,30 @@ public class RequestHandler implements Runnable {
@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];
@ -78,23 +96,32 @@ public class RequestHandler implements Runnable {
args[i] = in.readLine();
}
// If there has been any internal errors, notify client
checkInternalErrorLog();
// Perform compilation
Writer stdout = new LinePrefixFilterWriter(new AutoFlushWriter(out), LINE_TYPE_STDOUT + ":");
Writer stderr = new LinePrefixFilterWriter(new AutoFlushWriter(out), LINE_TYPE_STDERR + ":");
int rc = sjavac.compile(args, stdout, stderr);
stdout.flush();
stderr.flush();
int rc = sjavac.compile(args);
// Send return code back to client
out.println(LINE_TYPE_RC + ":" + rc);
// 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.
StringWriter sw = new StringWriter();
ex.printStackTrace(new PrintWriter(sw));
Log.error(sw.toString());
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.");
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2014, 2016, 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
@ -25,10 +25,20 @@
package com.sun.tools.sjavac.server;
import java.io.FileWriter;
import java.io.FilterOutputStream;
import java.io.FilterWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.lang.Thread.UncaughtExceptionHandler;
import com.sun.tools.sjavac.Log;
import com.sun.tools.sjavac.Log.Level;
import com.sun.tools.sjavac.server.log.LazyInitFileLog;
import com.sun.tools.sjavac.server.log.LoggingOutputStream;
import static com.sun.tools.sjavac.Log.Level.ERROR;
import static com.sun.tools.sjavac.Log.Level.INFO;
/**
* <p><b>This is NOT part of any supported API.
@ -37,20 +47,40 @@ import com.sun.tools.sjavac.Log;
* 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) {
Log.initializeLog(new OutputStreamWriter(System.out),
new OutputStreamWriter(System.err));
// 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) {
System.err.println("When spawning a background server, only a single --startserver argument is allowed.");
Log.error("When spawning a background server, only a single --startserver argument is allowed.");
return 1;
}
int exitCode;
try {
SjavacServer server = new SjavacServer(args[0], System.err);
SjavacServer server = new SjavacServer(args[0]);
exitCode = server.startServer();
} catch (IOException | InterruptedException ex) {
ex.printStackTrace();
@ -59,4 +89,8 @@ public class ServerMain {
return exitCode;
}
public static LazyInitFileLog getErrorLog() {
return errorLog;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2014, 2016, 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
@ -42,6 +42,6 @@ public interface Sjavac {
final static int RC_FATAL = -1;
final static int RC_OK = 0;
int compile(String[] args, Writer stdout, Writer stderr);
int compile(String[] args);
void shutdown();
}

View File

@ -26,6 +26,7 @@
package com.sun.tools.sjavac.server;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
@ -39,6 +40,7 @@ import java.util.Map;
import java.util.Random;
import java.util.concurrent.atomic.AtomicBoolean;
import com.sun.tools.sjavac.Log;
import com.sun.tools.sjavac.Util;
import com.sun.tools.sjavac.client.PortFileInaccessibleException;
import com.sun.tools.sjavac.comp.PooledSjavac;
@ -54,17 +56,12 @@ import com.sun.tools.sjavac.comp.SjavacImpl;
*/
public class SjavacServer implements Terminable {
// Used in protocol to tell the content of each line
// Prefix of line containing return code.
public final static String LINE_TYPE_RC = "RC";
public final static String LINE_TYPE_STDOUT = "STDOUT";
public final static String LINE_TYPE_STDERR = "STDERR";
final private String portfilename;
final private String logfile;
final private String stdouterrfile;
final private int poolsize;
final private int keepalive;
final private PrintStream err;
// The secret cookie shared between server and client through the port file.
// Used to prevent clients from believing that they are communicating with
@ -75,9 +72,6 @@ public class SjavacServer implements Terminable {
// Accumulated build time, not counting idle time, used for logging purposes
private long totalBuildTime;
// The javac server specific log file.
PrintWriter theLog;
// The sjavac implementation to delegate requests to
Sjavac sjavac;
@ -92,40 +86,29 @@ public class SjavacServer implements Terminable {
// 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;
private static Map<String, Long> maxServerMemory;
public SjavacServer(String settings, PrintStream err) throws FileNotFoundException {
public SjavacServer(String settings) throws FileNotFoundException {
this(Util.extractStringOption("portfile", settings),
Util.extractStringOption("logfile", settings),
Util.extractStringOption("stdouterrfile", settings),
Util.extractIntOption("poolsize", settings, Runtime.getRuntime().availableProcessors()),
Util.extractIntOption("keepalive", settings, 120),
err);
Util.extractIntOption("keepalive", settings, 120));
}
public SjavacServer(String portfilename,
String logfile,
String stdouterrfile,
int poolsize,
int keepalive,
PrintStream err)
int keepalive)
throws FileNotFoundException {
this.portfilename = portfilename;
this.logfile = logfile;
this.stdouterrfile = stdouterrfile;
this.poolsize = poolsize;
this.keepalive = keepalive;
this.err = err;
myCookie = new Random().nextLong();
theLog = new PrintWriter(logfile);
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) throws PortFileInaccessibleException {
public static synchronized PortFile getPortFile(String filename)
throws PortFileInaccessibleException {
if (allPortFiles == null) {
allPortFiles = new HashMap<>();
}
@ -169,26 +152,6 @@ public class SjavacServer implements Terminable {
totalBuildTime += inc;
}
/**
* Log this message.
*/
public void log(String msg) {
if (theLog != null) {
theLog.println(msg);
} else {
System.err.println(msg);
}
}
/**
* Make sure the log is flushed.
*/
public void flushLog() {
if (theLog != null) {
theLog.flush();
}
}
/**
* 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.
@ -203,7 +166,7 @@ public class SjavacServer implements Terminable {
portFile.lock();
portFile.getValues();
if (portFile.containsPortInfo()) {
err.println("Javac server not started because portfile exists!");
Log.info("Javac server not started because portfile exists!");
portFile.unlock();
return -1;
}
@ -230,23 +193,23 @@ public class SjavacServer implements Terminable {
portFileMonitor = new PortFileMonitor(portFile, this);
portFileMonitor.start();
log("Sjavac server started. Accepting connections...");
log(" port: " + getPort());
log(" time: " + new java.util.Date());
log(" poolsize: " + poolsize);
flushLog();
Log.info("Sjavac server started. Accepting connections...");
Log.info(" port: " + getPort());
Log.info(" time: " + new java.util.Date());
Log.info(" poolsize: " + poolsize);
keepAcceptingRequests.set(true);
do {
try {
Socket socket = serverSocket.accept();
new Thread(new RequestHandler(socket, sjavac)).start();
new RequestHandler(socket, sjavac).start();
} catch (SocketException se) {
// Caused by serverSocket.close() and indicates shutdown
}
} while (keepAcceptingRequests.get());
log("Shutting down.");
Log.info("Shutting down.");
// No more connections accepted. If any client managed to connect after
// the accept() was interrupted but before the server socket is closed
@ -254,8 +217,7 @@ public class SjavacServer implements Terminable {
// IOException on the client side.
long realTime = System.currentTimeMillis() - serverStart;
log("Total wall clock time " + realTime + "ms build time " + totalBuildTime + "ms");
flushLog();
Log.info("Total wall clock time " + realTime + "ms build time " + totalBuildTime + "ms");
// Shut down
sjavac.shutdown();
@ -270,8 +232,7 @@ public class SjavacServer implements Terminable {
return;
}
log("Quitting: " + quitMsg);
flushLog();
Log.info("Quitting: " + quitMsg);
portFileMonitor.shutdown(); // No longer any need to monitor port file
@ -280,12 +241,12 @@ public class SjavacServer implements Terminable {
try {
portFile.delete();
} catch (IOException | InterruptedException e) {
e.printStackTrace(theLog);
Log.error(e);
}
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace(theLog);
Log.error(e);
}
}
}

View File

@ -0,0 +1,80 @@
/*
* Copyright (c) 2016, 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 com.sun.tools.sjavac.server.log;
import com.sun.tools.sjavac.Log;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class LazyInitFileLog extends Log {
String baseFilename;
Path destination = null;
public LazyInitFileLog(String baseFilename) {
super(null, null);
this.baseFilename = baseFilename;
}
protected void printLogMsg(Level msgLevel, String msg) {
try {
// Lazily initialize out/err
if (out == null && isLevelLogged(msgLevel)) {
destination = getAvailableDestination();
out = err = new PrintWriter(new FileWriter(destination.toFile()), true);
}
// Proceed to log the message
super.printLogMsg(msgLevel, msg);
} catch (IOException e) {
// This could be bad. We might have run into an error and we can't
// log it. Resort to printing on stdout.
System.out.println("IO error occurred: " + e.getMessage());
System.out.println("Original message: [" + msgLevel + "] " + msg);
}
}
/**
* @return The first available path of baseFilename, baseFilename.1,
* basefilename.2, ...
*/
private Path getAvailableDestination() {
Path p = Paths.get(baseFilename);
int i = 1;
while (Files.exists(p)) {
p = Paths.get(baseFilename + "." + i++);
}
return p;
}
public Path getLogDestination() {
return destination;
}
}

View File

@ -0,0 +1,74 @@
/*
* Copyright (c) 2016, 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 com.sun.tools.sjavac.server.log;
import com.sun.tools.sjavac.Log;
import java.io.ByteArrayOutputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class LoggingOutputStream extends FilterOutputStream {
private static final byte[] LINE_SEP = System.lineSeparator().getBytes();
private final Log.Level level;
private final String linePrefix;
private EolTrackingByteArrayOutputStream buf = new EolTrackingByteArrayOutputStream();
public LoggingOutputStream(OutputStream out, Log.Level level, String linePrefix) {
super(out);
this.level = level;
this.linePrefix = linePrefix;
}
@Override
public void write(int b) throws IOException {
super.write(b);
buf.write(b);
if (buf.isLineComplete()) {
String line = new String(buf.toByteArray(), 0, buf.size() - LINE_SEP.length);
Log.log(level, linePrefix + line);
buf = new EolTrackingByteArrayOutputStream();
}
}
private static class EolTrackingByteArrayOutputStream extends ByteArrayOutputStream {
private static final byte[] EOL = System.lineSeparator().getBytes();
private boolean isLineComplete() {
if (count < EOL.length) {
return false;
}
for (int i = 0; i < EOL.length; i++) {
if (buf[count - EOL.length + i] != EOL[i]) {
return false;
}
}
return true;
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2014, 2016, 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
@ -65,11 +65,11 @@ public class IdleShutdown {
// Use Sjavac object and wait less than TIMEOUT_MS in between calls
Thread.sleep(TIMEOUT_MS - 1000);
log("Compiling");
service.compile(new String[0], null, null);
service.compile(new String[0]);
Thread.sleep(TIMEOUT_MS - 1000);
log("Compiling");
service.compile(new String[0], null, null);
service.compile(new String[0]);
if (timeoutTimestamp.get() != -1)
throw new AssertionError("Premature timeout detected.");
@ -103,7 +103,7 @@ public class IdleShutdown {
public void shutdown() {
}
@Override
public int compile(String[] args, Writer out, Writer err) {
public int compile(String[] args) {
// Attempt to trigger idle timeout during a call by sleeping
try {
Thread.sleep(TIMEOUT_MS + 1000);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2014, 2016, 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
@ -30,10 +30,12 @@
* @build Wrapper
* @run main Wrapper PooledExecution
*/
import java.io.PrintWriter;
import java.io.Writer;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import com.sun.tools.sjavac.Log;
import com.sun.tools.sjavac.comp.PooledSjavac;
import com.sun.tools.sjavac.server.Sjavac;
@ -67,7 +69,7 @@ public class PooledExecution {
for (int i = 0; i < NUM_REQUESTS; i++) {
tasks[i] = new Thread() {
public void run() {
service.compile(new String[0], null, null);
service.compile(new String[0]);
tasksFinished.incrementAndGet();
}
};
@ -109,7 +111,7 @@ public class PooledExecution {
AtomicInteger activeRequests = new AtomicInteger(0);
@Override
public int compile(String[] args, Writer out, Writer err) {
public int compile(String[] args) {
leftToStart.countDown();
int numActiveRequests = activeRequests.incrementAndGet();
System.out.printf("Left to start: %2d / Currently active: %2d%n",