Fork server if it's not running

Fixes https://github.com/sbt/sbt/issues/3508

This forks an instance of sbt in the background when it's not running already.

```
$ time sbt -client compile
Getting org.scala-sbt sbt 1.2.0-SNAPSHOT  (this may take some time)...
:: retrieving :: org.scala-sbt#boot-app
	confs: [default]
	79 artifacts copied, 0 already retrieved (28214kB/130ms)
[info] entering *experimental* thin client - BEEP WHIRR
[info] server was not detected. starting an instance
[info] waiting for the server...
[info] waiting for the server...
[info] server found
> compile
[success] completed
sbt -client compile  9.25s user 2.39s system 33% cpu 34.893 total
$ time sbt -client compile
[info] entering *experimental* thin client - BEEP WHIRR
> compile
[success] completed
sbt -client compile  3.55s user 1.68s system 107% cpu 4.889 total
```
This commit is contained in:
Eugene Yokota 2018-06-25 03:02:26 -04:00
parent 3eb76125e0
commit f3038167a5
3 changed files with 53 additions and 8 deletions

View File

@ -297,7 +297,7 @@ object BasicCommands {
case e :: Nil if e.commandLine == "shell" => Nil
case xs => xs map (_.commandLine)
})
NetworkClient.run(s0.configuration.baseDirectory, arguments)
NetworkClient.run(s0.configuration, arguments)
"exit" :: s0.copy(remainingCommands = Nil)
}

View File

@ -9,12 +9,13 @@ package sbt
package internal
package client
import java.io.IOException
import java.io.{ File, IOException }
import java.util.UUID
import java.util.concurrent.atomic.{ AtomicBoolean, AtomicReference }
import scala.collection.mutable.ListBuffer
import scala.util.control.NonFatal
import scala.util.{ Success, Failure }
import scala.sys.process.{ BasicIO, Process, ProcessLogger }
import sbt.protocol._
import sbt.internal.protocol._
import sbt.internal.langserver.{ LogMessageParams, MessageType, PublishDiagnosticsParams }
@ -24,7 +25,7 @@ import sbt.io.syntax._
import sbt.io.IO
import sjsonnew.support.scalajson.unsafe.Converter
class NetworkClient(baseDirectory: File, arguments: List[String]) { self =>
class NetworkClient(configuration: xsbti.AppConfiguration, arguments: List[String]) { self =>
private val channelName = new AtomicReference("_")
private val status = new AtomicReference("Ready")
private val lock: AnyRef = new AnyRef {}
@ -32,6 +33,7 @@ class NetworkClient(baseDirectory: File, arguments: List[String]) { self =>
private val pendingExecIds = ListBuffer.empty[String]
private val console = ConsoleAppender("thin1")
private def baseDirectory: File = configuration.baseDirectory
lazy val connection = init()
@ -40,7 +42,9 @@ class NetworkClient(baseDirectory: File, arguments: List[String]) { self =>
// Open server connection based on the portfile
def init(): ServerConnection = {
val portfile = baseDirectory / "project" / "target" / "active.json"
if (!portfile.exists) sys.error("server does not seem to be running.")
if (!portfile.exists) {
forkServer(portfile)
}
val (sk, tkn) = ClientSocket.socket(portfile)
val conn = new ServerConnection(sk) {
override def onNotification(msg: JsonRpcNotificationMessage): Unit = self.onNotification(msg)
@ -57,6 +61,42 @@ class NetworkClient(baseDirectory: File, arguments: List[String]) { self =>
conn
}
/**
* Forks another instance of sbt in the background.
* This instance must be shutdown explicitly via `sbt -client shutdown`
*/
def forkServer(portfile: File): Unit = {
console.appendLog(Level.Info, "server was not detected. starting an instance")
val args = List[String]()
val launchOpts = List("-Xms2048M", "-Xmx2048M", "-Xss2M")
val launcherJarString = sys.props.get("java.class.path") match {
case Some(cp) =>
cp.split(File.pathSeparator)
.toList
.headOption
.getOrElse(sys.error("launcher JAR classpath not found"))
case _ => sys.error("property java.class.path expected")
}
val cmd = "java" :: launchOpts ::: "-jar" :: launcherJarString :: args
// val cmd = "sbt"
val io = BasicIO(false, ProcessLogger(_ => ()))
val _ = Process(cmd, baseDirectory).run(io)
def waitForPortfile(n: Int): Unit =
if (portfile.exists) {
console.appendLog(Level.Info, "server found")
} else {
if (n <= 0) sys.error(s"timeout. $portfile is not found.")
else {
Thread.sleep(1000)
if ((n - 1) % 10 == 0) {
console.appendLog(Level.Info, "waiting for the server...")
}
waitForPortfile(n - 1)
}
}
waitForPortfile(90)
}
/** Called on the response for a returning message. */
def onReturningReponse(msg: JsonRpcResponseMessage): Unit = {
def printResponse(): Unit = {
@ -152,6 +192,7 @@ class NetworkClient(baseDirectory: File, arguments: List[String]) { self =>
def start(): Unit = {
console.appendLog(Level.Info, "entering *experimental* thin client - BEEP WHIRR")
val _ = connection
val userCommands = arguments filterNot { cmd =>
cmd.startsWith("-")
}
@ -162,7 +203,9 @@ class NetworkClient(baseDirectory: File, arguments: List[String]) { self =>
def batchExecute(userCommands: List[String]): Unit = {
userCommands foreach { cmd =>
println("> " + cmd)
val execId = sendExecCommand(cmd)
val execId =
if (cmd == "shutdown") sendExecCommand("exit")
else sendExecCommand(cmd)
while (pendingExecIds contains execId) {
Thread.sleep(100)
}
@ -176,6 +219,8 @@ class NetworkClient(baseDirectory: File, arguments: List[String]) { self =>
case Some("shutdown") =>
// `sbt -client shutdown` shuts down the server
sendExecCommand("exit")
Thread.sleep(100)
running.set(false)
case Some("exit") =>
running.set(false)
case Some(s) if s.trim.nonEmpty =>
@ -213,9 +258,9 @@ class NetworkClient(baseDirectory: File, arguments: List[String]) { self =>
}
object NetworkClient {
def run(baseDirectory: File, arguments: List[String]): Unit =
def run(configuration: xsbti.AppConfiguration, arguments: List[String]): Unit =
try {
new NetworkClient(baseDirectory, arguments)
new NetworkClient(configuration, arguments)
()
} catch {
case NonFatal(e) => println(e.getMessage)

View File

@ -78,7 +78,7 @@ final class xMain extends xsbti.AppMain {
val args = userCommands.toList filterNot { cmd =>
(cmd == DashClient) || (cmd == DashDashClient)
}
NetworkClient.run(configuration.baseDirectory, args)
NetworkClient.run(configuration, args)
Exit(0)
} else {
val state = StandardMain.initialState(