mirror of https://github.com/sbt/sbt.git
[2.x] fix: Complete earlyOutputPing on cache-hit and failed compiles (#9542)
With usePipelining enabled, earlyOutputPing was completed only as a side effect of zinc reporting progress mid-compile (CompileProgress.afterEarlyOutput via writeEarlyOut / notifyNoEarlyOut in zinc-core). On an action-cache hit zinc never runs, and on a failed compile it never reports, so the promise stayed unfulfilled and every task waiting on it - compileEarly and makePickleProducts across downstream pipelined projects - parked forever. A warm compile;compile on a multi-module build hung indefinitely (#9486). The compile task now completes the ping on every resolution path. Zinc's own completion still wins when zinc ran (tryComplete is atomic, so it can never pre-empt it); a cache hit completes it from the pickle jar's presence on disk (absent jar means downstream falls back to a full compile, which is the correct degradation); a failed compile completes it false so waiters take the full compile path and propagate the failure instead of hanging. Generated-by: kimi-code/k3 (Oh My Pi)
This commit is contained in:
parent
5d7dbdb427
commit
25445191d7
|
|
@ -17,6 +17,11 @@ final class PromiseWrap[A]:
|
|||
case Result.Inc(cause) => underlying.failure(cause)
|
||||
case Result.Value(value) => underlying.success(value)
|
||||
}
|
||||
def tryComplete(result: Result[A]): Boolean =
|
||||
result match {
|
||||
case Result.Inc(cause) => underlying.tryFailure(cause)
|
||||
case Result.Value(value) => underlying.trySuccess(value)
|
||||
}
|
||||
def success(value: A): Unit = underlying.success(value)
|
||||
def failure(cause: Throwable): Unit = underlying.failure(cause)
|
||||
def isCompleted: Boolean = underlying.isCompleted
|
||||
|
|
|
|||
|
|
@ -2259,8 +2259,13 @@ object Defaults extends BuildCommon with DefExtra {
|
|||
val ci = (compile / compileInputs).value
|
||||
val c = fileConverter.value
|
||||
val dir = c.toPath(backendOutput.value).toFile
|
||||
val ping = (TaskZero / earlyOutputPing).value
|
||||
result match
|
||||
case Result.Value(res) =>
|
||||
// Zinc completes the ping during a real compile; on an action-cache hit
|
||||
// zinc never runs, so complete it from the pickle jar. tryComplete is
|
||||
// atomic: zinc's own completion always wins when it ran.
|
||||
ping.tryComplete(Result.Value(c.toPath(earlyOutput.value).toFile.exists))
|
||||
val af = compileAnalysisFile.value
|
||||
val store = analysisStore(af.toPath(), c)
|
||||
if !af.exists then sys.error(s"${af} is missing")
|
||||
|
|
@ -2269,6 +2274,7 @@ object Defaults extends BuildCommon with DefExtra {
|
|||
bspTask.notifySuccess(analysis)
|
||||
res
|
||||
case Result.Inc(cause) =>
|
||||
ping.tryComplete(Result.Value(false))
|
||||
val compileFailed = cause.directCause.collect { case c: CompileFailed => c }
|
||||
reporter.sendFailureReport(ci.options.sources, compileFailed)
|
||||
bspTask.notifyFailure(compileFailed)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
### Fixes
|
||||
|
||||
- Don't hang on `earlyOutputPing` when pipelining is enabled and a compile resolves from the action cache or fails. The ping was completed only as a side effect of zinc running, so a cache-hit or failed compile left downstream pipelined compiles waiting on it forever (`compile;compile` on a warm build hung indefinitely). The compile task now completes the ping on every resolution path: zinc's own completion still wins when zinc runs, a cache hit completes it from the pickle jar's presence, and a failure completes it false so waiters fall back to a full compile. Fixes [#9486](https://github.com/sbt/sbt/issues/9486).
|
||||
|
|
@ -12,11 +12,12 @@ lazy val dep = project
|
|||
lazy val use = project
|
||||
.dependsOn(dep)
|
||||
.settings(
|
||||
TaskKey[Unit]("checkPickle") := {
|
||||
TaskKey[Unit]("checkPickle") := Def.uncached {
|
||||
val s = streams.value
|
||||
val x = (dep / Compile / compile).value
|
||||
val picklePath = (Compile / internalDependencyPicklePath).value
|
||||
assert(picklePath.size == 1 &&
|
||||
picklePath.head.data.name == "early.jar", s"picklePath = ${picklePath}")
|
||||
picklePath.head.data.name.endsWith(".jar") &&
|
||||
picklePath.head.data.toString.contains("early"), s"picklePath = ${picklePath}")
|
||||
},
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in New Issue