From 64bf50cd08384ab4050e6c6300804029c93fed08 Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Tue, 18 Oct 2011 22:43:25 -0400 Subject: [PATCH] task execution interruptible using ctrl+c. fixes #228,#229 - interrupts task execution only - no further tasks scheduled - existing tasks interrupted - a task must terminate any other started threads when interrupted - set cancelable to true to enable - currently, 'run' properly terminates if the application properly terminates when interrupted - 'console' does not, 'test' depends on the test framework - also bundled: set connectInput to true to connect standard input to forked run --- util/collection/Signal.scala | 43 ++++++++++++++++++++++++++++++++++ util/process/Process.scala | 2 ++ util/process/ProcessImpl.scala | 8 +++---- 3 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 util/collection/Signal.scala diff --git a/util/collection/Signal.scala b/util/collection/Signal.scala new file mode 100644 index 000000000..09756249d --- /dev/null +++ b/util/collection/Signal.scala @@ -0,0 +1,43 @@ +package sbt + +object Signals +{ + def withHandler[T](handler: () => Unit)(action: () => T): T = + { + val result = + try + { + val signals = new Signals0 + signals.withHandler(handler)(action) + } + catch { case e: LinkageError => Right(action()) } + + result match { + case Left(e) => throw e + case Right(v) => v + } + } +} + +// Must only be referenced using a +// try { } catch { case e: LinkageError => ... } +// block to +private final class Signals0 +{ + // returns a LinkageError in `action` as Left(t) in order to avoid it being + // incorrectly swallowed as missing Signal/SignalHandler + def withHandler[T](handler: () => Unit)(action: () => T): Either[Throwable, T] = + { + import sun.misc.{Signal,SignalHandler} + val intSignal = new Signal("INT") + val newHandler = new SignalHandler { + def handle(sig: Signal) { handler() } + } + + val oldHandler = Signal.handle(intSignal, newHandler) + + try Right(action()) + catch { case e: LinkageError => Left(e) } + finally Signal.handle(intSignal, oldHandler) + } +} \ No newline at end of file diff --git a/util/process/Process.scala b/util/process/Process.scala index 2dd70484c..5a1f46b4a 100644 --- a/util/process/Process.scala +++ b/util/process/Process.scala @@ -166,6 +166,8 @@ trait ProcessBuilder extends SourcePartialBuilder with SinkPartialBuilder * The newly started process reads from standard input of the current process if `connectInput` is true.*/ def run(log: ProcessLogger, connectInput: Boolean): Process + def runBuffered(log: ProcessLogger, connectInput: Boolean): Process + /** Constructs a command that runs this command first and then `other` if this command succeeds.*/ def #&& (other: ProcessBuilder): ProcessBuilder /** Constructs a command that runs this command first and then `other` if this command does not succeed.*/ diff --git a/util/process/ProcessImpl.scala b/util/process/ProcessImpl.scala index c20b23f20..69191d054 100644 --- a/util/process/ProcessImpl.scala +++ b/util/process/ProcessImpl.scala @@ -159,10 +159,10 @@ private abstract class AbstractProcessBuilder extends ProcessBuilder with SinkPa def ! = run(false).exitValue() def !< = run(true).exitValue() - def !(log: ProcessLogger) = runBuffered(log, false) - def !<(log: ProcessLogger) = runBuffered(log, true) - private[this] def runBuffered(log: ProcessLogger, connectInput: Boolean) = - log.buffer { run(log, connectInput).exitValue() } + def !(log: ProcessLogger) = runBuffered(log, false).exitValue() + def !<(log: ProcessLogger) = runBuffered(log, true).exitValue() + def runBuffered(log: ProcessLogger, connectInput: Boolean) = + log.buffer { run(log, connectInput) } def !(io: ProcessIO) = run(io).exitValue() def canPipeTo = false