mirror of https://github.com/sbt/sbt.git
Updated Client server discovery lifecycle (markdown)
parent
187398e978
commit
b3e4fbf1d7
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue