Avoid leaking sbt processes

On windows, it is sometimes possible to leak an sbt process if two
processes are started simultaneously by a remote client at the same
time. When this happens, the second process is unable to create a
server because of the first process and it also has no io streams
because the the client detaches its streams. We can detect this
in the shell command and prevent the process from persisting as a
zombie.
This commit is contained in:
Ethan Atkins 2020-06-27 11:00:57 -07:00
parent ae2899baae
commit 261084bbb2
3 changed files with 27 additions and 16 deletions

View File

@ -538,6 +538,7 @@ object Terminal {
case _ => None
}
}
private[sbt] def startedByRemoteClient = props.isDefined
/**
* Creates an instance of [[Terminal]] that delegates most of its methods to an underlying

View File

@ -1019,22 +1019,31 @@ object BuiltinCommands {
val exchange = StandardMain.exchange
val welcomeState = displayWelcomeBanner(s0)
val s1 = exchange run welcomeState
exchange prompt ConsolePromptEvent(s0)
val minGCInterval = Project
.extract(s1)
.getOpt(Keys.minForcegcInterval)
.getOrElse(GCUtil.defaultMinForcegcInterval)
val exec: Exec = getExec(s1, minGCInterval)
val newState = s1
.copy(
onFailure = Some(Exec(Shell, None)),
remainingCommands = exec +: Exec(Shell, None) +: s1.remainingCommands
)
.setInteractive(true)
val res =
if (exec.commandLine.trim.isEmpty) newState
else newState.clearGlobalLog
res
/*
* It is possible for sbt processes to leak if two are started simultaneously
* by a remote client and only one is able to start a server. This seems to
* happen primarily on windows.
*/
if (Terminal.startedByRemoteClient && !exchange.hasServer) {
Exec("shutdown", None) +: s1
} else {
exchange prompt ConsolePromptEvent(s0)
val minGCInterval = Project
.extract(s1)
.getOpt(Keys.minForcegcInterval)
.getOrElse(GCUtil.defaultMinForcegcInterval)
val exec: Exec = getExec(s1, minGCInterval)
val newState = s1
.copy(
onFailure = Some(Exec(Shell, None)),
remainingCommands = exec +: Exec(Shell, None) +: s1.remainingCommands
)
.setInteractive(true)
val res =
if (exec.commandLine.trim.isEmpty) newState
else newState.clearGlobalLog
res
}
}
def startServer: Command =

View File

@ -53,6 +53,7 @@ private[sbt] final class CommandExchange {
private val nextChannelId: AtomicInteger = new AtomicInteger(0)
private[this] val lastState = new AtomicReference[State]
private[this] val currentExecRef = new AtomicReference[Exec]
private[sbt] def hasServer = server.isDefined
def channels: List[CommandChannel] = channelBuffer.toList