Kill external processes on sigint

On linux and mac, entering ctrl+c will automatically kill any forked
processes that were created by the sbt server because sigint is
automatically forwarded to the child process. This is not the case on
windows where it is necessary to forcibly kill these processes.
This commit is contained in:
Ethan Atkins 2020-08-22 14:35:00 -07:00
parent 8fe7e33a31
commit d3f8cc8161
4 changed files with 48 additions and 2 deletions

View File

@ -0,0 +1,33 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.util
import java.util.concurrent.ConcurrentHashMap
import scala.sys.process.Process
/**
* Manages forked processes created by sbt. Any process registered
* with RunningProcesses can be killed with the killAll method. In
* particular, this can be used in a signal handler to kill these
* processes when the user inputs ctrl+c.
*/
private[sbt] object RunningProcesses {
val active = ConcurrentHashMap.newKeySet[Process]
def add(process: Process): Unit = active.synchronized {
active.add(process)
()
}
def remove(process: Process): Unit = active.synchronized {
active.remove(process)
()
}
def killAll(): Unit = active.synchronized {
active.forEach(_.destroy())
active.clear()
}
}

View File

@ -17,8 +17,8 @@ import sbt.io.IO
import sbt.util.Logger
import sbt.ConcurrentRestrictions.Tag
import sbt.protocol.testing._
import sbt.internal.util.ConsoleAppender
import sbt.internal.util.Util.{ AnyOps, none }
import sbt.internal.util.{ ConsoleAppender, RunningProcesses }
private[sbt] object ForkTests {
def apply(
@ -147,7 +147,13 @@ private[sbt] object ForkTests {
classOf[ForkMain].getCanonicalName,
server.getLocalPort.toString
)
val ec = Fork.java(fork, options)
val p = Fork.java.fork(fork, options)
RunningProcesses.add(p)
val ec = try p.exitValue()
finally {
if (p.isAlive) p.destroy()
RunningProcesses.remove(p)
}
val result =
if (ec != 0)
TestOutput(

View File

@ -480,6 +480,7 @@ object EvaluateTask {
def cancelAndShutdown(): Unit = {
println("")
log.warn("Canceling execution...")
RunningProcesses.killAll()
ConcurrentRestrictions.cancelAll()
shutdown()
}

View File

@ -14,12 +14,14 @@ import java.net.SocketException
import scala.sys.process.Process
import sbt.internal.scripted.{ StatementHandler, TestFailed }
import sbt.internal.util.RunningProcesses
import xsbt.IPC
final case class SbtInstance(process: Process, server: IPC.Server)
final class SbtHandler(remoteSbtCreator: RemoteSbtCreator) extends StatementHandler {
Signals.register(() => RunningProcesses.killAll())
type State = Option[SbtInstance]
@ -63,6 +65,9 @@ final class SbtHandler(remoteSbtCreator: RemoteSbtCreator) extends StatementHand
()
} catch {
case _: IOException => process.destroy()
} finally {
if (process.isAlive) process.destroy()
RunningProcesses.remove(process)
}
}
@ -76,6 +81,7 @@ final class SbtHandler(remoteSbtCreator: RemoteSbtCreator) extends StatementHand
def newRemote(server: IPC.Server): Process = {
val p = remoteSbtCreator.newRemote(server)
RunningProcesses.add(p)
try receive("Remote sbt initialization failed", server)
catch { case _: SocketException => throw new TestFailed("Remote sbt initialization failed") }
p