Add remote cancellation support

This commit makes it possible for a remote client to cancel a running
task initiated by a different client by typing `cancel` into the shell.
It can be useful if the remote client has run something blocking like
console.

The console task can't safely be interrupted, so instead we write some
newlines filed by ctrl+d to exit the console.
This commit is contained in:
Ethan Atkins 2020-06-24 19:20:54 -07:00
parent ba345dd797
commit ab362397ba
3 changed files with 44 additions and 0 deletions

View File

@ -658,6 +658,7 @@ private[sbt] object ProgressState {
}
} else {
pe.command.toSeq.flatMap { cmd =>
val tail = if (isWatch) Nil else "enter 'cancel' to stop evaluation" :: Nil
s"sbt server is running '$cmd'" :: tail
}
}

View File

@ -350,6 +350,15 @@ private[sbt] final class CommandExchange {
commandQueue.add(exit)
()
}
private[this] def cancel(e: Exec): Unit = {
if (e.commandLine.startsWith("console")) {
val terminal = Terminal.get
terminal.write(13, 13, 13, 4)
terminal.printStream.println("\nconsole session killed by remote sbt client")
} else {
Util.ignoreResult(NetworkChannel.cancel(e.execId, e.execId.getOrElse("0")))
}
}
private[this] class MaintenanceThread
extends Thread("sbt-command-exchange-maintenance")

View File

@ -698,6 +698,40 @@ object NetworkChannel {
case object SingleLine extends ChannelState
case object InHeader extends ChannelState
case object InBody extends ChannelState
private[sbt] def cancel(
execID: Option[String],
id: String
): Either[String, String] = {
Option(EvaluateTask.currentlyRunningEngine.get) match {
case Some((state, runningEngine)) =>
val runningExecId = state.currentExecId.getOrElse("")
def checkId(): Boolean = {
if (runningExecId.startsWith("\u2668")) {
(
Try { id.toLong }.toOption,
Try { runningExecId.substring(1).toLong }.toOption
) match {
case (Some(id), Some(eid)) => id == eid
case _ => false
}
} else runningExecId == id
}
// direct comparison on strings and
// remove hotspring unicode added character for numbers
if (checkId) {
runningEngine.cancelAndShutdown()
Right(runningExecId)
} else {
Left("Task ID not matched")
}
case None =>
Left("No tasks under execution")
}
}
private[sbt] val disconnect: Command =
Command.arb { s =>