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, throws IOException, InterruptedException {
String logfile,
int poolsize,
int keepalive,
final PrintStream err,
String stdouterrfile)
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;
@ -53,8 +57,8 @@ public class IdleResetSjavac implements Sjavac {
private TimerTask idlenessTimerTask; private TimerTask idlenessTimerTask;
public IdleResetSjavac(Sjavac delegate, public IdleResetSjavac(Sjavac delegate,
Terminable toShutdown, Terminable toShutdown,
long idleTimeout) { long idleTimeout) {
this.delegate = delegate; this.delegate = delegate;
this.toShutdown = toShutdown; this.toShutdown = toShutdown;
this.idleTimeout = idleTimeout; this.idleTimeout = idleTimeout;
@ -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",