summaryrefslogtreecommitdiff
path: root/src/compiler
diff options
context:
space:
mode:
authorAntonio Cunei <antonio.cunei@epfl.ch>2011-03-18 21:45:45 +0000
committerAntonio Cunei <antonio.cunei@epfl.ch>2011-03-18 21:45:45 +0000
commit1ca657a0a8e2c1d378b6344b1961042bc126e97e (patch)
tree36b61062c79e649d536d7e6e7a57f3bf034a3446 /src/compiler
parent44e18adf9af287c4be0ef12156faec5a634f2323 (diff)
downloadscala-1ca657a0a8e2c1d378b6344b1961042bc126e97e.tar.gz
scala-1ca657a0a8e2c1d378b6344b1961042bc126e97e.tar.bz2
scala-1ca657a0a8e2c1d378b6344b1961042bc126e97e.zip
Merged revisions 24494-24498 via svnmerge from
https://lampsvn.epfl.ch/svn-repos/scala/scala/trunk ........ r24494 | extempore | 2011-03-18 19:23:14 +0100 (Fri, 18 Mar 2011) | 17 lines Accumulated work on fsc. Cleans up a bunch of things, and 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. ........ r24495 | extempore | 2011-03-18 20:06:22 +0100 (Fri, 18 Mar 2011) | 2 lines Improved the error message when one gives invalid options to scala. Similar improvements for scalac/fsc/etc has to wait. No review. ........ r24496 | prokopec | 2011-03-18 20:07:38 +0100 (Fri, 18 Mar 2011) | 25 lines Removing toPar* methods, since we've agreed they're difficult to: - underestand - maintain Also, changed the docs and some tests appropriately. Description: 1) Every collection is now parallelizable - switch to the parallel version of the collection is done via `par`. - Traversable collections and iterators have `par` return a parallel collection of type `ParIterable[A]` with the implementation being the representative of `ParIterable`s (currently, `ParArray`). - Iterable collections do the same thing. - Sequences refine `par`'s returns type to `ParSeq[A]`. - Maps and sets do a similar thing. The above means that the contract for `par` changed - it is no longer guaranteed to be O(1), nor reflect the same underlying data, as was the case for mutable collections before. Method `par` is now at worst linear. Furthermore, specific collection implementations override `par` to a more efficient alternative - instead of copying the dataset, the dataset is shared between the old and the new version. Implementation complexity may be sublinear or constant in these cases, and the underlying data structure may be shared. Currently, these data structures include parallel arrays, maps and sets, vectors, hash trie maps and sets, and ranges. Finally, parallel collections implement `par` trivially. 2) Methods `toMap`, `toSet`, `toSeq` and `toIterable` have been refined for parallel collections to switch between collection types, however, they never switch an implementation from parallel to sequential. They may or may not copy the elements, as is the case with sequential variants of these methods. 3) The preferred way to switch between different collection types, whether maps, sets and seqs, or parallel and sequential, is now via use of methods `toIterable`, `toSeq`, `toSet` and `toMap` in combination with `par` and `seq`. Review by odersky. ........ r24497 | extempore | 2011-03-18 22:07:37 +0100 (Fri, 18 Mar 2011) | 2 lines A minor overhaul of power mode. I'm going to document it any minute now. No review. ........ r24498 | extempore | 2011-03-18 22:22:57 +0100 (Fri, 18 Mar 2011) | 1 line Little tweak for failing test, no review. ........
Diffstat (limited to 'src/compiler')
-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/CompilerCommand.scala4
-rw-r--r--src/compiler/scala/tools/nsc/GenericRunnerCommand.scala21
-rw-r--r--src/compiler/scala/tools/nsc/MainGenericRunner.scala2
-rw-r--r--src/compiler/scala/tools/nsc/ScriptRunner.scala6
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/Power.scala199
-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
15 files changed, 373 insertions, 288 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/CompilerCommand.scala b/src/compiler/scala/tools/nsc/CompilerCommand.scala
index 2e3bc32007..1c0fd8b9de 100644
--- a/src/compiler/scala/tools/nsc/CompilerCommand.scala
+++ b/src/compiler/scala/tools/nsc/CompilerCommand.scala
@@ -17,9 +17,11 @@ class CompilerCommand(arguments: List[String], val settings: Settings) {
/** file extensions of files that the compiler can process */
lazy val fileEndings = Properties.fileEndings
- val (ok, files) =
+ private val processArgumentsResult =
if (shouldProcessArguments) processArguments
else (true, Nil)
+ def ok = processArgumentsResult._1
+ def files = processArgumentsResult._2
/** The name of the command */
def cmdName = "scalac"
diff --git a/src/compiler/scala/tools/nsc/GenericRunnerCommand.scala b/src/compiler/scala/tools/nsc/GenericRunnerCommand.scala
index c8e86a752d..68689a4109 100644
--- a/src/compiler/scala/tools/nsc/GenericRunnerCommand.scala
+++ b/src/compiler/scala/tools/nsc/GenericRunnerCommand.scala
@@ -27,8 +27,8 @@ extends CompilerCommand(args, settings) {
/** thingToRun: What to run. If it is None, then the interpreter should be started
* arguments: Arguments to pass to the object or script to run
*/
- val (thingToRun, arguments) = {
- val (_, remaining) = settings.processArguments(args, false)
+ val (_ok, thingToRun, arguments) = {
+ val (ok, remaining) = settings.processArguments(args, false)
val mainClass =
if (settings.jarfile.isDefault) None
else new io.Jar(settings.jarfile.value).mainClass
@@ -37,17 +37,23 @@ extends CompilerCommand(args, settings) {
// Otherwise, the first remaining argument is the program to run, and the rest
// of the arguments go to it. If remaining is empty, we'll start the repl.
mainClass match {
- case Some(name) => (Some(name), remaining)
- case _ => (remaining.headOption, remaining drop 1)
+ case Some(name) => (ok, Some(name), remaining)
+ case _ => (ok, remaining.headOption, remaining drop 1)
}
}
+ override def ok = _ok
- override def usageMsg ="""
+ private def interpolate(s: String) = s.trim.replaceAll("@cmd@", cmdName).replaceAll("@compileCmd@", compCmdName) + "\n"
+
+ def shortUsageMsg = interpolate("""
Usage: @cmd@ <options> [<script|class|object> <arguments>]
or @cmd@ <options> [-jar <jarfile> <arguments>]
+ or @cmd@ -help
-All options to @compileCmd@ are allowed. See @compileCmd@ -help.
+All options to @compileCmd@ are also allowed. See @compileCmd@ -help.
+ """)
+ override def usageMsg = shortUsageMsg + "\n" + interpolate("""
The first given argument other than options to @cmd@ designates
what to run. Runnable targets are:
@@ -74,6 +80,5 @@ A file argument will be run as a scala script unless it contains only top
level classes and objects, and exactly one runnable main method. In that
case the file will be compiled and the main method invoked. This provides
a bridge between scripts and standard scala source.
-
- """.replaceAll("@cmd@", cmdName).replaceAll("@compileCmd@", compCmdName)
+ """) + "\n"
}
diff --git a/src/compiler/scala/tools/nsc/MainGenericRunner.scala b/src/compiler/scala/tools/nsc/MainGenericRunner.scala
index ad7cde1e30..e40fdea3b9 100644
--- a/src/compiler/scala/tools/nsc/MainGenericRunner.scala
+++ b/src/compiler/scala/tools/nsc/MainGenericRunner.scala
@@ -38,7 +38,7 @@ object MainGenericRunner {
import command.settings
def sampleCompiler = new Global(settings) // def so its not created unless needed
- if (!command.ok) return errorFn("%s\n%s".format(command.usageMsg, sampleCompiler.pluginOptionsHelp))
+ if (!command.ok) return errorFn("\n" + command.shortUsageMsg)
else if (settings.version.value) return errorFn("Scala code runner %s -- %s".format(versionString, copyrightString))
else if (command.shouldStopWithInfo) return errorFn(command getInfoMessage sampleCompiler)
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/interpreter/Power.scala b/src/compiler/scala/tools/nsc/interpreter/Power.scala
index 8ad799a265..6dc8cee219 100644
--- a/src/compiler/scala/tools/nsc/interpreter/Power.scala
+++ b/src/compiler/scala/tools/nsc/interpreter/Power.scala
@@ -7,6 +7,7 @@ package scala.tools.nsc
package interpreter
import scala.collection.{ mutable, immutable }
+import scala.util.matching.Regex
import scala.tools.nsc.util.{ BatchSourceFile }
import session.{ History }
@@ -17,29 +18,68 @@ class Power(repl: ILoop, intp: IMain) {
def this(intp: IMain) = this(null, intp)
val global: intp.global.type = intp.global
-
import global._
- import definitions.{ getMember, getModule, getClass => getCompilerClass }
import intp.{ beQuietDuring, interpret, parse }
- object phased extends Phased {
- val global: Power.this.global.type = Power.this.global
- }
-
- class ReplSnippet[T](val path: String, initial: T) {
- var code: String = ""
- var value: T = initial
+ abstract class SymSlurper {
+ def isKeep(sym: Symbol): Boolean
+ def isIgnore(sym: Symbol): Boolean
+ def isRecur(sym: Symbol): Boolean
+ def isFinished(): Boolean
+
+ val keep = mutable.HashSet[Symbol]()
+ val seen = mutable.HashSet[Symbol]()
+ def processed = keep.size + seen.size
+ def discarded = seen.size - keep.size
+
+ def members(x: Symbol): List[Symbol] =
+ if (x.rawInfo.isComplete) x.info.members
+ else Nil
+
+ var lastCount = -1
+ var pass = 0
+ val unseenHistory = new mutable.ListBuffer[Int]
+
+ def loop(todo: Set[Symbol]): Set[Symbol] = {
+ pass += 1
+ val (repeats, unseen) = todo partition seen
+ unseenHistory += unseen.size
+ if (opt.verbose) {
+ println("%3d %s accumulated, %s discarded. This pass: %s unseen, %s repeats".format(
+ pass, keep.size, discarded, unseen.size, repeats.size))
+ }
+ if (lastCount == processed || unseen.isEmpty || isFinished())
+ return keep.toSet
+
+ lastCount = processed
+ keep ++= (unseen filter isKeep filterNot isIgnore)
+ seen ++= unseen
+ loop(unseen filter isRecur flatMap members)
+ }
- def set(code: String) = interpret(path + ".value = " + code)
- def get: T = value
- override def toString = "intp." + path + ".value = \"" + code + "\""
+ def apply(sym: Symbol): Set[Symbol] = {
+ keep.clear()
+ seen.clear()
+ loop(Set(sym))
+ }
}
- object vars {
- private def create[T](name: String, initial: T): ReplSnippet[T] =
- new ReplSnippet[T]("power.vars." + name, initial)
+ class PackageSlurper(pkgName: String) extends SymSlurper {
+ val pkgSymbol = getCompilerModule(pkgName)
+ val modClass = pkgSymbol.moduleClass
+
+ /** Looking for dwindling returns */
+ def droppedEnough() = unseenHistory.size >= 4 && (
+ unseenHistory.takeRight(4).sliding(2) map (_.toList) forall {
+ case List(a, b) => a > b
+ }
+ )
- val symfilter = create("symfilter", (s: Symbol) => true)
+ def isRecur(sym: Symbol) = true
+ def isIgnore(sym: Symbol) = sym.isAnonOrRefinementClass || (sym.name.toString contains "$mc")
+ def isKeep(sym: Symbol) = sym.hasTransOwner(modClass)
+ def isFinished() = droppedEnough()
+ def slurp() = apply(modClass)
}
def banner = """
@@ -56,7 +96,8 @@ class Power(repl: ILoop, intp: IMain) {
|val global: intp.global.type = intp.global
|import global._
|import definitions._
- |import power.{ phased, show, clazz, module }
+ |import power.phased
+ |import power.Implicits._
""".stripMargin
/** Starts up power mode and runs whatever is in init.
@@ -74,62 +115,94 @@ class Power(repl: ILoop, intp: IMain) {
init split '\n' foreach interpret
}
- object show {
- private def defStrings(sym: Symbol, p: Symbol => Boolean) =
- phased(sym.info.members filter p map (_.defString))
-
- private def display(sym: Symbol, p: Symbol => Boolean) =
- defStrings(sym, p) foreach println
-
- def methods[T: Manifest] = display(clazz[T], _.isMethod)
- def apply[T: Manifest] = display(clazz[T], vars.symfilter.get)
- }
-
- abstract class NameBased[T <: Name] {
- def mkName(s: String): T
- def mkSymbol(s: String): Symbol
-
- def apply[T: Manifest] = mkSymbol(manifest[T].erasure.getName)
- def tpe[T: Manifest] = apply[T].tpe
- def members[T: Manifest] = tpe[T].members
- def member[T: Manifest](name: Name) = getMember(apply[T], name)
- def vmember[T: Manifest](name: String) = member[T](newTermName(name))
- def tmember[T: Manifest](name: String) = member[T](newTypeName(name))
- }
private def missingWrap(op: => Symbol): Symbol =
try op
catch { case _: MissingRequirementError => NoSymbol }
- object clazz extends NameBased[TypeName] {
- def mkName(s: String) = newTypeName(s)
- def mkSymbol(s: String): Symbol = missingWrap(getCompilerClass(s))
+ private def getCompilerClass(name: String) = missingWrap(definitions.getClass(name))
+ private def getCompilerModule(name: String) = missingWrap(definitions.getModule(name))
+
+ object InternalInfo {
+ implicit def apply[T: Manifest] : InternalInfo[T] = new InternalInfo[T](None)
}
- object module extends NameBased[TermName] {
- def mkName(s: String) = newTermName(s)
- def mkSymbol(s: String): Symbol = missingWrap(getModule(s))
+ /** Todo: translate manifest type arguments into applied types. */
+ class InternalInfo[T: Manifest](value: Option[T] = None) {
+ def companion = symbol.companionSymbol
+ def info = symbol.info
+ def module = symbol.moduleClass
+ def owner = symbol.owner
+ def symDef = symbol.defString
+ def symName = symbol.name
+ def tpe = symbol.tpe
+
+ def declares = members filter (_.owner == symbol)
+ def inherits = members filterNot (_.owner == symbol)
+ def types = members filter (_.name.isTypeName)
+ def methods = members filter (_.isMethod)
+ def overrides = declares filter (_.isOverride)
+
+ def erasure = manifest[T].erasure
+ def symbol = getCompilerClass(erasure.getName)
+ def members = tpe.members
+ def bts = info.baseTypeSeq.toList
+ def btsmap = bts map (x => (x, x.decls.toList)) toMap
+ def pkgName = erasure.getPackage.getName
+ def pkg = getCompilerModule(pkgName)
+ def pkgmates = pkg.tpe.members
+ def pkgslurp = new PackageSlurper(pkgName) slurp()
+
+ def ? = this
+
+ def whoHas(name: String) = bts filter (_.decls.toList exists (_.name.toString == name))
+ def <:<[U: Manifest](other: U) = tpe <:< InternalInfo[U].tpe
+ def lub[U: Manifest](other: U) = global.lub(List(tpe, InternalInfo[U].tpe))
+ def glb[U: Manifest](other: U) = global.glb(List(tpe, InternalInfo[U].tpe))
+
+ def shortClass = erasure.getName split "[$.]" last
+ override def toString = value match {
+ case Some(x) => "%s (%s)".format(x, shortClass)
+ case _ => erasure.getName
+ }
}
- def mkContext(code: String = "") = analyzer.rootContext(mkUnit(code))
- def mkAlias(name: String, what: String) = interpret("type %s = %s".format(name, what))
- def mkSourceFile(code: String) = new BatchSourceFile("<console>", code)
- def mkUnit(code: String) = new CompilationUnit(mkSourceFile(code))
-
- def mkTree(code: String): Tree = mkTrees(code).headOption getOrElse EmptyTree
- def mkTrees(code: String): List[Tree] = parse(code) getOrElse Nil
- def mkTypedTrees(code: String*): List[Tree] = {
- class TyperRun extends Run {
- override def stopPhase(name: String) = name == "superaccessors"
+ trait PCFormatter extends (Any => List[String]) {
+ def apply(x: Any): List[String]
+ def show(x: Any): Unit = grep(x, _ => true)
+ def grep(x: Any, p: String => Boolean): Unit = apply(x) filter p foreach println
+ }
+ class PrintingConvenience[T](value: T)(implicit fmt: PCFormatter) {
+ def > : Unit = >(_ => true)
+ def >(s: String): Unit = >(_ contains s)
+ def >(r: Regex): Unit = >(_ matches r.pattern.toString)
+ def >(p: String => Boolean): Unit = fmt.grep(value, p)
+ }
+ object Implicits {
+ implicit lazy val powerNameOrdering: Ordering[Name] = Ordering[String] on (_.toString)
+ implicit object powerSymbolOrdering extends Ordering[Symbol] {
+ def compare(s1: Symbol, s2: Symbol) =
+ if (s1 eq s2) 0
+ else if (s1 isLess s2) -1
+ else 1
+ }
+ implicit def replPrinting[T](x: T)(implicit fmt: PCFormatter) = new PrintingConvenience[T](x)
+ implicit def replInternalInfo[T: Manifest](x: T): InternalInfo[T] = new InternalInfo[T](Some(x))
+ implicit object ReplDefaultFormatter extends PCFormatter {
+ def apply(x: Any): List[String] = x match {
+ case Tuple2(k, v) => apply(k) + " -> " + apply(v)
+ case xs: Traversable[_] => (xs.toList flatMap apply).sorted.distinct
+ case x => List("" + x)
+ }
}
+ }
- reporter.reset()
- val run = new TyperRun
- run compileSources (code.toList.zipWithIndex map {
- case (s, i) => new BatchSourceFile("<console %d>".format(i), s)
- })
- run.units.toList map (_.body)
+ object phased extends Phased {
+ val global: Power.this.global.type = Power.this.global
}
- def mkTypedTree(code: String) = mkTypedTrees(code).head
- def mkType(id: String): Type = intp.typeOfExpression(id) getOrElse NoType
+ def context(code: String) = analyzer.rootContext(unit(code))
+ def source(code: String) = new BatchSourceFile("<console>", code)
+ def unit(code: String) = new CompilationUnit(source(code))
+ def trees(code: String): List[Tree] = parse(code) getOrElse Nil
+ def typeOf(id: String): Type = intp.typeOfExpression(id) getOrElse NoType
override def toString = """
|** Power mode status **
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()
}
}