Fix watch for dumb terminals

On terminals with virtual io disabled, we'd spin up a thread for each
watch iteration that performed a blocking read from the terminal input
stream. This thread could not be joined which would cause the triggered
execution to be delayed by 1 second while sbt blocked trying to join
that thread. It also meant that input probably didn't work correctly
since the user would end up with many threads polling from system in.
The fix to this problem is to poll the terminal input stream if it is
unsafe to do a blocking read, which is the case for dumb terminals or if
virtual io is disabled.
This commit is contained in:
Ethan Atkins 2020-11-17 12:28:56 -08:00
parent c52e9916e2
commit d52d413867
2 changed files with 14 additions and 2 deletions

View File

@ -327,6 +327,9 @@ object Terminal {
if (isColorEnabled && doRed) Console.RED + str + Console.RESET
else str
private[this] def hasVirtualIO = System.getProperty("sbt.io.virtual", "") == "true" || !isCI
private[sbt] def canPollSystemIn: Boolean = hasConsole && !isDumbTerminal && hasVirtualIO
/**
*
* @param isServer toggles whether or not this is a server of client process
@ -337,7 +340,7 @@ object Terminal {
private[sbt] def withStreams[T](isServer: Boolean)(f: => T): T = {
// In ci environments, don't touch the io streams unless run with -Dsbt.io.virtual=true
if (hasConsole && !isDumbTerminal) consoleTerminalHolder.set(newConsoleTerminal())
if (System.getProperty("sbt.io.virtual", "") == "true" || !isCI) {
if (hasVirtualIO) {
hasProgress.set(isServer && isAnsiSupported)
activeTerminal.set(consoleTerminalHolder.get)
try withOut(withIn(f))

View File

@ -783,11 +783,20 @@ private[sbt] object Continuous extends DeprecatedContinuous {
}
executor => {
val interrupted = new AtomicBoolean(false)
@tailrec def read(): Int = {
if (terminal.name.startsWith("network")) terminal.inputStream.read
else if (Terminal.canPollSystemIn || terminal.inputStream.available > 0)
terminal.inputStream.read
else {
Thread.sleep(50)
read()
}
}
@tailrec def impl(): Option[Watch.Action] = {
val action =
try {
interrupted.set(false)
terminal.inputStream.read match {
read() match {
case -1 => throw new InterruptedException
case 3 => Watch.CancelWatch // ctrl+c on windows
case byte => inputHandler(byte.toChar.toString)