mirror of https://github.com/sbt/sbt.git
[2.x] fix: sbtn to retry on corrupt active.json (#9617)
**Problem** sbtn gets stuck when active.json is corrupt. **Solution** Delete active.json, and retry.
This commit is contained in:
parent
6b3d7c6301
commit
8753a98161
|
|
@ -196,7 +196,7 @@ class NetworkClient(
|
|||
promptCompleteUsers: Boolean,
|
||||
retry: Boolean
|
||||
): (Socket, Option[String]) =
|
||||
try {
|
||||
try
|
||||
if (!portfile.exists) {
|
||||
if (shutdownOnly) {
|
||||
console.appendLog(Level.Info, "no sbt server is running. ciao")
|
||||
|
|
@ -246,12 +246,14 @@ class NetworkClient(
|
|||
}
|
||||
}
|
||||
connect(0)
|
||||
} catch {
|
||||
case e: ConnectionRefusedException if retry =>
|
||||
if (Files.deleteIfExists(portfile.toPath))
|
||||
catch
|
||||
case e @ (_: ConnectionRefusedException | _: ClientSocket.ConnectionFileReadException)
|
||||
if retry =>
|
||||
errorStream.println(s"${e.getMessage}; starting a new server")
|
||||
if Files.deleteIfExists(portfile.toPath) then
|
||||
connectOrStartServerAndConnect(promptCompleteUsers, retry = false)
|
||||
else throw e
|
||||
}
|
||||
end connectOrStartServerAndConnect
|
||||
|
||||
// Open server connection based on the portfile
|
||||
def init(promptCompleteUsers: Boolean, retry: Boolean): ServerSession = {
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ object NetworkClientSilentDisconnectTest extends BasicTestSuite:
|
|||
s"client exited 1 without explaining the disconnect: '$explained'"
|
||||
)
|
||||
|
||||
test("a corrupt portfile must not fail silently"):
|
||||
test("a corrupt portfile is replaced and a fresh server connection is attempted"):
|
||||
val base = Files.createTempDirectory("corrupt-portfile-project").toFile
|
||||
Files.writeString(
|
||||
base.toPath.resolve("build.sbt"),
|
||||
|
|
@ -77,22 +77,58 @@ object NetworkClientSilentDisconnectTest extends BasicTestSuite:
|
|||
)
|
||||
val target = base.toPath.resolve("project").resolve("target")
|
||||
Files.createDirectories(target)
|
||||
Files.writeString(target.resolve("active.json"), "not json")
|
||||
val (code, explained) = runBatchClient(base)
|
||||
assert(code == 1, s"expected exit 1, got $code")
|
||||
assert(
|
||||
explained.contains("sbt client failed"),
|
||||
s"client exited 1 without explaining the failure: '$explained'"
|
||||
)
|
||||
val portfile = target.resolve("active.json")
|
||||
Files.writeString(portfile, "not json")
|
||||
try
|
||||
val (_, explained) = runBatchClient(base)
|
||||
assert(
|
||||
explained.contains("corrupt or unreadable") &&
|
||||
explained.contains("active.json") &&
|
||||
explained.contains("starting a new server"),
|
||||
s"client did not explain the corrupt portfile before retrying: '$explained'"
|
||||
)
|
||||
assert(
|
||||
!Files.exists(portfile) || Files.readString(portfile) != "not json",
|
||||
"corrupt portfile should have been replaced by a fresh server connection"
|
||||
)
|
||||
finally shutdownServer(base)
|
||||
|
||||
private def runBatchClient(base: File): (Int, String) =
|
||||
test("an empty portfile is replaced and a fresh server connection is attempted"):
|
||||
val base = Files.createTempDirectory("empty-portfile-project").toFile
|
||||
Files.writeString(
|
||||
base.toPath.resolve("build.sbt"),
|
||||
"lazy val root = (project in file(\".\"))\n"
|
||||
)
|
||||
val target = base.toPath.resolve("project").resolve("target")
|
||||
Files.createDirectories(target)
|
||||
val portfile = target.resolve("active.json")
|
||||
Files.writeString(portfile, "")
|
||||
try
|
||||
val (_, explained) = runBatchClient(base)
|
||||
assert(
|
||||
explained.contains("corrupt or unreadable") &&
|
||||
explained.contains("active.json") &&
|
||||
explained.contains("starting a new server"),
|
||||
s"client did not explain the corrupt portfile before retrying: '$explained'"
|
||||
)
|
||||
assert(
|
||||
!Files.exists(portfile) || Files.readString(portfile).nonEmpty,
|
||||
"empty portfile should have been replaced by a fresh server connection"
|
||||
)
|
||||
finally shutdownServer(base)
|
||||
|
||||
private def shutdownServer(base: File): Unit =
|
||||
try runBatchClient(base, Array("shutdown"))
|
||||
catch case _: Exception => ()
|
||||
|
||||
private def runBatchClient(base: File, args: Array[String] = Array("compile")): (Int, String) =
|
||||
val errBytes = new ByteArrayOutputStream
|
||||
val outBytes = new ByteArrayOutputStream
|
||||
val err = new PrintStream(errBytes, true)
|
||||
val out = new PrintStream(outBytes, true)
|
||||
val code = NetworkClient.client(
|
||||
base,
|
||||
Array("compile"),
|
||||
args,
|
||||
new InputStream { override def read(): Int = -1 },
|
||||
out,
|
||||
err,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ package protocol
|
|||
import java.io.{ File, InputStream, OutputStream }
|
||||
import java.net.{ InetAddress, Socket, StandardProtocolFamily, URI, UnixDomainSocketAddress }
|
||||
import java.nio.channels.{ Channels, SocketChannel }
|
||||
import scala.util.control.NonFatal
|
||||
import sjsonnew.BasicJsonProtocol
|
||||
import sjsonnew.support.scalajson.unsafe.{ Parser, Converter }
|
||||
import sjsonnew.shaded.scalajson.ast.unsafe.JValue
|
||||
|
|
@ -23,18 +24,26 @@ import org.scalasbt.ipcsocket.*
|
|||
object ClientSocket {
|
||||
private lazy val fileFormats = new BasicJsonProtocol with PortFileFormats with TokenFileFormats {}
|
||||
|
||||
/** Thrown when a server connection file can't be read or parsed as JSON. */
|
||||
final class ConnectionFileReadException(file: File, cause: Throwable)
|
||||
extends Exception(s"sbt connection file $file is corrupt or unreadable: $cause", cause)
|
||||
|
||||
def socket(portfile: File): (Socket, Option[String]) = socket(portfile, false)
|
||||
def socket(portfile: File, useJNI: Boolean): (Socket, Option[String]) = {
|
||||
import fileFormats.given
|
||||
val json: JValue = Parser.parseFromString(sbt.io.IO.read(portfile)).get
|
||||
val p = Converter.fromJson[PortFile](json).get
|
||||
val p =
|
||||
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 uri = new URI(p.uri)
|
||||
// println(uri)
|
||||
val token = p.tokenfilePath map { tp =>
|
||||
val tokeFile = new File(tp)
|
||||
val json: JValue = Parser.parseFromFile(tokeFile).get
|
||||
val t = Converter.fromJson[TokenFile](json).get
|
||||
t.token
|
||||
try
|
||||
val json: JValue = Parser.parseFromFile(tokeFile).get
|
||||
Converter.fromJson[TokenFile](json).get.token
|
||||
catch case NonFatal(e) => throw new ConnectionFileReadException(tokeFile, e)
|
||||
}
|
||||
val sk = uri.getScheme match {
|
||||
case "local" => localSocket(uri.getSchemeSpecificPart, useJNI)
|
||||
|
|
|
|||
Loading…
Reference in New Issue