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 7cfed50e6..2b1700572 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 @@ -126,11 +126,12 @@ object ConsoleAppender { def out: ConsoleOut } private[sbt] object Properties { - def from(terminal: Terminal): Properties = new Properties { - override def isAnsiSupported: Boolean = terminal.isAnsiSupported - override def isColorEnabled: Boolean = terminal.isColorEnabled - override def out = ConsoleOut.terminalOut(terminal) - } + def from(terminal: Terminal): Properties = + from(ConsoleOut.terminalOut(terminal), terminal.isAnsiSupported, terminal.isColorEnabled) + + def safelyFrom(terminal: Terminal): Properties = + from(ConsoleOut.safeTerminalOut(terminal), terminal.isAnsiSupported, terminal.isColorEnabled) + def from(o: ConsoleOut, ansi: Boolean, color: Boolean): Properties = new Properties { override def isAnsiSupported: Boolean = ansi override def isColorEnabled: Boolean = color @@ -246,6 +247,18 @@ object ConsoleAppender { new ConsoleAppender(name, Properties.from(terminal), noSuppressedMessage) } + /** + * A new `ConsoleAppender` identified by `name`, and that writes to `terminal`. + * Printing to this Appender will not throw if the Terminal has been closed. + * + * @param name An identifier for the `ConsoleAppender`. + * @param terminal The terminal to which this appender corresponds + * @return A new `ConsoleAppender` that writes to `terminal`. + */ + def safe(name: String, terminal: Terminal): Appender = { + new ConsoleAppender(name, Properties.safelyFrom(terminal), noSuppressedMessage) + } + /** * A new `ConsoleAppender` identified by `name`, and that writes to `out`. * diff --git a/internal/util-logging/src/main/scala/sbt/internal/util/ConsoleOut.scala b/internal/util-logging/src/main/scala/sbt/internal/util/ConsoleOut.scala index b946a0cc8..0f6a089bf 100644 --- a/internal/util-logging/src/main/scala/sbt/internal/util/ConsoleOut.scala +++ b/internal/util-logging/src/main/scala/sbt/internal/util/ConsoleOut.scala @@ -8,6 +8,7 @@ package sbt.internal.util import java.io.{ BufferedWriter, PrintStream, PrintWriter } +import java.nio.channels.ClosedChannelException import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.atomic.AtomicReference @@ -90,6 +91,26 @@ object ConsoleOut { override def toString: String = s"TerminalOut" } + /** Same as terminalOut but it catches and ignores the ClosedChannelException + */ + def safeTerminalOut(terminal: Terminal): ConsoleOut = { + val out = terminalOut(terminal) + new ConsoleOut { + override val lockObject: AnyRef = terminal + override def print(s: String): Unit = catchException(out.print(s)) + override def println(s: String): Unit = catchException(out.println(s)) + override def println(): Unit = catchException(out.println()) + override def flush(): Unit = catchException(out.flush) + override def toString: String = s"SafeTerminalOut($terminal)" + private def catchException(f: => Unit): Unit = { + try f + catch { + case _: ClosedChannelException => () + } + } + } + } + private[this] val consoleOutPerTerminal = new ConcurrentHashMap[Terminal, ConsoleOut] def terminalOut(terminal: Terminal): ConsoleOut = consoleOutPerTerminal.get(terminal) match { case null => diff --git a/main/src/main/scala/sbt/internal/LogManager.scala b/main/src/main/scala/sbt/internal/LogManager.scala index 8612c692b..9feb065d1 100644 --- a/main/src/main/scala/sbt/internal/LogManager.scala +++ b/main/src/main/scala/sbt/internal/LogManager.scala @@ -149,7 +149,7 @@ object LogManager { task: ScopedKey[_], context: LoggerContext ): ManagedLogger = { - val console = ConsoleAppender("bg-" + ConsoleAppender.generateName(), ITerminal.current) + val console = ConsoleAppender.safe("bg-" + ConsoleAppender.generateName(), ITerminal.current) LogManager.backgroundLog(data, state, task, console, relay(()), context) } }