Don't use global execution context

Because we are sharing the scala library classloader with test and run,
it is possible that sbt will be competing with for resources with the
test and run tasks when trying to get threads from the global execution
context. Also, by using our own execution context, we can shut it down
when sbt exits.

The motivation for this change is that I was looking at the active jvm
threads of an idle sbt process and noticed a bunch of global execution
context threads.
This commit is contained in:
Ethan Atkins 2019-05-30 12:43:40 -07:00
parent 63956827e0
commit 3de3cc15cf
4 changed files with 18 additions and 5 deletions

View File

@ -9,6 +9,7 @@ package sbt
import java.io.{ File, IOException } import java.io.{ File, IOException }
import java.net.URI import java.net.URI
import java.util.concurrent.{ Executors, ForkJoinPool }
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
import java.util.{ Locale, Properties } import java.util.{ Locale, Properties }
@ -96,11 +97,21 @@ object StandardMain {
private[sbt] lazy val exchange = new CommandExchange() private[sbt] lazy val exchange = new CommandExchange()
import scalacache.caffeine._ import scalacache.caffeine._
private[sbt] lazy val cache: scalacache.Cache[Any] = CaffeineCache[Any] private[sbt] lazy val cache: scalacache.Cache[Any] = CaffeineCache[Any]
// The access to the pool should be thread safe because lazy val instantiation is thread safe
// and pool is only referenced directly in closeRunnable after the executionContext is sure
// to have been instantiated
private[this] var pool: Option[ForkJoinPool] = None
private[sbt] lazy val executionContext: ExecutionContext = ExecutionContext.fromExecutor({
val p = new ForkJoinPool
pool = Some(p)
p
})
private[this] val closeRunnable = () => { private[this] val closeRunnable = () => {
cache.close()(scalacache.modes.sync.mode) cache.close()(scalacache.modes.sync.mode)
cache.close()(scalacache.modes.scalaFuture.mode(ExecutionContext.global)) cache.close()(scalacache.modes.scalaFuture.mode(executionContext))
exchange.shutdown() exchange.shutdown()
pool.foreach(_.shutdownNow())
} }
def runManaged(s: State): xsbti.MainResult = { def runManaged(s: State): xsbti.MainResult = {

View File

@ -93,9 +93,9 @@ private[sbt] abstract class AbstractBackgroundJobService extends BackgroundJobSe
val workingDirectory: File, val workingDirectory: File,
val job: BackgroundJob val job: BackgroundJob
) extends AbstractJobHandle { ) extends AbstractJobHandle {
implicit val executionContext: ExecutionContext = StandardMain.executionContext
def humanReadableName: String = job.humanReadableName def humanReadableName: String = job.humanReadableName
// EC for onStop handler below // EC for onStop handler below
import ExecutionContext.Implicits.global
job.onStop { () => job.onStop { () =>
// TODO: Fix this // TODO: Fix this
// logger.close() // logger.close()

View File

@ -234,7 +234,7 @@ private[sbt] object Definition {
private[sbt] def getAnalyses: Future[Seq[Analysis]] = { private[sbt] def getAnalyses: Future[Seq[Analysis]] = {
import scalacache.modes.scalaFuture._ import scalacache.modes.scalaFuture._
import scala.concurrent.ExecutionContext.Implicits.global implicit val executionContext: ExecutionContext = StandardMain.executionContext
AnalysesAccess AnalysesAccess
.getFrom(StandardMain.cache) .getFrom(StandardMain.cache)
.collect { case Some(a) => a } .collect { case Some(a) => a }

View File

@ -13,7 +13,7 @@ import sjsonnew.JsonFormat
import sjsonnew.shaded.scalajson.ast.unsafe.JValue import sjsonnew.shaded.scalajson.ast.unsafe.JValue
import sjsonnew.support.scalajson.unsafe.Converter import sjsonnew.support.scalajson.unsafe.Converter
import sbt.protocol.Serialization import sbt.protocol.Serialization
import sbt.protocol.{ SettingQuery => Q, CompletionParams => CP } import sbt.protocol.{ CompletionParams => CP, SettingQuery => Q }
import sbt.internal.langserver.{ CancelRequestParams => CRP } import sbt.internal.langserver.{ CancelRequestParams => CRP }
import sbt.internal.protocol._ import sbt.internal.protocol._
import sbt.internal.protocol.codec._ import sbt.internal.protocol.codec._
@ -21,6 +21,8 @@ import sbt.internal.langserver._
import sbt.internal.util.ObjectEvent import sbt.internal.util.ObjectEvent
import sbt.util.Logger import sbt.util.Logger
import scala.concurrent.ExecutionContext
private[sbt] final case class LangServerError(code: Long, message: String) private[sbt] final case class LangServerError(code: Long, message: String)
extends Throwable(message) extends Throwable(message)
@ -70,7 +72,7 @@ private[sbt] object LanguageServerProtocol {
jsonRpcRespond(InitializeResult(serverCapabilities), Option(r.id)) jsonRpcRespond(InitializeResult(serverCapabilities), Option(r.id))
case r: JsonRpcRequestMessage if r.method == "textDocument/definition" => case r: JsonRpcRequestMessage if r.method == "textDocument/definition" =>
import scala.concurrent.ExecutionContext.Implicits.global implicit val executionContext: ExecutionContext = StandardMain.executionContext
Definition.lspDefinition(json(r), r.id, CommandSource(name), log) Definition.lspDefinition(json(r), r.id, CommandSource(name), log)
() ()
case r: JsonRpcRequestMessage if r.method == "sbt/exec" => case r: JsonRpcRequestMessage if r.method == "sbt/exec" =>