From ab362397ba54f357459c0d0ba996939e475489f4 Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Wed, 24 Jun 2020 19:20:54 -0700 Subject: [PATCH] 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. --- .../sbt/internal/util/ConsoleAppender.scala | 1 + .../scala/sbt/internal/CommandExchange.scala | 9 +++++ .../sbt/internal/server/NetworkChannel.scala | 34 +++++++++++++++++++ 3 files changed, 44 insertions(+) diff --git a/internal/util-logging/src/main/scala/sbt/internal/util/ConsoleAppender.scala b/internal/util-logging/src/main/scala/sbt/internal/util/ConsoleAppender.scala index 7b533ef61..fb7d6be16 100644 --- a/internal/util-logging/src/main/scala/sbt/internal/util/ConsoleAppender.scala +++ b/internal/util-logging/src/main/scala/sbt/internal/util/ConsoleAppender.scala @@ -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 } } diff --git a/main/src/main/scala/sbt/internal/CommandExchange.scala b/main/src/main/scala/sbt/internal/CommandExchange.scala index cbabbb6a5..3d47351be 100644 --- a/main/src/main/scala/sbt/internal/CommandExchange.scala +++ b/main/src/main/scala/sbt/internal/CommandExchange.scala @@ -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") diff --git a/main/src/main/scala/sbt/internal/server/NetworkChannel.scala b/main/src/main/scala/sbt/internal/server/NetworkChannel.scala index 5a50b3016..ecf4e1caa 100644 --- a/main/src/main/scala/sbt/internal/server/NetworkChannel.scala +++ b/main/src/main/scala/sbt/internal/server/NetworkChannel.scala @@ -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 =>