From d52d4138670465c4e51593c69e7e7a460024e58e Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Tue, 17 Nov 2020 12:28:56 -0800 Subject: [PATCH] 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. --- .../src/main/scala/sbt/internal/util/Terminal.scala | 5 ++++- main/src/main/scala/sbt/internal/Continuous.scala | 11 ++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/internal/util-logging/src/main/scala/sbt/internal/util/Terminal.scala b/internal/util-logging/src/main/scala/sbt/internal/util/Terminal.scala index 816276ae5..762541cc4 100644 --- a/internal/util-logging/src/main/scala/sbt/internal/util/Terminal.scala +++ b/internal/util-logging/src/main/scala/sbt/internal/util/Terminal.scala @@ -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)) diff --git a/main/src/main/scala/sbt/internal/Continuous.scala b/main/src/main/scala/sbt/internal/Continuous.scala index 1a6790c76..b416446f7 100644 --- a/main/src/main/scala/sbt/internal/Continuous.scala +++ b/main/src/main/scala/sbt/internal/Continuous.scala @@ -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)