mirror of https://github.com/sbt/sbt.git
Add server idle timeout
This commit adds the ability for sbt to automatically shut itself down if it has been idle for some duration of time. The motivation is that if the user may not realize they have an sbt server running in the background that is using resources. We don't want to be too aggressive with the idle timeout because that can reduce the efficacy of the thin client. A value of one week is chosen so that users can enjoy a long weekend and when they return to their computer, they won't have to restart sbt. If they haven't used the server in at least a week, it seems prudent to just kill it.
This commit is contained in:
parent
f8e06def74
commit
ea823f1051
|
|
@ -16,6 +16,7 @@ import sbt.internal.server.ServerHandler
|
|||
import sbt.internal.util.{ AttributeKey, Terminal }
|
||||
import sbt.librarymanagement.ModuleID
|
||||
import sbt.util.Level
|
||||
import scala.concurrent.duration.FiniteDuration
|
||||
|
||||
object BasicKeys {
|
||||
val historyPath = AttributeKey[Option[File]](
|
||||
|
|
@ -83,6 +84,13 @@ object BasicKeys {
|
|||
10000
|
||||
)
|
||||
|
||||
val serverIdleTimeout =
|
||||
AttributeKey[Option[FiniteDuration]](
|
||||
"serverIdleTimeOut",
|
||||
"If set to a defined value, sbt server will exit if it goes at least the specified duration without receiving any commands.",
|
||||
10000
|
||||
)
|
||||
|
||||
// Unlike other BasicKeys, this is not used directly as a setting key,
|
||||
// and severLog / logLevel is used instead.
|
||||
private[sbt] val serverLogLevel =
|
||||
|
|
|
|||
|
|
@ -378,6 +378,7 @@ object Defaults extends BuildCommon {
|
|||
interactionService :== CommandLineUIService,
|
||||
autoStartServer := true,
|
||||
serverHost := "127.0.0.1",
|
||||
serverIdleTimeout := Some(new FiniteDuration(7, TimeUnit.DAYS)),
|
||||
serverPort := 5000 + (Hash
|
||||
.toHex(Hash(appConfiguration.value.baseDirectory.toString))
|
||||
.## % 1000),
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ object Keys {
|
|||
val serverHost = SettingKey(BasicKeys.serverHost)
|
||||
val serverAuthentication = SettingKey(BasicKeys.serverAuthentication)
|
||||
val serverConnectionType = SettingKey(BasicKeys.serverConnectionType)
|
||||
val serverIdleTimeout = SettingKey(BasicKeys.serverIdleTimeout)
|
||||
val windowsServerSecurityLevel = SettingKey(BasicKeys.windowsServerSecurityLevel)
|
||||
val fullServerHandlers = SettingKey(BasicKeys.fullServerHandlers)
|
||||
val serverHandlers = settingKey[Seq[ServerHandler]]("User-defined server handlers.")
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import Keys.{
|
|||
templateResolverInfos,
|
||||
autoStartServer,
|
||||
serverHost,
|
||||
serverIdleTimeout,
|
||||
serverLog,
|
||||
serverPort,
|
||||
serverAuthentication,
|
||||
|
|
@ -52,6 +53,7 @@ import sbt.util.{ Show, Level }
|
|||
import sjsonnew.JsonFormat
|
||||
|
||||
import language.experimental.macros
|
||||
import scala.concurrent.duration.FiniteDuration
|
||||
|
||||
sealed trait ProjectDefinition[PR <: ProjectReference] {
|
||||
|
||||
|
|
@ -515,6 +517,7 @@ object Project extends ProjectExtra {
|
|||
val startSvr: Option[Boolean] = get(autoStartServer)
|
||||
val host: Option[String] = get(serverHost)
|
||||
val port: Option[Int] = get(serverPort)
|
||||
val timeout: Option[Option[FiniteDuration]] = get(serverIdleTimeout)
|
||||
val authentication: Option[Set[ServerAuthentication]] = get(serverAuthentication)
|
||||
val connectionType: Option[ConnectionType] = get(serverConnectionType)
|
||||
val srvLogLevel: Option[Level.Value] = (logLevel in (ref, serverLog)).get(structure.data)
|
||||
|
|
@ -534,6 +537,7 @@ object Project extends ProjectExtra {
|
|||
.setCond(serverHost.key, host)
|
||||
.setCond(serverAuthentication.key, authentication)
|
||||
.setCond(serverConnectionType.key, connectionType)
|
||||
.setCond(serverIdleTimeout.key, timeout)
|
||||
.put(historyPath.key, history)
|
||||
.put(templateResolverInfos.key, trs)
|
||||
.setCond(shellPrompt.key, prompt)
|
||||
|
|
|
|||
|
|
@ -71,14 +71,36 @@ private[sbt] final class CommandExchange {
|
|||
state: Option[State],
|
||||
logger: Logger
|
||||
): Exec = {
|
||||
@tailrec def impl(deadline: Option[Deadline]): Exec = {
|
||||
val idleDeadline = state.flatMap { s =>
|
||||
lastState.set(s)
|
||||
s.get(BasicKeys.serverIdleTimeout) match {
|
||||
case Some(Some(d)) => Some(d.fromNow)
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
@tailrec def impl(gcDeadline: Option[Deadline], idleDeadline: Option[Deadline]): Exec = {
|
||||
state.foreach(s => prompt(ConsolePromptEvent(s)))
|
||||
def poll: Option[Exec] =
|
||||
def poll: Option[Exec] = {
|
||||
val deadline = gcDeadline.toSeq ++ idleDeadline match {
|
||||
case s @ Seq(_, _) => Some(s.min)
|
||||
case s => s.headOption
|
||||
}
|
||||
Option(deadline match {
|
||||
case Some(d: Deadline) =>
|
||||
commandQueue.poll(d.timeLeft.toMillis + 1, TimeUnit.MILLISECONDS)
|
||||
commandQueue.poll(d.timeLeft.toMillis + 1, TimeUnit.MILLISECONDS) match {
|
||||
case null if idleDeadline.fold(false)(_.isOverdue) =>
|
||||
state.foreach { s =>
|
||||
s.get(BasicKeys.serverIdleTimeout) match {
|
||||
case Some(Some(d)) => s.log.info(s"sbt idle timeout of $d expired")
|
||||
case _ =>
|
||||
}
|
||||
}
|
||||
Exec("exit", Some(CommandSource(ConsoleChannel.defaultName)))
|
||||
case x => x
|
||||
}
|
||||
case _ => commandQueue.take
|
||||
})
|
||||
}
|
||||
poll match {
|
||||
case Some(exec) if exec.source.fold(true)(s => channels.exists(_.name == s.channelName)) =>
|
||||
exec.commandLine match {
|
||||
|
|
@ -92,24 +114,24 @@ private[sbt] final class CommandExchange {
|
|||
} match {
|
||||
case Some(c) if c.isAttached =>
|
||||
c.shutdown(false)
|
||||
impl(deadline)
|
||||
impl(gcDeadline, idleDeadline)
|
||||
case _ => exec
|
||||
}
|
||||
case _ => exec
|
||||
}
|
||||
case None =>
|
||||
val newDeadline = if (deadline.fold(false)(_.isOverdue())) {
|
||||
val newDeadline = if (gcDeadline.fold(false)(_.isOverdue())) {
|
||||
GCUtil.forceGcWithInterval(interval, logger)
|
||||
None
|
||||
} else deadline
|
||||
impl(newDeadline)
|
||||
} else gcDeadline
|
||||
impl(newDeadline, idleDeadline)
|
||||
}
|
||||
}
|
||||
// Do not manually run GC until the user has been idling for at least the min gc interval.
|
||||
impl(interval match {
|
||||
case d: FiniteDuration => Some(d.fromNow)
|
||||
case _ => None
|
||||
})
|
||||
}, idleDeadline)
|
||||
}
|
||||
|
||||
private def addConsoleChannel(): Unit =
|
||||
|
|
|
|||
Loading…
Reference in New Issue