summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorPaul Phillips <paulp@improving.org>2011-03-18 18:23:14 +0000
committerPaul Phillips <paulp@improving.org>2011-03-18 18:23:14 +0000
commitf3b970b28cf640904fe0d340f9eb7de37514cb67 (patch)
treeb5e77b7b78ec8743c8e4e0131cc7af4eb5611f7c /src
parentd5d7953ab4c31b4f58f51b530ca82603f9e59b94 (diff)
downloadscala-f3b970b28cf640904fe0d340f9eb7de37514cb67.tar.gz
scala-f3b970b28cf640904fe0d340f9eb7de37514cb67.tar.bz2
scala-f3b970b28cf640904fe0d340f9eb7de37514cb67.zip
Accumulated work on fsc.
adds the following new options. -ipv4 Use IPv4 rather than IPv6 for the server socket absolute-cp Make -classpath elements absolute paths before sending to server max-idle -Set idle timeout in minutes for fsc (use 0 for no timeout) My question marks are what are the right defaults for the first two. Former behavior is to absolutize the classpath always and never prefer IPv4 sockets. I changed the default to not absolutize the classpath, with the option if you need it; I left the system default in place for the socket creation, but I have a feeling we should default to IPv4. My only hesitation is that the only way to request an IPv4 socket from java involves mutating a global system property. (Robustness FTW.) So for now, you have to give -ipv4. Closes #3626, #3785, #3788, #3789. Review by community.
Diffstat (limited to 'src')
-rw-r--r--src/compiler/scala/tools/ant/FastScalac.scala3
-rw-r--r--src/compiler/scala/tools/nsc/CompileClient.scala29
-rw-r--r--src/compiler/scala/tools/nsc/CompileServer.scala59
-rw-r--r--src/compiler/scala/tools/nsc/CompileSocket.scala72
-rw-r--r--src/compiler/scala/tools/nsc/ScriptRunner.scala6
-rw-r--r--src/compiler/scala/tools/nsc/io/Socket.scala56
-rw-r--r--src/compiler/scala/tools/nsc/settings/AbsScalaSettings.scala4
-rw-r--r--src/compiler/scala/tools/nsc/settings/FscSettings.scala20
-rw-r--r--src/compiler/scala/tools/nsc/settings/MutableSettings.scala49
-rw-r--r--src/compiler/scala/tools/nsc/settings/ScalaSettings.scala4
-rw-r--r--src/compiler/scala/tools/util/SocketServer.scala133
-rw-r--r--src/library/scala/sys/BooleanProp.scala7
-rw-r--r--src/library/scala/sys/Prop.scala4
-rw-r--r--src/library/scala/sys/PropImpl.scala6
-rw-r--r--src/library/scala/sys/SystemProperties.scala14
15 files changed, 246 insertions, 220 deletions
diff --git a/src/compiler/scala/tools/ant/FastScalac.scala b/src/compiler/scala/tools/ant/FastScalac.scala
index 3c39bcf24d..403d9cc117 100644
--- a/src/compiler/scala/tools/ant/FastScalac.scala
+++ b/src/compiler/scala/tools/ant/FastScalac.scala
@@ -80,7 +80,6 @@ class FastScalac extends Scalac {
* Most likely this manifests in confusing and very difficult to debug behavior in fsc.
* We should warn or fix.
*/
-
val stringSettings =
List(s.outdir, s.classpath, s.bootclasspath, s.extdirs, s.encoding) flatMap (x => List(x.name, x.value))
@@ -104,7 +103,7 @@ class FastScalac extends Scalac {
val args = (cmdOptions ::: (sourceFiles map (_.toString))).toArray
try {
- if (scala.tools.nsc.CompileClient.main0(args) > 0 && failonerror)
+ if (scala.tools.nsc.CompileClient.process(args) != 0 && failonerror)
buildError("Compile failed; see the compiler error output for details.")
}
catch {
diff --git a/src/compiler/scala/tools/nsc/CompileClient.scala b/src/compiler/scala/tools/nsc/CompileClient.scala
index d1b1f751ef..9987eb0204 100644
--- a/src/compiler/scala/tools/nsc/CompileClient.scala
+++ b/src/compiler/scala/tools/nsc/CompileClient.scala
@@ -6,40 +6,37 @@
package scala.tools.nsc
import java.io.{ BufferedReader, File, InputStreamReader, PrintWriter }
-import Properties.fileEndings
-import io.Path
import settings.FscSettings
+import scala.tools.util.CompileOutputCommon
+import sys.SystemProperties.preferIPv4Stack
/** The client part of the fsc offline compiler. Instead of compiling
* things itself, it send requests to a CompileServer.
*/
-class StandardCompileClient extends HasCompileSocket {
+class StandardCompileClient extends HasCompileSocket with CompileOutputCommon {
lazy val compileSocket: CompileSocket = CompileSocket
val versionMsg = "Fast " + Properties.versionMsg
var verbose = false
- def logVerbose(msg: String) =
- if (verbose)
- Console println msg
-
- def main0(argsIn: Array[String]): Int = {
- // TODO: put -J -and -D options back. Right now they are lost
- // because bash parses them out and they don't arrive.
- val (vmArgs, fscArgs) = (Nil, argsIn.toList)
+ def process(args: Array[String]): Int = {
+ val fscArgs = args.toList
val settings = new FscSettings
val command = new CompilerCommand(fscArgs, settings)
verbose = settings.verbose.value
val shutdown = settings.shutdown.value
+ val vmArgs = settings.jvmargs.unparse ++ settings.defines.unparse ++ (
+ if (settings.preferIPv4.value) List("-D%s=true".format(preferIPv4Stack.key)) else Nil
+ )
if (settings.version.value) {
Console println versionMsg
return 0
}
- logVerbose(versionMsg)
- logVerbose(fscArgs.mkString("[Given arguments: ", " ", "]"))
- logVerbose(vmArgs.mkString("[VM arguments: ", " ", "]"))
+ info(versionMsg)
+ info(fscArgs.mkString("[Given arguments: ", " ", "]"))
+ info(vmArgs.mkString("[VM arguments: ", " ", "]"))
val socket =
if (settings.server.value == "") compileSocket.getOrCreateSocket(vmArgs mkString " ", !shutdown)
@@ -48,7 +45,7 @@ class StandardCompileClient extends HasCompileSocket {
val success = socket match {
case Some(sock) => compileOnServer(sock, fscArgs)
case _ =>
- Console.println(
+ echo(
if (shutdown) "[No compilation server running.]"
else "Compilation failed."
)
@@ -60,7 +57,7 @@ class StandardCompileClient extends HasCompileSocket {
object CompileClient extends StandardCompileClient {
def main(args: Array[String]): Unit = sys exit {
- try main0(args)
+ try process(args)
catch { case _: Exception => 1 }
}
}
diff --git a/src/compiler/scala/tools/nsc/CompileServer.scala b/src/compiler/scala/tools/nsc/CompileServer.scala
index fc98e30085..a34be6226b 100644
--- a/src/compiler/scala/tools/nsc/CompileServer.scala
+++ b/src/compiler/scala/tools/nsc/CompileServer.scala
@@ -21,6 +21,11 @@ import settings.FscSettings
*/
class StandardCompileServer extends SocketServer {
lazy val compileSocket: CompileSocket = CompileSocket
+ private var compiler: Global = null
+
+ var reporter: ConsoleReporter = _
+ var shutdown = false
+ var verbose = false
val versionMsg = "Fast Scala compiler " +
Properties.versionString + " -- " +
@@ -28,16 +33,6 @@ class StandardCompileServer extends SocketServer {
val MaxCharge = 0.8
- private var compiler: Global = null
- var reporter: ConsoleReporter = _
- var shutdown = false
-
- private def exit(code: Int): Nothing = {
- System.err.close()
- System.out.close()
- sys.exit(code)
- }
-
private val runtime = Runtime.getRuntime()
import runtime.{ totalMemory, freeMemory, maxMemory }
@@ -53,9 +48,9 @@ class StandardCompileServer extends SocketServer {
}
def printMemoryStats() {
- System.out.println("New session, total memory = %s, max memory = %s, free memory = %s".format(
- totalMemory, maxMemory, freeMemory))
- System.out.flush()
+ def mb(bytes: Long) = "%dMB".format(bytes / 1000000)
+ info("New session: total memory = %s, max memory = %s, free memory = %s".format(
+ mb(totalMemory), mb(maxMemory), mb(freeMemory)))
}
def isMemoryFullEnough() = {
@@ -97,13 +92,18 @@ class StandardCompileServer extends SocketServer {
if (input == null || password != guessedPassword)
return
- val args = input.split("\0", -1).toList
+ val args = input.split("\0", -1).toList
val settings = new FscSettings(fscError)
- def logVerbose(msg: String) =
- if (settings.verbose.value)
- out println msg
+ val command = newOfflineCompilerCommand(args, settings)
- val command = newOfflineCompilerCommand(args, settings)
+ // Update the idle timeout if given
+ if (!settings.idleMins.isDefault) {
+ val mins = settings.idleMins.value
+ if (mins == 0) echo("Disabling idle timeout on compile server.")
+ else echo("Setting idle timeout to " + mins + " minutes.")
+
+ this.idleMinutes = mins
+ }
if (settings.shutdown.value) {
shutdown = true
return out.println("[Compile server exited]")
@@ -112,6 +112,7 @@ class StandardCompileServer extends SocketServer {
compiler = null
return out.println("[Compile server was reset]")
}
+ this.verbose = settings.verbose.value
reporter = new ConsoleReporter(command.settings, in, out) {
// disable prompts, so that compile server cannot block
@@ -119,14 +120,14 @@ class StandardCompileServer extends SocketServer {
}
def isCompilerReusable: Boolean = {
if (compiler == null) {
- logVerbose("[Creating new instance for compile server.]")
- logVerbose("[Compiler version: " + Properties.versionString + ".]")
+ info("[Creating new instance for compile server.]")
+ info("[Compiler version: " + Properties.versionString + ".]")
return false
}
val unequal = unequalSettings(command.settings, compiler.settings)
if (unequal.nonEmpty) {
- logVerbose("[Replacing compiler with new instance because settings are unequal.]")
- logVerbose("[Asymmetric settings: " + unequal.mkString(", ") + "]")
+ info("[Replacing compiler with new instance because settings are unequal.]")
+ info("[Asymmetric settings: " + unequal.mkString(", ") + "]")
}
unequal.isEmpty
}
@@ -144,8 +145,7 @@ class StandardCompileServer extends SocketServer {
compiler = newGlobal(command.settings, reporter)
}
val c = compiler
- val run = new c.Run()
- try run compile command.files
+ try new c.Run() compile command.files
catch {
case ex @ FatalError(msg) =>
reporter.error(null, "fatal error: " + msg)
@@ -157,7 +157,7 @@ class StandardCompileServer extends SocketServer {
}
reporter.printSummary()
if (isMemoryFullEnough) {
- logVerbose("Nulling out compiler due to memory utilization.")
+ info("Nulling out compiler due to memory utilization.")
compiler = null
}
}
@@ -166,12 +166,19 @@ class StandardCompileServer extends SocketServer {
object CompileServer extends StandardCompileServer {
/** A directory holding redirected output */
- private val redirectDir = (compileSocket.tmpDir / "output-redirects").createDirectory()
+ private lazy val redirectDir = (compileSocket.tmpDir / "output-redirects").createDirectory()
private def redirect(setter: PrintStream => Unit, filename: String): Unit =
setter(new PrintStream((redirectDir / filename).createFile().bufferedOutput()))
def main(args: Array[String]) {
+ val debug = args contains "-v"
+
+ if (debug) {
+ echo("Starting CompileServer on port " + port)
+ echo("Redirect dir is " + redirectDir)
+ }
+
redirect(System.setOut, "scala-compile-server-out.log")
redirect(System.setErr, "scala-compile-server-err.log")
System.err.println("...starting server on socket "+port+"...")
diff --git a/src/compiler/scala/tools/nsc/CompileSocket.scala b/src/compiler/scala/tools/nsc/CompileSocket.scala
index c75c6d0f22..1fd37eb70c 100644
--- a/src/compiler/scala/tools/nsc/CompileSocket.scala
+++ b/src/compiler/scala/tools/nsc/CompileSocket.scala
@@ -10,14 +10,19 @@ import java.io.{ BufferedReader, FileReader }
import java.util.regex.Pattern
import java.net._
import java.security.SecureRandom
-
-import io.{ File, Path, Socket }
+import io.{ File, Path, Directory, Socket }
import scala.util.control.Exception.catching
+import scala.tools.util.CompileOutputCommon
import scala.tools.util.StringOps.splitWhere
import scala.sys.process._
trait HasCompileSocket {
def compileSocket: CompileSocket
+
+ // This is kind of a suboptimal way to identify error situations.
+ val errorMarkers = Set("error:", "error found", "errors found", "bad option")
+ def isErrorMessage(msg: String) = errorMarkers exists (msg contains _)
+
def compileOnServer(sock: Socket, args: Seq[String]): Boolean = {
var noErrors = true
@@ -28,10 +33,10 @@ trait HasCompileSocket {
def loop(): Boolean = in.readLine() match {
case null => noErrors
case line =>
- if (compileSocket.errorPattern matcher line matches)
+ if (isErrorMessage(line))
noErrors = false
- Console.err println line
+ compileSocket.echo(line)
loop()
}
try loop()
@@ -41,8 +46,9 @@ trait HasCompileSocket {
}
/** This class manages sockets for the fsc offline compiler. */
-class CompileSocket {
+class CompileSocket extends CompileOutputCommon {
protected lazy val compileClient: StandardCompileClient = CompileClient
+ def verbose = compileClient.verbose
/** The prefix of the port identification file, which is followed
* by the port number.
@@ -60,23 +66,8 @@ class CompileSocket {
}
/** The class name of the scala compile server */
- protected val serverClass = "scala.tools.nsc.CompileServer"
-
- /** A regular expression for checking compiler output for errors */
- val errorRegex = ".*(errors? found|don't know|bad option).*"
-
- /** A Pattern object for checking compiler output for errors */
- val errorPattern = Pattern compile errorRegex
-
- protected def fscError(msg: String) = System.err.println(msg)
-
- protected def fatal(msg: String) = {
- fscError(msg)
- sys.error("fsc failure")
- }
-
- protected def info(msg: String) =
- if (compileClient.verbose) System.out.println(msg)
+ protected val serverClass = "scala.tools.nsc.CompileServer"
+ protected def serverClassArgs = if (verbose) List("-v") else Nil // debug
/** A temporary directory to use */
val tmpDir = {
@@ -93,23 +84,17 @@ class CompileSocket {
/* A directory holding port identification files */
val portsDir = (tmpDir / dirName).createDirectory()
- /** Maximum number of polls for an available port */
- private val MaxAttempts = 100
-
- /** Time (in ms) to sleep between two polls */
- private val sleepTime = 20
-
/** The command which starts the compile server, given vm arguments.
*
* @param vmArgs the argument string to be passed to the java or scala command
*/
private def serverCommand(vmArgs: Seq[String]): Seq[String] =
- Seq(vmCommand) ++ vmArgs ++ Seq(serverClass) filterNot (_ == "")
+ Seq(vmCommand) ++ vmArgs ++ Seq(serverClass) ++ serverClassArgs filterNot (_ == "")
/** Start a new server. */
private def startNewServer(vmArgs: String) = {
val cmd = serverCommand(vmArgs split " " toSeq)
- info("[Executing command: %s]" format cmd)
+ info("[Executing command: %s]" format cmd.mkString(" "))
cmd.daemonized().run()
}
@@ -117,18 +102,18 @@ class CompileSocket {
def portFile(port: Int) = portsDir / File(port.toString)
/** Poll for a server port number; return -1 if none exists yet */
- private def pollPort(): Int = portsDir.list match {
- case it if !it.hasNext => -1
- case it =>
- val ret = it.next.name.toInt
- it foreach (_.delete())
- ret
+ private def pollPort(): Int = portsDir.list.toList match {
+ case Nil => -1
+ case x :: xs => try x.name.toInt finally xs foreach (_.delete())
}
/** Get the port number to which a scala compile server is connected;
* If no server is running yet, then create one.
*/
def getPort(vmArgs: String): Int = {
+ val maxPolls = 300
+ val sleepTime = 25
+
var attempts = 0
var port = pollPort()
@@ -136,15 +121,14 @@ class CompileSocket {
info("No compile server running: starting one with args '" + vmArgs + "'")
startNewServer(vmArgs)
}
-
- while (port < 0 && attempts < MaxAttempts) {
+ while (port < 0 && attempts < maxPolls) {
attempts += 1
Thread.sleep(sleepTime)
port = pollPort()
}
info("[Port number: " + port + "]")
if (port < 0)
- fatal("Could not connect to compilation daemon.")
+ fatal("Could not connect to compilation daemon after " + attempts + " attempts.")
port
}
@@ -167,17 +151,17 @@ class CompileSocket {
* cannot be established.
*/
def getOrCreateSocket(vmArgs: String, create: Boolean = true): Option[Socket] = {
- // try for 10 seconds
- val retryDelay = 100
- val maxAttempts = (10 * 1000) / retryDelay
+ val maxMillis = 10 * 1000 // try for 10 seconds
+ val retryDelay = 50
+ val maxAttempts = maxMillis / retryDelay
def getsock(attempts: Int): Option[Socket] = attempts match {
- case 0 => fscError("Unable to establish connection to compilation daemon") ; None
+ case 0 => warn("Unable to establish connection to compilation daemon") ; None
case num =>
val port = if (create) getPort(vmArgs) else pollPort()
if (port < 0) return None
- Socket(InetAddress.getLocalHost(), port).either match {
+ Socket.localhost(port).either match {
case Right(socket) =>
info("[Connected to compilation daemon at port %d]" format port)
Some(socket)
diff --git a/src/compiler/scala/tools/nsc/ScriptRunner.scala b/src/compiler/scala/tools/nsc/ScriptRunner.scala
index 5eb6027d0a..07aa62d164 100644
--- a/src/compiler/scala/tools/nsc/ScriptRunner.scala
+++ b/src/compiler/scala/tools/nsc/ScriptRunner.scala
@@ -49,12 +49,6 @@ import util.Exceptional.unwrap
class ScriptRunner extends HasCompileSocket {
lazy val compileSocket = CompileSocket
- /* While I'm chasing down the fsc and script bugs. */
- def DBG(msg: Any) {
- System.err.println(msg.toString)
- System.err.flush()
- }
-
/** Default name to use for the wrapped script */
val defaultScriptMain = "Main"
diff --git a/src/compiler/scala/tools/nsc/io/Socket.scala b/src/compiler/scala/tools/nsc/io/Socket.scala
index 0be7de3873..b7920921fa 100644
--- a/src/compiler/scala/tools/nsc/io/Socket.scala
+++ b/src/compiler/scala/tools/nsc/io/Socket.scala
@@ -6,36 +6,56 @@
package scala.tools.nsc
package io
-import java.io.{ IOException, InputStreamReader, BufferedReader, PrintWriter }
-import java.net.{ InetAddress, Socket => JSocket }
-import scala.util.control.Exception._
+import java.io.{ IOException, InputStreamReader, BufferedReader, PrintWriter, Closeable }
+import java.io.{ BufferedOutputStream, BufferedReader }
+import java.net.{ ServerSocket, SocketException, SocketTimeoutException, InetAddress, Socket => JSocket }
+import scala.sys.SystemProperties._
+import scala.io.Codec
/** A skeletal only-as-much-as-I-need Socket wrapper.
*/
-object Socket
-{
- private val socketExceptions = List(classOf[IOException], classOf[SecurityException])
+object Socket {
+ def preferringIPv4[T](body: => T): T = exclusively {
+ val saved = preferIPv4Stack.value
+ try { preferIPv4Stack.enable() ; body }
+ finally preferIPv4Stack setValue saved
+ }
+
+ class Box[+T](f: () => T) {
+ private def handlerFn[U](f: Throwable => U): PartialFunction[Throwable, U] = {
+ case x @ (_: IOException | _: SecurityException) => f(x)
+ }
+ private val optHandler = handlerFn[Option[T]](_ => None)
+ private val eitherHandler = handlerFn[Either[Throwable, T]](x => Left(x))
- class SocketBox(f: () => Socket) {
- def either: Either[Throwable, Socket] = catching(socketExceptions: _*) either f()
- def opt: Option[Socket] = catching(socketExceptions: _*) opt f()
+ def getOrElse[T1 >: T](alt: T1): T1 = opt getOrElse alt
+ def either: Either[Throwable, T] = try Right(f()) catch eitherHandler
+ def opt: Option[T] = try Some(f()) catch optHandler
}
- def apply(host: InetAddress, port: Int) = new SocketBox(() => new Socket(new JSocket(host, port)))
- def apply(host: String, port: Int) = new SocketBox(() => new Socket(new JSocket(host, port)))
+ def newIPv4Server(port: Int = 0) = new Box(() => preferringIPv4(new ServerSocket(0)))
+ def newServer(port: Int = 0) = new Box(() => new ServerSocket(0))
+ def localhost(port: Int) = apply(InetAddress.getLocalHost(), port)
+ def apply(host: InetAddress, port: Int) = new Box(() => new Socket(new JSocket(host, port)))
+ def apply(host: String, port: Int) = new Box(() => new Socket(new JSocket(host, port)))
}
-class Socket(jsocket: JSocket) {
- def getOutputStream() = jsocket.getOutputStream()
- def getInputStream() = jsocket.getInputStream()
- def getPort() = jsocket.getPort()
- def close() = jsocket.close()
+class Socket(jsocket: JSocket) extends Streamable.Bytes with Closeable {
+ def inputStream() = jsocket.getInputStream()
+ def outputStream() = jsocket.getOutputStream()
+ def getPort() = jsocket.getPort()
+ def close() = jsocket.close()
+
+ def printWriter() = new PrintWriter(outputStream(), true)
+ def bufferedReader(implicit codec: Codec) = new BufferedReader(new InputStreamReader(inputStream()))
+ def bufferedOutput(size: Int) = new BufferedOutputStream(outputStream(), size)
/** Creates an InputStream and applies the closure, automatically closing it on completion.
*/
def applyReaderAndWriter[T](f: (BufferedReader, PrintWriter) => T): T = {
- val out = new PrintWriter(getOutputStream(), true)
- val in = new BufferedReader(new InputStreamReader(getInputStream()))
+ val out = printWriter()
+ val in = bufferedReader
+
try f(in, out)
finally {
in.close()
diff --git a/src/compiler/scala/tools/nsc/settings/AbsScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/AbsScalaSettings.scala
index 80183791ed..6f130b9e74 100644
--- a/src/compiler/scala/tools/nsc/settings/AbsScalaSettings.scala
+++ b/src/compiler/scala/tools/nsc/settings/AbsScalaSettings.scala
@@ -16,7 +16,7 @@ trait AbsScalaSettings {
type PathSetting <: AbsSetting { type T = String }
type PhasesSetting <: AbsSetting { type T = List[String] }
type StringSetting <: AbsSetting { type T = String }
- type MapSetting <: AbsSetting { type T = Map[String, String] }
+ type PrefixSetting <: AbsSetting { type T = List[String] }
type OutputDirs
type OutputSetting <: AbsSetting
@@ -29,7 +29,7 @@ trait AbsScalaSettings {
def PathSetting(name: String, descr: String, default: String): PathSetting
def PhasesSetting(name: String, descr: String): PhasesSetting
def StringSetting(name: String, helpArg: String, descr: String, default: String): StringSetting
- def MapSetting(name: String, prefix: String, descr: String): MapSetting
+ def PrefixSetting(name: String, prefix: String, descr: String): PrefixSetting
/** **/
abstract class SettingGroup(val prefix: String) extends AbsSetting {
diff --git a/src/compiler/scala/tools/nsc/settings/FscSettings.scala b/src/compiler/scala/tools/nsc/settings/FscSettings.scala
index b7f0c553f6..a219148b16 100644
--- a/src/compiler/scala/tools/nsc/settings/FscSettings.scala
+++ b/src/compiler/scala/tools/nsc/settings/FscSettings.scala
@@ -14,16 +14,22 @@ class FscSettings(error: String => Unit) extends Settings(error) {
def this() = this(Console.println)
- val reset = BooleanSetting("-reset", "Reset compile server caches")
- val shutdown = BooleanSetting("-shutdown", "Shutdown compile server")
- val server = StringSetting ("-server", "hostname:portnumber", "Specify compile server socket", "")
+ val reset = BooleanSetting("-reset", "Reset compile server caches")
+ val shutdown = BooleanSetting("-shutdown", "Shutdown compile server")
+ val server = StringSetting ("-server", "hostname:portnumber", "Specify compile server socket", "")
+ val preferIPv4 = BooleanSetting("-ipv4", "Use IPv4 rather than IPv6 for the server socket")
+ val absClasspath = BooleanSetting("-absolute-cp", "Make classpath elements absolute paths before sending to server") .
+ withPostSetHook (_ => absolutizeClasspath())
+ val idleMins = IntSetting ("-max-idle", "Set idle timeout in minutes for fsc (use 0 for no timeout)",
+ 30, Some(0, Int.MaxValue), (_: String) => None)
disable(prompt)
disable(resident)
- // Make all paths absolute since we may be going from client to server
- userSetSettings foreach {
- case x: PathSetting => x.value = ClassPath.makeAbsolute(x.value)
- case _ => ()
+ // Make the classpath absolute: for going from client to server.
+ private def absolutizeClasspath() {
+ userSetSettings collect {
+ case x: PathSetting => x.value = ClassPath.makeAbsolute(x.value)
+ }
}
}
diff --git a/src/compiler/scala/tools/nsc/settings/MutableSettings.scala b/src/compiler/scala/tools/nsc/settings/MutableSettings.scala
index f67a9a84dc..6a357cc08a 100644
--- a/src/compiler/scala/tools/nsc/settings/MutableSettings.scala
+++ b/src/compiler/scala/tools/nsc/settings/MutableSettings.scala
@@ -80,6 +80,11 @@ class MutableSettings(val errorFn: String => Unit) extends AbsSettings with Scal
*/
lazy val outputDirs = new OutputDirs
+ /** A list of settings which act based on prefix rather than an exact
+ * match. This is basically -D and -J.
+ */
+ lazy val prefixSettings = allSettings collect { case x: PrefixSetting => x }
+
/** Split the given line into parameters.
*/
def splitParams(line: String) = cmd.Parser.tokenize(line, errorFn)
@@ -124,12 +129,18 @@ class MutableSettings(val errorFn: String => Unit) extends AbsSettings with Scal
}
else {
// we dispatch differently based on the appearance of p:
- // 1) If it has a : it is presumed to be -Xfoo:bar,baz
- // 2) Otherwise, the whole string should be a command name
+ // 1) If it matches a prefix setting it is sent there directly.
+ // 2) If it has a : it is presumed to be -Xfoo:bar,baz
+ // 3) Otherwise, the whole string should be a command name
//
// Internally we use Option[List[String]] to discover error,
// but the outside expects our arguments back unchanged on failure
- if (arg contains ":") parseColonArg(arg) match {
+ val prefix = prefixSettings find (_ respondsTo arg)
+ if (prefix.isDefined) {
+ prefix.get tryToSet args
+ rest
+ }
+ else if (arg contains ":") parseColonArg(arg) match {
case Some(_) => rest
case None => args
}
@@ -188,7 +199,7 @@ class MutableSettings(val errorFn: String => Unit) extends AbsSettings with Scal
add(new PathSetting(name, descr, default, prepend, append))
}
- def MapSetting(name: String, prefix: String, descr: String): MapSetting = add(new MapSetting(name, prefix, descr))
+ def PrefixSetting(name: String, prefix: String, descr: String): PrefixSetting = add(new PrefixSetting(name, prefix, descr))
// basically this is a value which remembers if it's been modified
trait SettingValue extends AbsSettingValue {
@@ -410,31 +421,23 @@ class MutableSettings(val errorFn: String => Unit) extends AbsSettings with Scal
}
/** A special setting for accumulating arguments like -Dfoo=bar. */
- class MapSetting private[nsc](
+ class PrefixSetting private[nsc](
name: String,
prefix: String,
descr: String)
extends Setting(name, descr) {
- type T = Map[String, String]
- protected var v: Map[String, String] = Map()
-
- def tryToSet(args: List[String]) = {
- val (xs, rest) = args partition (_ startsWith prefix)
- val pairs = xs map (_ stripPrefix prefix) map { x =>
- (x indexOf '=') match {
- case -1 => (x, "")
- case idx => (x take idx, x drop (idx + 1))
- }
- }
- v = v ++ pairs
- Some(rest)
- }
+ type T = List[String]
+ protected var v: List[String] = Nil
- override def respondsTo(label: String) = label startsWith prefix
- def unparse: List[String] = v.toList map {
- case (k, "") => prefix + k
- case (k, v) => prefix + k + "=" + v
+ def tryToSet(args: List[String]) = args match {
+ case x :: xs if x startsWith prefix =>
+ v = v :+ x
+ Some(xs)
+ case _ =>
+ None
}
+ override def respondsTo(token: String) = token startsWith prefix
+ def unparse: List[String] = value
}
/** A setting represented by a string, (`default' unless set) */
diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
index 16289f11d9..1670dfb94a 100644
--- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
+++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
@@ -23,8 +23,8 @@ trait ScalaSettings extends AbsScalaSettings with StandardScalaSettings {
/** Disable a setting */
def disable(s: Setting) = allSettings -= s
- val jvmargs = MapSetting("-J<flag>", "-J", "Pass <flag> directly to the runtime system.")
- val defines = MapSetting("-Dproperty=value", "-D", "Pass -Dproperty=value directly to the runtime system.")
+ val jvmargs = PrefixSetting("-J<flag>", "-J", "Pass <flag> directly to the runtime system.")
+ val defines = PrefixSetting("-Dproperty=value", "-D", "Pass -Dproperty=value directly to the runtime system.")
val toolcp = PathSetting("-toolcp", "Add to the runner classpath.", "")
val nobootcp = BooleanSetting("-nobootcp", "Do not use the boot classpath for the scala jars.")
diff --git a/src/compiler/scala/tools/util/SocketServer.scala b/src/compiler/scala/tools/util/SocketServer.scala
index 0094333402..fba3d4ce48 100644
--- a/src/compiler/scala/tools/util/SocketServer.scala
+++ b/src/compiler/scala/tools/util/SocketServer.scala
@@ -6,100 +6,95 @@
** |/ **
\* */
-
package scala.tools.util
-import java.io.{ PrintWriter, BufferedOutputStream, BufferedReader, InputStreamReader, IOException }
-import java.net.{ Socket, ServerSocket, SocketException, SocketTimeoutException }
+import java.net.{ ServerSocket, SocketException, SocketTimeoutException }
+import java.io.{ PrintWriter, BufferedReader }
+import scala.tools.nsc.io.Socket
-object SocketServer {
- val BufferSize = 10240
+trait CompileOutputCommon {
+ def verbose: Boolean
- def bufferedReader(s: Socket) = new BufferedReader(new InputStreamReader(s.getInputStream()))
- def bufferedOutput(s: Socket) = new BufferedOutputStream(s.getOutputStream, BufferSize)
+ def info(msg: String) = if (verbose) echo(msg)
+ def echo(msg: String) = Console println msg
+ def warn(msg: String) = System.err println msg
+ def fatal(msg: String) = { warn(msg) ; sys.exit(1) }
}
-import SocketServer._
-/** The abstract class <code>SocketServer</code> implements the server
+/** The abstract class SocketServer implements the server
* communication for the fast Scala compiler.
*
* @author Martin Odersky
* @version 1.0
*/
-abstract class SocketServer {
- // After some number of idle minutes, politely exit.
- // Should the port file disappear, and the clients
- // therefore unable to contact this server instance,
- // the process will just eventually terminate by itself.
- def fscIdleMinutes = {
- sys.props("scala.config.fsc.idle-minutes") match {
- case null => 30
- case str => try str.toInt catch { case _: Exception => 30 }
- }
- }
- def fscIdleMillis = fscIdleMinutes * 60 * 1000
-
+abstract class SocketServer extends CompileOutputCommon {
def shutdown: Boolean
def session(): Unit
+ def timeout(): Unit = () // called after a timeout is detected for subclasses to cleanup
+ // a hook for subclasses
+ protected def createServerSocket(): ServerSocket = new ServerSocket(0)
- var out: PrintWriter = _
var in: BufferedReader = _
-
- def fatal(msg: String): Nothing = {
- System.err.println(msg)
- sys.exit(1)
- }
-
- private def warn(msg: String) {
- System.err.println(msg)
+ var out: PrintWriter = _
+ val BufferSize = 10240
+ lazy val serverSocket = createServerSocket()
+ lazy val port = serverSocket.getLocalPort()
+
+ // Default to 30 minute idle timeout, settable with -max-idle
+ protected var idleMinutes = 30
+ private var savedTimeout = 0
+ private val acceptBox = new Socket.Box(() => {
+ // update the timeout if it has changed
+ if (savedTimeout != idleMinutes) {
+ savedTimeout = idleMinutes
+ setTimeoutOnSocket(savedTimeout)
+ }
+ new Socket(serverSocket.accept())
+ })
+ private def setTimeoutOnSocket(mins: Int) = {
+ try {
+ serverSocket setSoTimeout (mins * 60 * 1000)
+ info("Set socket timeout to " + mins + " minutes.")
+ true
+ }
+ catch {
+ case ex: SocketException =>
+ warn("Failed to set socket timeout: " + ex)
+ false
+ }
}
- // called after a timeout is detected,
- // for SocketServer subclasses to perform
- // some cleanup, if any
- def timeout() {}
-
- val serverSocket =
- try new ServerSocket(0)
- catch { case e: IOException => fatal("Could not listen on any port; exiting.") }
-
- val port: Int = serverSocket.getLocalPort()
-
- // @todo: this is going to be a prime candidate for ARM
def doSession(clientSocket: Socket) = {
- out = new PrintWriter(clientSocket.getOutputStream(), true)
- in = bufferedReader(clientSocket)
- val bufout = bufferedOutput(clientSocket)
+ clientSocket.applyReaderAndWriter { (in, out) =>
+ this.in = in
+ this.out = out
+ val bufout = clientSocket.bufferedOutput(BufferSize)
- try scala.Console.withOut(bufout)(session())
- finally {
- bufout.close()
- out.close()
- in.close()
+ try scala.Console.withOut(bufout)(session())
+ finally bufout.close()
}
}
def run() {
- def fail(s: String) = fatal(s format port)
- Console.println("Setting timeout to " + fscIdleMillis)
- try serverSocket setSoTimeout fscIdleMillis catch {
- case e: SocketException => fatal("Could not set timeout on server socket; exiting.")
- }
-
- try {
- while (!shutdown) {
- val clientSocket = try serverSocket.accept() catch {
- case e: IOException => fail("Accept on port %d failed; exiting.")
- }
- try doSession(clientSocket)
- finally clientSocket.close()
+ info("Starting SocketServer run() loop.")
+
+ def loop() {
+ acceptBox.either match {
+ case Right(clientSocket) =>
+ try doSession(clientSocket)
+ finally clientSocket.close()
+ case Left(_: SocketTimeoutException) =>
+ warn("Idle timeout exceeded on port %d; exiting" format port)
+ timeout()
+ return
+ case _ =>
+ warn("Accept on port %d failed")
}
+ if (!shutdown)
+ loop()
}
- catch {
- case e: SocketTimeoutException =>
- warn("Timeout elapsed with no requests from clients on port %d; exiting" format port)
- timeout()
- }
+ try loop()
+ catch { case ex: SocketException => fatal("Compile server caught fatal exception: " + ex) }
finally serverSocket.close()
}
}
diff --git a/src/library/scala/sys/BooleanProp.scala b/src/library/scala/sys/BooleanProp.scala
index e598d81307..85719103de 100644
--- a/src/library/scala/sys/BooleanProp.scala
+++ b/src/library/scala/sys/BooleanProp.scala
@@ -31,7 +31,11 @@ trait BooleanProp extends Prop[Boolean] {
object BooleanProp {
private[sys]
class BooleanPropImpl(key: String, valueFn: String => Boolean) extends PropImpl(key, valueFn) with BooleanProp {
- def enable() = this set "true"
+ override def setValue[T1 >: Boolean](newValue: T1): Boolean = newValue match {
+ case x: Boolean if !x => val old = value ; clear() ; old
+ case x => super.setValue(newValue)
+ }
+ def enable() = this setValue true
def disable() = this.clear()
def toggle() = if (value) disable() else enable()
}
@@ -39,6 +43,7 @@ object BooleanProp {
class ConstantImpl(val key: String, val value: Boolean) extends BooleanProp {
val isSet = value
def set(newValue: String) = "" + value
+ def setValue[T1 >: Boolean](newValue: T1): Boolean = value
def get: String = "" + value
val clear, enable, disable, toggle = ()
protected def zero = false
diff --git a/src/library/scala/sys/Prop.scala b/src/library/scala/sys/Prop.scala
index e3cbd4e515..de38a56c73 100644
--- a/src/library/scala/sys/Prop.scala
+++ b/src/library/scala/sys/Prop.scala
@@ -43,6 +43,10 @@ trait Prop[+T] {
*/
def set(newValue: String): String
+ /** Sets the property with a value of the represented type.
+ */
+ def setValue[T1 >: T](value: T1): T
+
/** Gets the current string value if any. Will not return null: use
* `isSet` to test for existence.
* @return the current string value if any, else the empty string
diff --git a/src/library/scala/sys/PropImpl.scala b/src/library/scala/sys/PropImpl.scala
index 55073c35d3..888e9d7327 100644
--- a/src/library/scala/sys/PropImpl.scala
+++ b/src/library/scala/sys/PropImpl.scala
@@ -20,6 +20,12 @@ private[sys] class PropImpl[+T](val key: String, valueFn: String => T) extends P
underlying(key) = newValue
old
}
+ def setValue[T1 >: T](newValue: T1): T = {
+ val old = value
+ if (newValue == null) set(null)
+ else set("" + newValue)
+ old
+ }
def get: String =
if (isSet) underlying.getOrElse(key, "")
else ""
diff --git a/src/library/scala/sys/SystemProperties.scala b/src/library/scala/sys/SystemProperties.scala
index 7c6286516b..228ca6315e 100644
--- a/src/library/scala/sys/SystemProperties.scala
+++ b/src/library/scala/sys/SystemProperties.scala
@@ -48,9 +48,14 @@ class SystemProperties extends mutable.Map[String, String] {
* }}}
*/
object SystemProperties {
+ /** An unenforceable, advisory only place to do some synchronization when
+ * mutating system properties.
+ */
+ def exclusively[T](body: => T) = this synchronized body
+
implicit def systemPropertiesToCompanion(p: SystemProperties): SystemProperties.type = this
private lazy val propertyHelp = mutable.Map[String, String]()
- private def bool(key: String, helpText: String) = {
+ private def bool(key: String, helpText: String): BooleanProp = {
val prop = (
if (key startsWith "java.") BooleanProp.valueIsTrue(key)
else BooleanProp.keyExists(key)
@@ -63,8 +68,9 @@ object SystemProperties {
// Todo: bring some sanity to the intersection of system properties aka "mutable
// state shared by everyone and everything" and the reality that there is no other
// mechanism for accomplishing some things on the jvm.
- lazy val headless = bool("java.awt.headless", "system should not utilize a display device")
- lazy val preferIPv4 = bool("java.net.preferIPv4Stack", "system should prefer IPv4 sockets")
- lazy val noTraceSupression = bool("scala.control.no-trace-suppression", "scala should not suppress any stack trace creation")
+ lazy val headless = bool("java.awt.headless", "system should not utilize a display device")
+ lazy val preferIPv4Stack = bool("java.net.preferIPv4Stack", "system should prefer IPv4 sockets")
+ lazy val preferIPv6Addresses = bool("java.net.preferIPv6Addresses", "system should prefer IPv6 addresses")
+ lazy val noTraceSupression = bool("scala.control.noTraceSuppression", "scala should not suppress any stack trace creation")
}