From 3de3cc15cf63db513200105c96ffe315b57d8c44 Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Thu, 30 May 2019 12:43:40 -0700 Subject: [PATCH] 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. --- main/src/main/scala/sbt/Main.scala | 13 ++++++++++++- .../sbt/internal/DefaultBackgroundJobService.scala | 2 +- .../main/scala/sbt/internal/server/Definition.scala | 2 +- .../internal/server/LanguageServerProtocol.scala | 6 ++++-- 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index 54b8b5216..a1a54f095 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -9,6 +9,7 @@ package sbt import java.io.{ File, IOException } import java.net.URI +import java.util.concurrent.{ Executors, ForkJoinPool } import java.util.concurrent.atomic.AtomicBoolean import java.util.{ Locale, Properties } @@ -96,11 +97,21 @@ object StandardMain { private[sbt] lazy val exchange = new CommandExchange() import scalacache.caffeine._ 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 = () => { cache.close()(scalacache.modes.sync.mode) - cache.close()(scalacache.modes.scalaFuture.mode(ExecutionContext.global)) + cache.close()(scalacache.modes.scalaFuture.mode(executionContext)) exchange.shutdown() + pool.foreach(_.shutdownNow()) } def runManaged(s: State): xsbti.MainResult = { diff --git a/main/src/main/scala/sbt/internal/DefaultBackgroundJobService.scala b/main/src/main/scala/sbt/internal/DefaultBackgroundJobService.scala index 0a04ce4d4..823cad5fe 100644 --- a/main/src/main/scala/sbt/internal/DefaultBackgroundJobService.scala +++ b/main/src/main/scala/sbt/internal/DefaultBackgroundJobService.scala @@ -93,9 +93,9 @@ private[sbt] abstract class AbstractBackgroundJobService extends BackgroundJobSe val workingDirectory: File, val job: BackgroundJob ) extends AbstractJobHandle { + implicit val executionContext: ExecutionContext = StandardMain.executionContext def humanReadableName: String = job.humanReadableName // EC for onStop handler below - import ExecutionContext.Implicits.global job.onStop { () => // TODO: Fix this // logger.close() diff --git a/main/src/main/scala/sbt/internal/server/Definition.scala b/main/src/main/scala/sbt/internal/server/Definition.scala index 038eabc4c..b741f6feb 100644 --- a/main/src/main/scala/sbt/internal/server/Definition.scala +++ b/main/src/main/scala/sbt/internal/server/Definition.scala @@ -234,7 +234,7 @@ private[sbt] object Definition { private[sbt] def getAnalyses: Future[Seq[Analysis]] = { import scalacache.modes.scalaFuture._ - import scala.concurrent.ExecutionContext.Implicits.global + implicit val executionContext: ExecutionContext = StandardMain.executionContext AnalysesAccess .getFrom(StandardMain.cache) .collect { case Some(a) => a } diff --git a/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala b/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala index a23435173..9a369acb8 100644 --- a/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala +++ b/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala @@ -13,7 +13,7 @@ import sjsonnew.JsonFormat import sjsonnew.shaded.scalajson.ast.unsafe.JValue import sjsonnew.support.scalajson.unsafe.Converter 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.protocol._ import sbt.internal.protocol.codec._ @@ -21,6 +21,8 @@ import sbt.internal.langserver._ import sbt.internal.util.ObjectEvent import sbt.util.Logger +import scala.concurrent.ExecutionContext + private[sbt] final case class LangServerError(code: Long, message: String) extends Throwable(message) @@ -70,7 +72,7 @@ private[sbt] object LanguageServerProtocol { jsonRpcRespond(InitializeResult(serverCapabilities), Option(r.id)) 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) () case r: JsonRpcRequestMessage if r.method == "sbt/exec" =>