From f3b970b28cf640904fe0d340f9eb7de37514cb67 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Fri, 18 Mar 2011 18:23:14 +0000 Subject: 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. --- src/compiler/scala/tools/ant/FastScalac.scala | 3 +- src/compiler/scala/tools/nsc/CompileClient.scala | 29 ++--- src/compiler/scala/tools/nsc/CompileServer.scala | 59 +++++---- src/compiler/scala/tools/nsc/CompileSocket.scala | 72 +++++------ src/compiler/scala/tools/nsc/ScriptRunner.scala | 6 - src/compiler/scala/tools/nsc/io/Socket.scala | 56 ++++++--- .../tools/nsc/settings/AbsScalaSettings.scala | 4 +- .../scala/tools/nsc/settings/FscSettings.scala | 20 ++-- .../scala/tools/nsc/settings/MutableSettings.scala | 49 ++++---- .../scala/tools/nsc/settings/ScalaSettings.scala | 4 +- src/compiler/scala/tools/util/SocketServer.scala | 133 ++++++++++----------- src/library/scala/sys/BooleanProp.scala | 7 +- src/library/scala/sys/Prop.scala | 4 + src/library/scala/sys/PropImpl.scala | 6 + src/library/scala/sys/SystemProperties.scala | 14 ++- 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", "-J", "Pass directly to the runtime system.") - val defines = MapSetting("-Dproperty=value", "-D", "Pass -Dproperty=value directly to the runtime system.") + val jvmargs = PrefixSetting("-J", "-J", "Pass 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 SocketServer 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") } -- cgit v1.2.3