Shutdown background job on error

When running a main method, if the user inputs ctrl+c then the `run`
task will exit but the main method is not interrupted so it continues
running even once sbt has returned to the shell. If the main method is a
webserver, this prevents run from ever starting again on a fixed port.
To fix this, we can modify the waitForTry method to stop the job if an
exception is thrown (ctrl+c leads to an interrupted exception being
thrown by waitFor).

I rework the BackgroundJobService so that the default implementation of
waitForTry is now usable and no longer needs to be overridden. The side
effect of this change is that waitFor may now throw an exception. Within
sbt, waitFor was only used in one place and I reworked it to use
waitForTry instead. This could theoretically break a downstream user who
relied on waitFor not throwing an exception but I suspect that there
aren't many users of this api, if any at all.
This commit is contained in:
Ethan Atkins 2019-11-16 11:35:56 -08:00
parent 181bfe8a46
commit 8d26bc73b4
3 changed files with 23 additions and 26 deletions

View File

@ -8,11 +8,14 @@
package sbt
import java.io.Closeable
import sbt.util.Logger
import Def.{ ScopedKey, Classpath }
import Def.{ Classpath, ScopedKey }
import sbt.internal.util.complete._
import java.io.File
import scala.util.Try
import scala.util.control.NonFatal
import scala.util.{ Failure, Success, Try }
abstract class BackgroundJobService extends Closeable {
@ -49,9 +52,19 @@ abstract class BackgroundJobService extends Closeable {
def jobs: Vector[JobHandle]
def stop(job: JobHandle): Unit
/**
* Delegate to waitFor but catches any exceptions and returns the result in an instance of `Try`.
* @param job the job to wait for
* @return the result of waiting for the job to complete.
*/
def waitForTry(job: JobHandle): Try[Unit] = {
// This implementation is provided only for backward compatibility.
Try(waitFor(job))
try Success(waitFor(job))
catch {
case NonFatal(e) =>
try stop(job)
catch { case NonFatal(_) => }
Failure(e)
}
}
def waitFor(job: JobHandle): Unit

View File

@ -1599,7 +1599,8 @@ object Defaults extends BuildCommon {
}
def bgWaitForTask: Initialize[InputTask[Unit]] = foreachJobTask { (manager, handle) =>
manager.waitFor(handle)
manager.waitForTry(handle)
()
}
def docTaskSettings(key: TaskKey[File] = doc): Seq[Setting[_]] =

View File

@ -161,7 +161,7 @@ private[sbt] abstract class AbstractBackgroundJobService extends BackgroundJobSe
jobSet.headOption.foreach {
case handle: ThreadJobHandle @unchecked =>
handle.job.shutdown()
handle.job.awaitTermination()
handle.job.awaitTerminationTry()
case _ => //
}
}
@ -178,24 +178,9 @@ private[sbt] abstract class AbstractBackgroundJobService extends BackgroundJobSe
)
}
private def withHandleTry(job: JobHandle)(f: ThreadJobHandle => Try[Unit]): Try[Unit] =
job match {
case handle: ThreadJobHandle @unchecked => f(handle)
case _: DeadHandle @unchecked => Try(()) // nothing to stop or wait for
case other =>
Try(
sys.error(
s"BackgroundJobHandle does not originate with the current BackgroundJobService: $other"
)
)
}
override def stop(job: JobHandle): Unit =
withHandle(job)(_.job.shutdown())
override def waitForTry(job: JobHandle): Try[Unit] =
withHandleTry(job)(_.job.awaitTerminationTry())
override def waitFor(job: JobHandle): Unit =
withHandle(job)(_.job.awaitTermination())
@ -398,11 +383,9 @@ private[sbt] class BackgroundThreadPool extends java.io.Closeable {
stopListeners += result
result
}
override def awaitTermination(): Unit = finishedLatch.await()
override def awaitTerminationTry(): Try[Unit] = {
awaitTermination()
exitTry.getOrElse(Try(()))
override def awaitTermination(): Unit = {
finishedLatch.await()
exitTry.foreach(_.fold(e => throw e, identity))
}
override def humanReadableName: String = taskName