From b3e4fbf1d71b2f3232a77ca26df4161888750dc0 Mon Sep 17 00:00:00 2001 From: Josh Suereth Date: Wed, 16 Oct 2013 07:29:37 -0700 Subject: [PATCH] Updated Client server discovery lifecycle (markdown) --- Client-server-discovery-lifecycle.md | 103 +++++++++++++++++++++++---- 1 file changed, 88 insertions(+), 15 deletions(-) diff --git a/Client-server-discovery-lifecycle.md b/Client-server-discovery-lifecycle.md index fb54aea..0993b45 100644 --- a/Client-server-discovery-lifecycle.md +++ b/Client-server-discovery-lifecycle.md @@ -80,7 +80,7 @@ Here's a more concrete proposal of how launching a server would work. However, 1. the sbt launcher's locking mechanism is sufficient for us to find a means to avoid double-server-startup. 2. We want clients to have *one* means of attempting to fork the sbt server process. This is assumed to - be the sbt launcher. + be a new "service locator" feature of the sbt-launcher. 3. Upon network failure, or inability to communicate to the server, clients will attempt to spawn another server. This will only succeed if the server is actually down. Clients are also responsible for restoring any state they need on the server upon reconnect. @@ -88,31 +88,104 @@ Here's a more concrete proposal of how launching a server would work. However, no more clients. -*TODO - more assumptions?* - -We create a new interface for the sbt launcher, called `xsbt.ServerMain`: +So, first We create a new interface for the sbt launcher, called `xsbt.ServerMain`: ```java package xsbti; public interface ServerMain { - public java.net.URI[] start(AppConfiguration configuration); - public boolean isAlive(java.net.URI[] listenPorts); + public java.net.URI start(AppConfiguration configuration); + public boolean isAlive(java.net.URI active); +} +``` + +The interface has two methods. One responsible for starting the Server, and another which can be used to check whether or not another server is alive, based on a given set of server port connections. + +The sbt launcher can now also support launching servers, via the following pseudocode: + +```scala +def launchServer(config: ServerConfiguration): Unit = { + val oldOut = System.out + System.setErr(getErr(config)) + System.setOut(getOut(config)) + val uri = runServer(config) + oldOut.println(uri) + // Let this thread die, server thread will continue running. +} +``` + +This is done via a new configuration similar to AppConfiguration, only with an additional values. E.g. here's a possibly launch configuration for the sbt server: + +``` +[scala] + version: auto + +[server] + org: org.scala-sbt + name: sbt + version: ${sbt.version-read(sbt.version)[${{sbt.version}}]} + class: sbt.xServer + components: xsbti,extra + cross-versioned: false + resources: ${sbt.extraClasspath-} + lock: .active.properties + +[repositories] + local + maven-central + +[boot] + directory: ${sbt.boot.directory-${sbt.global.base-${user.home}/.sbt}/boot/} + +[ivy] + ivy-home: ${sbt.ivy.home-${user.home}/.ivy2/} + checksums: ${sbt.checksums-sha1,md5} + override-build-repos: ${sbt.override.build.repos-false} + repository-config: ${sbt.repository.config-${sbt.global.base-${user.home}/.sbt}/repositories} +``` + +The only new features in the above launch configuration are: + +1. using [server] rather than [app] to inform the launcher which interface to expect. +2. An additional `lock` attribute that specifies where the service-locator can find this servers lock file. + +When launching a server, you'll note that the lock attribute is not inspected. Instead, the sbt-launcher will also support a "service locator" functionality where this lock will be critical. + +The Service locator will be executed by clients via the command line: + +`java -jar sbt-launch.jar locate:MyServerBootConfigurationFile` + +This will return the URI of an active launched server. It will use the below pseudo-code as a possible implementation: + +```scala +def serviceLocator(config: ServerConfiguration, launcher: Launcher): URI = { + def isReachable(info: URI): Boolean = + launcher.server(config).isAlive(info) + + withLockFile(config.lockFile) { + readServerInfo(config.lockFile).filter(isReachable) match { + case Some(info) => info + case None => + val uri = startServer(config) + writeServerInfo(config.lockFile, uri) + uri + } + } +} + +def startServer(config: ServerConfiguration): URI = { + val bootProps = makeTemporaryBootProps(config) + val launcherJar = lookUpFromMyself() + val process = s"java -jar ${launcherJar} @${bootprops}".in(cwd).run + val uri = fromStdOut(process) + if(process isn't running) fail("Couldn't start server") + uri } ``` -The interface has two methods. One responsible for starting the Server, and another which can be used to check whether or not another server is alive, based on a given set of server port connections. The sbt launcher is responsible for: -1. Obtaining the lock file for the CWD + AppConfiguratoin-hash pairing. (That is there can only be one server of a given configuration (organization + module id) in any working directory). -2. Reading the ServerPorts from the active configuration. -3. Instantiating an instance of the ServerMain class denoted in the configuration. -3. Checking to see if the old server is alive using the ServerMain instance. If so, report these ports to the client (TODO - how?) -4. If the old server was not alive, start up a new server using the ServerMain instance. Write the new server bindings to the lock file and release the lock. -5. Notify the client (via stdout) what the bound ports are. -Port bindings come in java.net.URI, which includes an address, a protocol and a port. The server will actually also write down the PID (process ID) of the server (assuming it can obtain the pid). This enables clients to "kill rogue sbt server" directly. -Clients should be able to be notified of all open ServerPort bindings created by a server. ## Sample code