mirror of
https://github.com/sbt/sbt.git
synced 2026-10-06 10:03:56 +02:00
[2.x] fix: Stop a displaced server (#9713)
**Problem** If a client cannot reach the server, it deletes the portfile. Then it starts a second server. The first server is displaced: its socket path now belongs to the second server. A displaced server keeps running. A dropIfIdle notification cannot reach it, because the proc file it registered names the socket that the second server now owns. **Solution** The portfile holds the serverId of whichever server wrote it. Watch that file, and exit when the id is not this server's. Leave the portfile to the server that owns it. --------- Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
ce7bb28f05
commit
dbeeb199aa
@@ -11,28 +11,32 @@ package sbt.internal.protocol
|
||||
* @param sysProps The -D options the thin client passed to this server, as names and salted digests.
|
||||
* @param sysPropsRecorded Whether sysProps is the whole story. Absent on a server no thin client started, whose
|
||||
options are its own business and unknown here.
|
||||
* @param serverId Identifies the server that wrote this file. A server that reads an id other than its own
|
||||
has been replaced, and no client can reach it any more.
|
||||
*/
|
||||
final class PortFile private (
|
||||
val uri: String,
|
||||
val tokenfilePath: Option[String],
|
||||
val tokenfileUri: Option[String],
|
||||
val sysProps: Vector[String],
|
||||
val sysPropsRecorded: Option[Boolean]) extends Serializable {
|
||||
val sysPropsRecorded: Option[Boolean],
|
||||
val serverId: Option[String]) extends Serializable {
|
||||
|
||||
private def this(uri: String, tokenfilePath: Option[String], tokenfileUri: Option[String]) = this(uri, tokenfilePath, tokenfileUri, Vector(), None)
|
||||
private def this(uri: String, tokenfilePath: Option[String], tokenfileUri: Option[String]) = this(uri, tokenfilePath, tokenfileUri, Vector(), None, None)
|
||||
private def this(uri: String, tokenfilePath: Option[String], tokenfileUri: Option[String], sysProps: Vector[String], sysPropsRecorded: Option[Boolean]) = this(uri, tokenfilePath, tokenfileUri, sysProps, sysPropsRecorded, None)
|
||||
|
||||
override def equals(o: Any): Boolean = this.eq(o.asInstanceOf[AnyRef]) || (o match {
|
||||
case x: PortFile => (this.uri == x.uri) && (this.tokenfilePath == x.tokenfilePath) && (this.tokenfileUri == x.tokenfileUri) && (this.sysProps == x.sysProps) && (this.sysPropsRecorded == x.sysPropsRecorded)
|
||||
case x: PortFile => (this.uri == x.uri) && (this.tokenfilePath == x.tokenfilePath) && (this.tokenfileUri == x.tokenfileUri) && (this.sysProps == x.sysProps) && (this.sysPropsRecorded == x.sysPropsRecorded) && (this.serverId == x.serverId)
|
||||
case _ => false
|
||||
})
|
||||
override def hashCode: Int = {
|
||||
37 * (37 * (37 * (37 * (37 * (37 * (17 + "sbt.internal.protocol.PortFile".##) + uri.##) + tokenfilePath.##) + tokenfileUri.##) + sysProps.##) + sysPropsRecorded.##)
|
||||
37 * (37 * (37 * (37 * (37 * (37 * (37 * (17 + "sbt.internal.protocol.PortFile".##) + uri.##) + tokenfilePath.##) + tokenfileUri.##) + sysProps.##) + sysPropsRecorded.##) + serverId.##)
|
||||
}
|
||||
override def toString: String = {
|
||||
"PortFile(" + uri + ", " + tokenfilePath + ", " + tokenfileUri + ", " + sysProps + ", " + sysPropsRecorded + ")"
|
||||
"PortFile(" + uri + ", " + tokenfilePath + ", " + tokenfileUri + ", " + sysProps + ", " + sysPropsRecorded + ", " + serverId + ")"
|
||||
}
|
||||
private def copy(uri: String = uri, tokenfilePath: Option[String] = tokenfilePath, tokenfileUri: Option[String] = tokenfileUri, sysProps: Vector[String] = sysProps, sysPropsRecorded: Option[Boolean] = sysPropsRecorded): PortFile = {
|
||||
new PortFile(uri, tokenfilePath, tokenfileUri, sysProps, sysPropsRecorded)
|
||||
private def copy(uri: String = uri, tokenfilePath: Option[String] = tokenfilePath, tokenfileUri: Option[String] = tokenfileUri, sysProps: Vector[String] = sysProps, sysPropsRecorded: Option[Boolean] = sysPropsRecorded, serverId: Option[String] = serverId): PortFile = {
|
||||
new PortFile(uri, tokenfilePath, tokenfileUri, sysProps, sysPropsRecorded, serverId)
|
||||
}
|
||||
def withUri(uri: String): PortFile = {
|
||||
copy(uri = uri)
|
||||
@@ -58,6 +62,12 @@ final class PortFile private (
|
||||
def withSysPropsRecorded(sysPropsRecorded: Boolean): PortFile = {
|
||||
copy(sysPropsRecorded = Option(sysPropsRecorded))
|
||||
}
|
||||
def withServerId(serverId: Option[String]): PortFile = {
|
||||
copy(serverId = serverId)
|
||||
}
|
||||
def withServerId(serverId: String): PortFile = {
|
||||
copy(serverId = Option(serverId))
|
||||
}
|
||||
}
|
||||
object PortFile {
|
||||
|
||||
@@ -65,4 +75,6 @@ object PortFile {
|
||||
def apply(uri: String, tokenfilePath: String, tokenfileUri: String): PortFile = new PortFile(uri, Option(tokenfilePath), Option(tokenfileUri))
|
||||
def apply(uri: String, tokenfilePath: Option[String], tokenfileUri: Option[String], sysProps: Vector[String], sysPropsRecorded: Option[Boolean]): PortFile = new PortFile(uri, tokenfilePath, tokenfileUri, sysProps, sysPropsRecorded)
|
||||
def apply(uri: String, tokenfilePath: String, tokenfileUri: String, sysProps: Vector[String], sysPropsRecorded: Boolean): PortFile = new PortFile(uri, Option(tokenfilePath), Option(tokenfileUri), sysProps, Option(sysPropsRecorded))
|
||||
def apply(uri: String, tokenfilePath: Option[String], tokenfileUri: Option[String], sysProps: Vector[String], sysPropsRecorded: Option[Boolean], serverId: Option[String]): PortFile = new PortFile(uri, tokenfilePath, tokenfileUri, sysProps, sysPropsRecorded, serverId)
|
||||
def apply(uri: String, tokenfilePath: String, tokenfileUri: String, sysProps: Vector[String], sysPropsRecorded: Boolean, serverId: String): PortFile = new PortFile(uri, Option(tokenfilePath), Option(tokenfileUri), sysProps, Option(sysPropsRecorded), Option(serverId))
|
||||
}
|
||||
|
||||
+3
-1
@@ -16,8 +16,9 @@ given PortFileFormat: JsonFormat[sbt.internal.protocol.PortFile] = new JsonForma
|
||||
val tokenfileUri = unbuilder.readField[Option[String]]("tokenfileUri")
|
||||
val sysProps = unbuilder.readField[Vector[String]]("sysProps")
|
||||
val sysPropsRecorded = unbuilder.readField[Option[Boolean]]("sysPropsRecorded")
|
||||
val serverId = unbuilder.readField[Option[String]]("serverId")
|
||||
unbuilder.endObject()
|
||||
sbt.internal.protocol.PortFile(uri, tokenfilePath, tokenfileUri, sysProps, sysPropsRecorded)
|
||||
sbt.internal.protocol.PortFile(uri, tokenfilePath, tokenfileUri, sysProps, sysPropsRecorded, serverId)
|
||||
case None =>
|
||||
deserializationError("Expected JsObject but found None")
|
||||
}
|
||||
@@ -29,6 +30,7 @@ given PortFileFormat: JsonFormat[sbt.internal.protocol.PortFile] = new JsonForma
|
||||
builder.addField("tokenfileUri", obj.tokenfileUri)
|
||||
builder.addField("sysProps", obj.sysProps)
|
||||
builder.addField("sysPropsRecorded", obj.sysPropsRecorded)
|
||||
builder.addField("serverId", obj.serverId)
|
||||
builder.endObject()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,9 @@ type PortFile {
|
||||
## Whether sysProps is the whole story. Absent on a server no thin client started, whose
|
||||
## options are its own business and unknown here.
|
||||
sysPropsRecorded: Boolean @since("2.0.7")
|
||||
## Identifies the server that wrote this file. A server that reads an id other than its own
|
||||
## has been replaced, and no client can reach it any more.
|
||||
serverId: String @since("2.0.9")
|
||||
}
|
||||
|
||||
type TokenFile {
|
||||
|
||||
@@ -21,6 +21,8 @@ import sbt.internal.protocol.codec.{ PortFileFormats, TokenFileFormats }
|
||||
import sbt.internal.util.Util.isWindows
|
||||
import org.scalasbt.ipcsocket.*
|
||||
|
||||
import scala.util.{ Failure, Success, Try }
|
||||
|
||||
object ClientSocket {
|
||||
private lazy val fileFormats = new BasicJsonProtocol with PortFileFormats with TokenFileFormats {}
|
||||
|
||||
@@ -31,19 +33,19 @@ object ClientSocket {
|
||||
def socket(portfile: File): (Socket, Option[String]) = socket(portfile, false)
|
||||
|
||||
/** Parses the connection file written by the server. */
|
||||
private[sbt] def portFile(portfile: File): PortFile = {
|
||||
private[sbt] def loadPortFile(portfile: File): Try[PortFile] = {
|
||||
import fileFormats.given
|
||||
try
|
||||
val json: JValue = Parser.parseFromString(sbt.io.IO.read(portfile)).get
|
||||
Converter.fromJson[PortFile](json).get
|
||||
catch case NonFatal(e) => throw new ConnectionFileReadException(portfile, e)
|
||||
val parsed = Try(sbt.io.IO.read(portfile)).flatMap(Parser.parseFromString)
|
||||
parsed.flatMap(Converter.fromJson[PortFile])
|
||||
}
|
||||
|
||||
def socket(portfile: File, useJNI: Boolean): (Socket, Option[String]) = {
|
||||
import fileFormats.given
|
||||
val p = portFile(portfile)
|
||||
val p = loadPortFile(portfile) match {
|
||||
case Success(p) => p
|
||||
case Failure(e) => throw new ConnectionFileReadException(portfile, e)
|
||||
}
|
||||
val uri = new URI(p.uri)
|
||||
// println(uri)
|
||||
val token = p.tokenfilePath map { tp =>
|
||||
val tokeFile = new File(tp)
|
||||
try
|
||||
|
||||
Reference in New Issue
Block a user