mirror of https://github.com/sbt/sbt.git
[2.x] feat: Notify sbtn client when command is queued (#8568)
Fixes #8356 **Problem** When `sbtn` sends a command while another long-running task (like `console`) is already executing, the client silently blocks with no indication that the command is waiting in a queue. **Solution** When a new command arrives via the network channel and another command is currently running, the server now sends an `ExecStatusEvent` notification with status `"Queued"` to the client. The client displays a message like: ``` [info] waiting for: console ```
This commit is contained in:
parent
4d7e0633a8
commit
c832fad7b5
|
|
@ -691,6 +691,14 @@ class NetworkClient(
|
||||||
}
|
}
|
||||||
case (`Shutdown`, Some(_)) => Vector.empty
|
case (`Shutdown`, Some(_)) => Vector.empty
|
||||||
case (msg, _) if msg.startsWith("build/") => Vector.empty
|
case (msg, _) if msg.startsWith("build/") => Vector.empty
|
||||||
|
case ("sbt/exec", Some(json)) =>
|
||||||
|
import sbt.protocol.codec.JsonProtocol.given
|
||||||
|
Converter.fromJson[ExecStatusEvent](json) match {
|
||||||
|
case Success(event) if event.status == "Queued" =>
|
||||||
|
event.message.foreach(m => errorStream.println(s"[info] $m"))
|
||||||
|
Vector.empty
|
||||||
|
case _ => Vector.empty
|
||||||
|
}
|
||||||
case _ =>
|
case _ =>
|
||||||
Vector(
|
Vector(
|
||||||
(
|
(
|
||||||
|
|
|
||||||
|
|
@ -131,10 +131,15 @@ final class NetworkChannel(
|
||||||
def jsonRpcNotify[A: JsonFormat](method: String, params: A): Unit =
|
def jsonRpcNotify[A: JsonFormat](method: String, params: A): Unit =
|
||||||
self.jsonRpcNotify(method, params)
|
self.jsonRpcNotify(method, params)
|
||||||
|
|
||||||
def appendExec(commandLine: String, execId: Option[String]): Boolean =
|
def appendExec(commandLine: String, execId: Option[String]): Boolean = {
|
||||||
|
self.notifyIfQueued(execId)
|
||||||
self.appendExec(commandLine, execId)
|
self.appendExec(commandLine, execId)
|
||||||
|
}
|
||||||
|
|
||||||
def appendExec(exec: Exec): Boolean = self.append(exec)
|
def appendExec(exec: Exec): Boolean = {
|
||||||
|
self.notifyIfQueued(exec.execId)
|
||||||
|
self.append(exec)
|
||||||
|
}
|
||||||
|
|
||||||
def log: Logger = self.log
|
def log: Logger = self.log
|
||||||
def name: String = self.name
|
def name: String = self.name
|
||||||
|
|
@ -166,6 +171,24 @@ final class NetworkChannel(
|
||||||
|
|
||||||
protected def authOptions: Set[ServerAuthentication] = auth
|
protected def authOptions: Set[ServerAuthentication] = auth
|
||||||
|
|
||||||
|
private def notifyIfQueued(execId: Option[String]): Unit =
|
||||||
|
StandardMain.exchange.currentExec match {
|
||||||
|
case Some(currentExec) =>
|
||||||
|
val event = ExecStatusEvent(
|
||||||
|
"Queued",
|
||||||
|
Some(name),
|
||||||
|
execId,
|
||||||
|
Vector.empty,
|
||||||
|
None,
|
||||||
|
currentExec.commandLine match {
|
||||||
|
case cmd if cmd.length > 50 => Some(s"waiting for: ${cmd.take(50)}...")
|
||||||
|
case cmd => Some(s"waiting for: $cmd")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
jsonRpcNotify("sbt/exec", event)
|
||||||
|
case None =>
|
||||||
|
}
|
||||||
|
|
||||||
override def mkUIThread: (State, CommandChannel) => UITask = (state, command) => {
|
override def mkUIThread: (State, CommandChannel) => UITask = (state, command) => {
|
||||||
if (interactive.get || ContinuousCommands.isInWatch(state, this)) mkUIThreadImpl(state, command)
|
if (interactive.get || ContinuousCommands.isInWatch(state, this)) mkUIThreadImpl(state, command)
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
|
||||||
|
commands += Command.command("slowTask") { state =>
|
||||||
|
Thread.sleep(5000)
|
||||||
|
state
|
||||||
|
}
|
||||||
|
|
||||||
|
commands += Command.command("quickTask") { state =>
|
||||||
|
state
|
||||||
|
}
|
||||||
|
|
||||||
|
Global / cancelable := true
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
* sbt
|
||||||
|
* Copyright 2011 - 2018, Lightbend, Inc.
|
||||||
|
* Copyright 2008 - 2010, Mark Harrah
|
||||||
|
* Licensed under Apache License 2.0 (see LICENSE)
|
||||||
|
*/
|
||||||
|
|
||||||
|
package testpkg
|
||||||
|
|
||||||
|
import scala.concurrent.duration.*
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
|
||||||
|
class QueuedNotificationTest extends AbstractServerTest {
|
||||||
|
override val testDirectory: String = "queued"
|
||||||
|
val currentID = new AtomicInteger(2000)
|
||||||
|
|
||||||
|
test("send Queued notification when command is queued behind another") {
|
||||||
|
val slowId = currentID.getAndIncrement()
|
||||||
|
svr.sendJsonRpc(
|
||||||
|
s"""{ "jsonrpc": "2.0", "id": $slowId, "method": "sbt/exec", "params": { "commandLine": "slowTask" } }"""
|
||||||
|
)
|
||||||
|
|
||||||
|
Thread.sleep(500)
|
||||||
|
|
||||||
|
val quickId = currentID.getAndIncrement()
|
||||||
|
svr.sendJsonRpc(
|
||||||
|
s"""{ "jsonrpc": "2.0", "id": $quickId, "method": "sbt/exec", "params": { "commandLine": "quickTask" } }"""
|
||||||
|
)
|
||||||
|
|
||||||
|
assert(svr.waitForString(10.seconds) { s =>
|
||||||
|
s.contains(""""status":"Queued"""") && s.contains(""""waiting for: slowTask"""")
|
||||||
|
})
|
||||||
|
|
||||||
|
assert(svr.waitForString(10.seconds) { s =>
|
||||||
|
s.contains(s""""id":$slowId""") && s.contains(""""status":"Done"""")
|
||||||
|
})
|
||||||
|
|
||||||
|
assert(svr.waitForString(10.seconds) { s =>
|
||||||
|
s.contains(s""""id":$quickId""") && s.contains(""""status":"Done"""")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue