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, Map<String, PubApi> dependencyPublicApis,
int debugLevel, int debugLevel,
boolean incremental, boolean incremental,
int numCores, int numCores) {
Writer out,
Writer err) {
boolean rc = true; boolean rc = true;
for (String pkgName : pkgSrcs.keySet()) { for (String pkgName : pkgSrcs.keySet()) {
String pkgNameF = pkgName.replace('.',File.separatorChar); 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.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.Future; 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.comp.CompilationService;
import com.sun.tools.sjavac.options.Options; import com.sun.tools.sjavac.options.Options;
@ -89,9 +91,7 @@ public class CompileJavaPackages implements Transformer {
final Map<String, PubApi> dependencyPubapis, final Map<String, PubApi> dependencyPubapis,
int debugLevel, int debugLevel,
boolean incremental, boolean incremental,
int numCores, int numCores) {
final Writer out,
final Writer err) {
Log.debug("Performing CompileJavaPackages transform..."); Log.debug("Performing CompileJavaPackages transform...");
@ -219,7 +219,9 @@ public class CompileJavaPackages implements Transformer {
} }
String chunkId = id + "-" + String.valueOf(i); String chunkId = id + "-" + String.valueOf(i);
Log log = Log.get();
compilationCalls.add(() -> { compilationCalls.add(() -> {
Log.setLogForCurrentThread(log);
CompilationSubResult result = sjavac.compile("n/a", CompilationSubResult result = sjavac.compile("n/a",
chunkId, chunkId,
args.prepJavacArgs(), args.prepJavacArgs(),
@ -227,8 +229,8 @@ public class CompileJavaPackages implements Transformer {
cc.srcs, cc.srcs,
visibleSources); visibleSources);
synchronized (lock) { synchronized (lock) {
safeWrite(result.stdout, out); Util.getLines(result.stdout).forEach(Log::info);
safeWrite(result.stderr, err); Util.getLines(result.stderr).forEach(Log::error);
} }
return result; return result;
}); });
@ -246,8 +248,10 @@ public class CompileJavaPackages implements Transformer {
subResults.add(fut.get()); subResults.add(fut.get());
} catch (ExecutionException ee) { } catch (ExecutionException ee) {
Log.error("Compilation failed: " + ee.getMessage()); Log.error("Compilation failed: " + ee.getMessage());
} catch (InterruptedException ee) { Log.error(ee);
Log.error("Compilation interrupted: " + ee.getMessage()); } catch (InterruptedException ie) {
Log.error("Compilation interrupted: " + ie.getMessage());
Log.error(ie);
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
} }
} }
@ -292,16 +296,6 @@ public class CompileJavaPackages implements Transformer {
return rc; 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 * 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! * 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, Map<String, PubApi> dependencyPublicApis,
int debugLevel, int debugLevel,
boolean incremental, boolean incremental,
int numCores, int numCores) {
Writer out,
Writer err) {
boolean rc = true; boolean rc = true;
for (String pkgName : pkgSrcs.keySet()) { for (String pkgName : pkgSrcs.keySet()) {
String pkgNameF = Util.toFileSystemPath(pkgName); String pkgNameF = Util.toFileSystemPath(pkgName);

View file

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

View file

@ -123,16 +123,11 @@ public class JavacState {
// Setup transform that always exist. // Setup transform that always exist.
private CompileJavaPackages compileJavaPackages = new CompileJavaPackages(); private CompileJavaPackages compileJavaPackages = new CompileJavaPackages();
// Where to send stdout and stderr.
private Writer out, err;
// Command line options. // Command line options.
private Options options; private Options options;
JavacState(Options op, boolean removeJavacState, Writer o, Writer e) { JavacState(Options op, boolean removeJavacState) {
options = op; options = op;
out = o;
err = e;
numCores = options.getNumCores(); numCores = options.getNumCores();
theArgs = options.getStateArgsString(); theArgs = options.getStateArgsString();
binDir = Util.pathToFile(options.getDestDir()); binDir = Util.pathToFile(options.getDestDir());
@ -294,8 +289,8 @@ public class JavacState {
/** /**
* Load a javac_state file. * Load a javac_state file.
*/ */
public static JavacState load(Options options, Writer out, Writer err) { public static JavacState load(Options options) {
JavacState db = new JavacState(options, false, out, err); JavacState db = new JavacState(options, false);
Module lastModule = null; Module lastModule = null;
Package lastPackage = null; Package lastPackage = null;
Source lastSource = null; Source lastSource = null;
@ -367,22 +362,22 @@ public class JavacState {
noFileFound = true; noFileFound = true;
} catch (IOException e) { } catch (IOException e) {
Log.info("Dropping old javac_state because of errors when reading it."); 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; foundCorrectVerNr = true;
newCommandLine = false; newCommandLine = false;
syntaxError = false; syntaxError = false;
} }
if (foundCorrectVerNr == false && !noFileFound) { if (foundCorrectVerNr == false && !noFileFound) {
Log.info("Dropping old javac_state since it is of an old version."); 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 } else
if (newCommandLine == true && !noFileFound) { if (newCommandLine == true && !noFileFound) {
Log.info("Dropping old javac_state since a new command line is used!"); 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 } else
if (syntaxError == true) { if (syntaxError == true) {
Log.info("Dropping old javac_state since it contains syntax errors."); 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(); db.prev.calculateDependents();
return db; return db;
@ -812,9 +807,7 @@ public class JavacState {
dependencyPublicApis, dependencyPublicApis,
0, 0,
isIncremental(), isIncremental(),
numCores, numCores);
out,
err);
if (!r) if (!r)
rc = false; 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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -26,11 +26,24 @@
package com.sun.tools.sjavac; package com.sun.tools.sjavac;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer; import java.io.Writer;
import java.util.Locale;
/** /**
* Utility class only for sjavac logging. * 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. * <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. * 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> * deletion without notice.</b>
*/ */
public class Log { public class Log {
private static PrintWriter out, err;
public final static int WARN = 1; public enum Level {
public final static int INFO = 2; ERROR,
public final static int DEBUG = 3; WARN,
public final static int TRACE = 4; INFO,
private static int level = WARN; 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) { static public void trace(String msg) {
if (level >= TRACE) { log(Level.TRACE, msg);
out.println(msg);
}
} }
static public void debug(String msg) { static public void debug(String msg) {
if (level >= DEBUG) { log(Level.DEBUG, msg);
out.println(msg);
}
} }
static public void info(String msg) { static public void info(String msg) {
if (level >= INFO) { log(Level.INFO, msg);
out.println(msg);
}
} }
static public void warn(String msg) { static public void warn(String msg) {
err.println(msg); log(Level.WARN, msg);
} }
static public void error(String msg) { static public void error(String msg) {
err.println(msg); log(Level.ERROR, msg);
} }
static public void initializeLog(Writer o, Writer e) { static public void error(Throwable t) {
out = new PrintWriter(o); log(Level.ERROR, t);
err = new PrintWriter(e);
} }
static public void setLogLevel(String l) { static public void log(Level l, String msg) {
switch (l) { get().printLogMsg(l, msg);
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 boolean isTracing() { public static void debug(Throwable t) {
return level >= TRACE; 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() { 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, Map<String, PubApi> dependencyApis,
int debugLevel, int debugLevel,
boolean incremental, boolean incremental,
int numCores, int numCores);
Writer out,
Writer err);
void setExtra(String e); void setExtra(String e);
void setExtra(Options args); void setExtra(Options args);

View file

@ -36,7 +36,9 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.function.Function; import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
/** /**
* Utilities. * Utilities.
@ -236,4 +238,10 @@ public class Util {
int dotIndex = fileNameStr.indexOf('.'); int dotIndex = fileNameStr.indexOf('.');
return dotIndex == -1 ? "" : fileNameStr.substring(dotIndex); 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) { public static int run(String[] args, Writer out, Writer err) {
Log.initializeLog(out, err); Log.setLogForCurrentThread(new Log(out, err));
Options options; Options options;
try { try {
@ -61,6 +61,8 @@ public class ClientMain {
return -1; return -1;
} }
Log.setLogLevel(options.getLogLevel());
Log.debug("=========================================================="); Log.debug("==========================================================");
Log.debug("Launching sjavac client with the following parameters:"); Log.debug("Launching sjavac client with the following parameters:");
Log.debug(" " + options.getStateArgsString()); Log.debug(" " + options.getStateArgsString());
@ -81,7 +83,7 @@ public class ClientMain {
sjavac = new SjavacImpl(); 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 sjavac is running in the foreground we should shut it down at this point
if (!useServer) 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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * 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.OutputStreamWriter;
import java.io.PrintStream; import java.io.PrintStream;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.Reader;
import java.io.Writer; import java.io.Writer;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
@ -40,6 +41,7 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Scanner; import java.util.Scanner;
import java.util.stream.Stream;
import com.sun.tools.sjavac.Log; import com.sun.tools.sjavac.Log;
import com.sun.tools.sjavac.Util; 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.Sjavac;
import com.sun.tools.sjavac.server.SjavacServer; import com.sun.tools.sjavac.server.SjavacServer;
import static java.util.stream.Collectors.joining;
/** /**
* Sjavac implementation that delegates requests to a SjavacServer. * 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. // JavaCompiler instance for several compiles using the same id.
private final String id; private final String id;
private final PortFile portFile; private final PortFile portFile;
private final String logfile;
private final String stdouterrfile;
// Default keepalive for server is 120 seconds. // Default keepalive for server is 120 seconds.
// I.e. it will accept 120 seconds of inactivity before quitting. // 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); Log.error("Port file inaccessable: " + e);
throw e; throw e;
} }
logfile = Util.extractStringOption("logfile", serverConf, portfileName + ".javaclog");
stdouterrfile = Util.extractStringOption("stdouterrfile", serverConf, portfileName + ".stdouterr");
sjavacForkCmd = Util.extractStringOption("sjavac", serverConf, "sjavac"); sjavacForkCmd = Util.extractStringOption("sjavac", serverConf, "sjavac");
int poolsize = Util.extractIntOption("poolsize", serverConf); int poolsize = Util.extractIntOption("poolsize", serverConf);
keepalive = Util.extractIntOption("keepalive", serverConf, 120); keepalive = Util.extractIntOption("keepalive", serverConf, 120);
@ -121,7 +121,7 @@ public class SjavacClient implements Sjavac {
} }
@Override @Override
public int compile(String[] args, Writer stdout, Writer stderr) { public int compile(String[] args) {
int result = -1; int result = -1;
try (Socket socket = tryConnect()) { try (Socket socket = tryConnect()) {
PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream())); PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
@ -136,32 +136,33 @@ public class SjavacClient implements Sjavac {
// Read server response line by line // Read server response line by line
String line; String line;
while (null != (line = in.readLine())) { while (null != (line = in.readLine())) {
if (!line.contains(":")) {
throw new AssertionError("Could not parse protocol line: >>\"" + line + "\"<<");
}
String[] typeAndContent = line.split(":", 2); String[] typeAndContent = line.split(":", 2);
String type = typeAndContent[0]; String type = typeAndContent[0];
String content = typeAndContent[1]; String content = typeAndContent[1];
switch (type) {
case SjavacServer.LINE_TYPE_STDOUT: try {
stdout.write(content); Log.log(Log.Level.valueOf(type), "[server] " + content);
stdout.write('\n'); continue;
break; } catch (IllegalArgumentException e) {
case SjavacServer.LINE_TYPE_STDERR: // Parsing of 'type' as log level failed.
stderr.write(content); }
stderr.write('\n');
break; if (type.equals(SjavacServer.LINE_TYPE_RC)) {
case SjavacServer.LINE_TYPE_RC:
result = Integer.parseInt(content); result = Integer.parseInt(content);
break;
} }
} }
} catch (IOException ioe) { } catch (IOException ioe) {
Log.error("[CLIENT] Exception caught: " + ioe); Log.error("IOException caught during compilation: " + ioe.getMessage());
Log.debug(ioe);
result = CompilationSubResult.ERROR_FATAL; result = CompilationSubResult.ERROR_FATAL;
ioe.printStackTrace(new PrintWriter(stderr));
} catch (InterruptedException ie) { } catch (InterruptedException ie) {
Thread.currentThread().interrupt(); // Restore interrupt Thread.currentThread().interrupt(); // Restore interrupt
Log.error("[CLIENT] compile interrupted."); Log.error("Compilation interrupted.");
Log.debug(ie);
result = CompilationSubResult.ERROR_FATAL; result = CompilationSubResult.ERROR_FATAL;
ie.printStackTrace(new PrintWriter(stderr));
} }
return result; return result;
} }
@ -215,11 +216,8 @@ public class SjavacClient implements Sjavac {
// Fork a new server and wait for it to start // Fork a new server and wait for it to start
SjavacClient.fork(sjavacForkCmd, SjavacClient.fork(sjavacForkCmd,
portFile, portFile,
logfile,
poolsize, poolsize,
keepalive, keepalive);
System.err,
stdouterrfile);
} }
@Override @Override
@ -230,51 +228,53 @@ public class SjavacClient implements Sjavac {
/* /*
* Fork a server process process and wait for server to come around * Fork a server process process and wait for server to come around
*/ */
public static void fork(String sjavacCmd, public static void fork(String sjavacCmd, PortFile portFile, int poolsize, int keepalive)
PortFile portFile,
String logfile,
int poolsize,
int keepalive,
final PrintStream err,
String stdouterrfile)
throws IOException, InterruptedException { throws IOException, InterruptedException {
List<String> cmd = new ArrayList<>(); List<String> cmd = new ArrayList<>();
cmd.addAll(Arrays.asList(OptionHelper.unescapeCmdArg(sjavacCmd).split(" "))); cmd.addAll(Arrays.asList(OptionHelper.unescapeCmdArg(sjavacCmd).split(" ")));
cmd.add("--startserver:" cmd.add("--startserver:"
+ "portfile=" + portFile.getFilename() + "portfile=" + portFile.getFilename()
+ ",logfile=" + logfile
+ ",stdouterrfile=" + stdouterrfile
+ ",poolsize=" + poolsize + ",poolsize=" + poolsize
+ ",keepalive="+ keepalive); + ",keepalive="+ keepalive);
Process p = null; Process serverProcess;
Log.info("Starting server. Command: " + String.join(" ", cmd)); Log.info("Starting server. Command: " + String.join(" ", cmd));
try { try {
// If the cmd for some reason can't be executed (file not found, or // If the cmd for some reason can't be executed (file is not found,
// is not executable) this will throw an IOException with a decent // or is not executable for instance) this will throw an
// error message. // IOException and p == null.
p = new ProcessBuilder(cmd) serverProcess = new ProcessBuilder(cmd)
.redirectErrorStream(true) .redirectErrorStream(true)
.redirectOutput(new File(stdouterrfile))
.start(); .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 // Throws an IOException if no valid values materialize
portFile.waitForValidValues(); portFile.waitForValidValues();
} catch (IOException ex) { } catch (IOException ex) {
// Log and rethrow exception // Process was started, but server failed to initialize. This could
Log.error("Faild to launch server."); // for instance be due to the JVM not finding the server class,
Log.error(" Message: " + ex.getMessage()); // or the server running in to some exception early on.
String rc = p == null || p.isAlive() ? "n/a" : "" + p.exitValue(); Log.error("Sjavac server failed to initialize: " + ex.getMessage());
Log.error(" Server process exit code: " + rc); Log.error("Process output:");
Log.error("Server log:"); Reader serverStdoutStderr = new InputStreamReader(serverProcess.getInputStream());
Log.error("------- Server log start -------"); try (BufferedReader br = new BufferedReader(serverStdoutStderr)) {
try (Scanner s = new Scanner(new File(stdouterrfile))) { br.lines().forEach(Log::error);
while (s.hasNextLine())
Log.error(s.nextLine());
} }
Log.error("------- Server log end ---------"); Log.error("<End of process output>");
throw ex; 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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -25,15 +25,14 @@
package com.sun.tools.sjavac.comp; 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.Objects;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; 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 * An sjavac implementation that limits the number of concurrent calls by
* wrapping invocations in Callables and delegating them to a FixedThreadPool. * wrapping invocations in Callables and delegating them to a FixedThreadPool.
@ -55,10 +54,12 @@ public class PooledSjavac implements Sjavac {
} }
@Override @Override
public int compile(String[] args, Writer out, Writer err) { public int compile(String[] args) {
Log log = Log.get();
try { try {
return pool.submit(() -> { return pool.submit(() -> {
return delegate.compile(args, out, err); Log.setLogForCurrentThread(log);
return delegate.compile(args);
}).get(); }).get();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();

View file

@ -27,6 +27,7 @@ package com.sun.tools.sjavac.comp;
import java.io.IOException; import java.io.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer; import java.io.Writer;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
@ -68,7 +69,7 @@ import javax.tools.JavaFileManager;
public class SjavacImpl implements Sjavac { public class SjavacImpl implements Sjavac {
@Override @Override
public int compile(String[] args, Writer out, Writer err) { public int compile(String[] args) {
Options options; Options options;
try { try {
options = Options.parseArgs(args); options = Options.parseArgs(args);
@ -77,8 +78,6 @@ public class SjavacImpl implements Sjavac {
return RC_FATAL; return RC_FATAL;
} }
Log.setLogLevel(options.getLogLevel());
if (!validateOptions(options)) if (!validateOptions(options))
return RC_FATAL; return RC_FATAL;
@ -100,18 +99,21 @@ public class SjavacImpl implements Sjavac {
if (stateDir == null) { if (stateDir == null) {
// Prepare context. Direct logging to our byte array stream. // Prepare context. Direct logging to our byte array stream.
Context context = new Context(); Context context = new Context();
PrintWriter writer = new PrintWriter(err); StringWriter strWriter = new StringWriter();
com.sun.tools.javac.util.Log.preRegister(context, writer); PrintWriter printWriter = new PrintWriter(strWriter);
com.sun.tools.javac.util.Log.preRegister(context, printWriter);
JavacFileManager.preRegister(context); JavacFileManager.preRegister(context);
// Prepare arguments // Prepare arguments
String[] passThroughArgs = Stream.of(args) String[] passThroughArgs = Stream.of(args)
.filter(arg -> !arg.startsWith(Option.SERVER.arg)) .filter(arg -> !arg.startsWith(Option.SERVER.arg))
.toArray(String[]::new); .toArray(String[]::new);
// Compile // Compile
com.sun.tools.javac.main.Main compiler = new com.sun.tools.javac.main.Main("javac", writer); Main.Result result = new Main("javac", printWriter).compile(passThroughArgs, context);
Main.Result result = compiler.compile(passThroughArgs, context);
// Process compiler output (which is always errors)
printWriter.flush();
Util.getLines(strWriter.toString()).forEach(Log::error);
// Clean up // Clean up
JavaFileManager fileManager = context.get(JavaFileManager.class); JavaFileManager fileManager = context.get(JavaFileManager.class);
@ -126,7 +128,7 @@ public class SjavacImpl implements Sjavac {
} else { } else {
// Load the prev build state database. // 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. // Setup the suffix rules from the command line.
Map<String, Transformer> suffixRules = new HashMap<>(); Map<String, Transformer> suffixRules = new HashMap<>();
@ -288,10 +290,12 @@ public class SjavacImpl implements Sjavac {
return rc[0] ? RC_OK : RC_FATAL; return rc[0] ? RC_OK : RC_FATAL;
} catch (ProblemException e) { } catch (ProblemException e) {
// For instance make file list mismatch.
Log.error(e.getMessage()); Log.error(e.getMessage());
Log.debug(e);
return RC_FATAL; return RC_FATAL;
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(new PrintWriter(err)); Log.error(e);
return RC_FATAL; 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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -25,6 +25,10 @@
package com.sun.tools.sjavac.server; 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.io.Writer;
import java.util.Timer; import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
@ -62,10 +66,10 @@ public class IdleResetSjavac implements Sjavac {
} }
@Override @Override
public int compile(String[] args, Writer out, Writer err) { public int compile(String[] args) {
startCall(); startCall();
try { try {
return delegate.compile(args, out, err); return delegate.compile(args);
} finally { } finally {
endCall(); endCall();
} }
@ -95,6 +99,7 @@ public class IdleResetSjavac implements Sjavac {
throw new IllegalStateException("Idle timeout already scheduled"); throw new IllegalStateException("Idle timeout already scheduled");
idlenessTimerTask = new TimerTask() { idlenessTimerTask = new TimerTask() {
public void run() { public void run() {
Log.setLogForCurrentThread(ServerMain.getErrorLog());
toShutdown.shutdown("Server has been idle for " + (idleTimeout / 1000) + " seconds."); 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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -25,6 +25,8 @@
package com.sun.tools.sjavac.server; package com.sun.tools.sjavac.server;
import com.sun.tools.sjavac.Log;
import java.io.IOException; import java.io.IOException;
import java.util.Timer; import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
@ -56,8 +58,11 @@ public class PortFileMonitor {
} }
public void start() { public void start() {
Log log = Log.get();
TimerTask shutdownCheck = new TimerTask() { TimerTask shutdownCheck = new TimerTask() {
public void run() { public void run() {
Log.setLogForCurrentThread(log);
Log.debug("Checking port file status...");
try { try {
if (!portFile.exists()) { if (!portFile.exists()) {
// Time to quit because the portfile was deleted by another // 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!"); server.shutdown("Quitting because portfile is now owned by another javac server!");
} }
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(server.theLog); Log.error("IOException caught in PortFileMonitor.");
server.flushLog(); Log.debug(e);
} catch (InterruptedException e) { } catch (InterruptedException e) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
e.printStackTrace(server.theLog); Log.error(e);
server.flushLog();
} }
} }
}; };

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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -25,19 +25,16 @@
package com.sun.tools.sjavac.server; package com.sun.tools.sjavac.server;
import static com.sun.tools.sjavac.server.SjavacServer.LINE_TYPE_RC; import com.sun.tools.sjavac.Log;
import static com.sun.tools.sjavac.server.SjavacServer.LINE_TYPE_STDERR; import com.sun.tools.sjavac.Util;
import static com.sun.tools.sjavac.server.SjavacServer.LINE_TYPE_STDOUT;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.net.Socket; import java.net.Socket;
import java.nio.file.Path;
import com.sun.tools.sjavac.AutoFlushWriter; import static com.sun.tools.sjavac.server.SjavacServer.LINE_TYPE_RC;
import com.sun.tools.sjavac.Log;
/** /**
@ -56,7 +53,7 @@ import com.sun.tools.sjavac.Log;
* This code and its internal interfaces are subject to change or * This code and its internal interfaces are subject to change or
* deletion without notice.</b> * deletion without notice.</b>
*/ */
public class RequestHandler implements Runnable { public class RequestHandler extends Thread {
private final Socket socket; private final Socket socket;
private final Sjavac sjavac; private final Sjavac sjavac;
@ -68,9 +65,30 @@ public class RequestHandler implements Runnable {
@Override @Override
public void run() { public void run() {
try (BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); try (BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(socket.getOutputStream(), true)) { 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 // Read argument array
int n = Integer.parseInt(in.readLine()); int n = Integer.parseInt(in.readLine());
String[] args = new String[n]; String[] args = new String[n];
@ -78,23 +96,32 @@ public class RequestHandler implements Runnable {
args[i] = in.readLine(); args[i] = in.readLine();
} }
// If there has been any internal errors, notify client
checkInternalErrorLog();
// Perform compilation // Perform compilation
Writer stdout = new LinePrefixFilterWriter(new AutoFlushWriter(out), LINE_TYPE_STDOUT + ":"); int rc = sjavac.compile(args);
Writer stderr = new LinePrefixFilterWriter(new AutoFlushWriter(out), LINE_TYPE_STDERR + ":");
int rc = sjavac.compile(args, stdout, stderr);
stdout.flush();
stderr.flush();
// Send return code back to client // Send return code back to client
out.println(LINE_TYPE_RC + ":" + rc); out.println(LINE_TYPE_RC + ":" + rc);
// Check for internal errors again.
checkInternalErrorLog();
} catch (Exception ex) { } catch (Exception ex) {
// Not much to be done at this point. The client side request // Not much to be done at this point. The client side request
// code will most likely throw an IOException and the // code will most likely throw an IOException and the
// compilation will fail. // compilation will fail.
StringWriter sw = new StringWriter(); Log.error(ex);
ex.printStackTrace(new PrintWriter(sw)); } finally {
Log.error(sw.toString()); 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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -25,10 +25,20 @@
package com.sun.tools.sjavac.server; 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.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;
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. * <p><b>This is NOT part of any supported API.
@ -37,20 +47,40 @@ import com.sun.tools.sjavac.Log;
* deletion without notice.</b> * deletion without notice.</b>
*/ */
public class ServerMain { public class ServerMain {
// For logging server internal (non request specific) errors.
private static LazyInitFileLog errorLog;
public static int run(String[] args) { public static int run(String[] args) {
Log.initializeLog(new OutputStreamWriter(System.out), // Under normal operation, all logging messages generated server-side
new OutputStreamWriter(System.err)); // 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? // Any options other than --startserver?
if (args.length > 1) { 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; return 1;
} }
int exitCode; int exitCode;
try { try {
SjavacServer server = new SjavacServer(args[0], System.err); SjavacServer server = new SjavacServer(args[0]);
exitCode = server.startServer(); exitCode = server.startServer();
} catch (IOException | InterruptedException ex) { } catch (IOException | InterruptedException ex) {
ex.printStackTrace(); ex.printStackTrace();
@ -59,4 +89,8 @@ public class ServerMain {
return exitCode; 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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * 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_FATAL = -1;
final static int RC_OK = 0; final static int RC_OK = 0;
int compile(String[] args, Writer stdout, Writer stderr); int compile(String[] args);
void shutdown(); void shutdown();
} }

View file

@ -26,6 +26,7 @@
package com.sun.tools.sjavac.server; package com.sun.tools.sjavac.server;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import java.io.PrintStream; import java.io.PrintStream;
import java.io.PrintWriter; import java.io.PrintWriter;
@ -39,6 +40,7 @@ import java.util.Map;
import java.util.Random; import java.util.Random;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import com.sun.tools.sjavac.Log;
import com.sun.tools.sjavac.Util; import com.sun.tools.sjavac.Util;
import com.sun.tools.sjavac.client.PortFileInaccessibleException; import com.sun.tools.sjavac.client.PortFileInaccessibleException;
import com.sun.tools.sjavac.comp.PooledSjavac; import com.sun.tools.sjavac.comp.PooledSjavac;
@ -54,17 +56,12 @@ import com.sun.tools.sjavac.comp.SjavacImpl;
*/ */
public class SjavacServer implements Terminable { 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_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 portfilename;
final private String logfile;
final private String stdouterrfile;
final private int poolsize; final private int poolsize;
final private int keepalive; final private int keepalive;
final private PrintStream err;
// The secret cookie shared between server and client through the port file. // The secret cookie shared between server and client through the port file.
// Used to prevent clients from believing that they are communicating with // 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 // Accumulated build time, not counting idle time, used for logging purposes
private long totalBuildTime; private long totalBuildTime;
// The javac server specific log file.
PrintWriter theLog;
// The sjavac implementation to delegate requests to // The sjavac implementation to delegate requests to
Sjavac sjavac; Sjavac sjavac;
@ -92,40 +86,29 @@ public class SjavacServer implements Terminable {
// For the client, all port files fetched, one per started javac server. // For the client, all port files fetched, one per started javac server.
// Though usually only one javac server is started by a client. // Though usually only one javac server is started by a client.
private static Map<String, PortFile> allPortFiles; 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), this(Util.extractStringOption("portfile", settings),
Util.extractStringOption("logfile", settings),
Util.extractStringOption("stdouterrfile", settings),
Util.extractIntOption("poolsize", settings, Runtime.getRuntime().availableProcessors()), Util.extractIntOption("poolsize", settings, Runtime.getRuntime().availableProcessors()),
Util.extractIntOption("keepalive", settings, 120), Util.extractIntOption("keepalive", settings, 120));
err);
} }
public SjavacServer(String portfilename, public SjavacServer(String portfilename,
String logfile,
String stdouterrfile,
int poolsize, int poolsize,
int keepalive, int keepalive)
PrintStream err)
throws FileNotFoundException { throws FileNotFoundException {
this.portfilename = portfilename; this.portfilename = portfilename;
this.logfile = logfile;
this.stdouterrfile = stdouterrfile;
this.poolsize = poolsize; this.poolsize = poolsize;
this.keepalive = keepalive; this.keepalive = keepalive;
this.err = err; this.myCookie = new Random().nextLong();
myCookie = new Random().nextLong();
theLog = new PrintWriter(logfile);
} }
/** /**
* Acquire the port file. Synchronized since several threads inside an smart javac wrapper client acquires the same port file at the same time. * 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) { if (allPortFiles == null) {
allPortFiles = new HashMap<>(); allPortFiles = new HashMap<>();
} }
@ -169,26 +152,6 @@ public class SjavacServer implements Terminable {
totalBuildTime += inc; 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" * 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. * 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.lock();
portFile.getValues(); portFile.getValues();
if (portFile.containsPortInfo()) { if (portFile.containsPortInfo()) {
err.println("Javac server not started because portfile exists!"); Log.info("Javac server not started because portfile exists!");
portFile.unlock(); portFile.unlock();
return -1; return -1;
} }
@ -230,23 +193,23 @@ public class SjavacServer implements Terminable {
portFileMonitor = new PortFileMonitor(portFile, this); portFileMonitor = new PortFileMonitor(portFile, this);
portFileMonitor.start(); portFileMonitor.start();
log("Sjavac server started. Accepting connections..."); Log.info("Sjavac server started. Accepting connections...");
log(" port: " + getPort()); Log.info(" port: " + getPort());
log(" time: " + new java.util.Date()); Log.info(" time: " + new java.util.Date());
log(" poolsize: " + poolsize); Log.info(" poolsize: " + poolsize);
flushLog();
keepAcceptingRequests.set(true); keepAcceptingRequests.set(true);
do { do {
try { try {
Socket socket = serverSocket.accept(); Socket socket = serverSocket.accept();
new Thread(new RequestHandler(socket, sjavac)).start(); new RequestHandler(socket, sjavac).start();
} catch (SocketException se) { } catch (SocketException se) {
// Caused by serverSocket.close() and indicates shutdown // Caused by serverSocket.close() and indicates shutdown
} }
} while (keepAcceptingRequests.get()); } while (keepAcceptingRequests.get());
log("Shutting down."); Log.info("Shutting down.");
// No more connections accepted. If any client managed to connect after // No more connections accepted. If any client managed to connect after
// the accept() was interrupted but before the server socket is closed // 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. // IOException on the client side.
long realTime = System.currentTimeMillis() - serverStart; long realTime = System.currentTimeMillis() - serverStart;
log("Total wall clock time " + realTime + "ms build time " + totalBuildTime + "ms"); Log.info("Total wall clock time " + realTime + "ms build time " + totalBuildTime + "ms");
flushLog();
// Shut down // Shut down
sjavac.shutdown(); sjavac.shutdown();
@ -270,8 +232,7 @@ public class SjavacServer implements Terminable {
return; return;
} }
log("Quitting: " + quitMsg); Log.info("Quitting: " + quitMsg);
flushLog();
portFileMonitor.shutdown(); // No longer any need to monitor port file portFileMonitor.shutdown(); // No longer any need to monitor port file
@ -280,12 +241,12 @@ public class SjavacServer implements Terminable {
try { try {
portFile.delete(); portFile.delete();
} catch (IOException | InterruptedException e) { } catch (IOException | InterruptedException e) {
e.printStackTrace(theLog); Log.error(e);
} }
try { try {
serverSocket.close(); serverSocket.close();
} catch (IOException e) { } 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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * 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 // Use Sjavac object and wait less than TIMEOUT_MS in between calls
Thread.sleep(TIMEOUT_MS - 1000); Thread.sleep(TIMEOUT_MS - 1000);
log("Compiling"); log("Compiling");
service.compile(new String[0], null, null); service.compile(new String[0]);
Thread.sleep(TIMEOUT_MS - 1000); Thread.sleep(TIMEOUT_MS - 1000);
log("Compiling"); log("Compiling");
service.compile(new String[0], null, null); service.compile(new String[0]);
if (timeoutTimestamp.get() != -1) if (timeoutTimestamp.get() != -1)
throw new AssertionError("Premature timeout detected."); throw new AssertionError("Premature timeout detected.");
@ -103,7 +103,7 @@ public class IdleShutdown {
public void shutdown() { public void shutdown() {
} }
@Override @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 // Attempt to trigger idle timeout during a call by sleeping
try { try {
Thread.sleep(TIMEOUT_MS + 1000); 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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -30,10 +30,12 @@
* @build Wrapper * @build Wrapper
* @run main Wrapper PooledExecution * @run main Wrapper PooledExecution
*/ */
import java.io.PrintWriter;
import java.io.Writer; import java.io.Writer;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import com.sun.tools.sjavac.Log;
import com.sun.tools.sjavac.comp.PooledSjavac; import com.sun.tools.sjavac.comp.PooledSjavac;
import com.sun.tools.sjavac.server.Sjavac; import com.sun.tools.sjavac.server.Sjavac;
@ -67,7 +69,7 @@ public class PooledExecution {
for (int i = 0; i < NUM_REQUESTS; i++) { for (int i = 0; i < NUM_REQUESTS; i++) {
tasks[i] = new Thread() { tasks[i] = new Thread() {
public void run() { public void run() {
service.compile(new String[0], null, null); service.compile(new String[0]);
tasksFinished.incrementAndGet(); tasksFinished.incrementAndGet();
} }
}; };
@ -109,7 +111,7 @@ public class PooledExecution {
AtomicInteger activeRequests = new AtomicInteger(0); AtomicInteger activeRequests = new AtomicInteger(0);
@Override @Override
public int compile(String[] args, Writer out, Writer err) { public int compile(String[] args) {
leftToStart.countDown(); leftToStart.countDown();
int numActiveRequests = activeRequests.incrementAndGet(); int numActiveRequests = activeRequests.incrementAndGet();
System.out.printf("Left to start: %2d / Currently active: %2d%n", System.out.printf("Left to start: %2d / Currently active: %2d%n",