From 9c8503a07b3b742d28b54c88883245e84c249753 Mon Sep 17 00:00:00 2001 From: Thomas Honeyman Date: Sun, 18 Oct 2026 22:22:45 +0000 Subject: [PATCH] Retry package-set heap exhaustion with clean compiler output --- app/src/App/CLI/Purs.purs | 10 ++++++++-- app/src/App/Effect/PackageSets.purs | 21 +++++++++++++++------ app/test/App/CLI/Purs.purs | 11 +++++++++++ 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/app/src/App/CLI/Purs.purs b/app/src/App/CLI/Purs.purs index bece27e6..7a87b9e3 100644 --- a/app/src/App/CLI/Purs.purs +++ b/app/src/App/CLI/Purs.purs @@ -191,10 +191,16 @@ callCompiler compilerArgs = do compilerRaceConditionError :: String compilerRaceConditionError = "Unexpected end of JSON input" +-- | Heap exhaustion can leave stdout empty, so JSON decoding also reports EOF. +isCompilerHeapExhaustion :: CompilerFailure -> Boolean +isCompilerHeapExhaustion = case _ of + UnknownError err -> String.contains (String.Pattern "purs: Heap exhausted") err + _ -> false + -- | Check if a compiler failure is due to the known race condition bug. isCompilerRaceCondition :: CompilerFailure -> Boolean -isCompilerRaceCondition = case _ of - UnknownError err -> String.contains (String.Pattern compilerRaceConditionError) err +isCompilerRaceCondition failure = case failure of + UnknownError err -> not (isCompilerHeapExhaustion failure) && String.contains (String.Pattern compilerRaceConditionError) err _ -> false -- | Compile PureScript source files with automatic retry for transient errors. diff --git a/app/src/App/Effect/PackageSets.purs b/app/src/App/Effect/PackageSets.purs index c02ba3a1..007d2ab7 100644 --- a/app/src/App/Effect/PackageSets.purs +++ b/app/src/App/Effect/PackageSets.purs @@ -263,12 +263,21 @@ handle env = case _ of compileInstalledPackages :: Version -> Run _ (Either CompilerFailure String) compileInstalledPackages compiler = do Log.debug "Compiling installed packages..." - Purs.compile - { globs: [ Path.concat [ Path.basename packagesWorkDir, "**/*.purs" ] ] - , profile: Purs.PackageSetBuild - , version: Just compiler - , cwd: Just env.workdir - } + let + compile = Purs.compile + { globs: [ Path.concat [ Path.basename packagesWorkDir, "**/*.purs" ] ] + , profile: Purs.PackageSetBuild + , version: Just compiler + , cwd: Just env.workdir + } + result <- compile + case result of + Left err | Purs.isCompilerHeapExhaustion err -> do + Log.warn "Compiler heap exhausted; clearing package-set output and retrying once." + -- Keep the backup intact so a failed retry can still roll back. + FS.Extra.remove outputWorkDir + compile + _ -> pure result attemptChanges :: Version -> PackageSet -> ChangeSet -> Run _ (Either CompilerFailure PackageSet) attemptChanges compiler (PackageSet set) changes = do diff --git a/app/test/App/CLI/Purs.purs b/app/test/App/CLI/Purs.purs index 988846a6..6bdf9f2a 100644 --- a/app/test/App/CLI/Purs.purs +++ b/app/test/App/CLI/Purs.purs @@ -54,6 +54,17 @@ spec = do let err = UnknownError "Some prefix text\nJSON: Unexpected end of JSON input\nSome suffix" Purs.isCompilerRaceCondition err `Assertions.shouldEqual` true + Spec.it "Recognizes heap exhaustion without treating its empty JSON output as a race" do + let + err = UnknownError $ String.joinWith "\n" + [ "purs: Heap exhausted;" + , "purs: Current maximum heap size is 3221225472 bytes (3072 MB)." + , "JSON: Unexpected end of JSON input" + ] + Purs.isCompilerHeapExhaustion err `Assertions.shouldEqual` true + Purs.isCompilerRaceCondition err `Assertions.shouldEqual` false + Purs.isCompilerHeapExhaustion (UnknownError "JSON: Unexpected end of JSON input") `Assertions.shouldEqual` false + Spec.it "Does not detect race condition for normal UnknownError" do let err = UnknownError "Some other compiler error message" Purs.isCompilerRaceCondition err `Assertions.shouldEqual` false