diff --git a/app/src/App/CLI/Purs.purs b/app/src/App/CLI/Purs.purs index bece27e6..b32d1801 100644 --- a/app/src/App/CLI/Purs.purs +++ b/app/src/App/CLI/Purs.purs @@ -191,10 +191,17 @@ callCompiler compilerArgs = do compilerRaceConditionError :: String compilerRaceConditionError = "Unexpected end of JSON input" +-- | We keep heap exhaustion classified as an unknown error rather than expanding +-- | CompilerFailure because only niche recovery paths need to distinguish it. +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..a9cdd485 100644 --- a/app/src/App/Effect/PackageSets.purs +++ b/app/src/App/Effect/PackageSets.purs @@ -263,12 +263,22 @@ 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." + -- Clear the output directory to avoid an incremental build, but leave + -- output-backup alone so it remains available for rollback. + 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..f3af024f 100644 --- a/app/test/App/CLI/Purs.purs +++ b/app/test/App/CLI/Purs.purs @@ -54,6 +54,15 @@ spec = do let err = UnknownError "Some prefix text\nJSON: Unexpected end of JSON input\nSome suffix" Purs.isCompilerRaceCondition err `Assertions.shouldEqual` true + Spec.it "Does not detect race condition for heap exhaustion with empty JSON output" 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.isCompilerRaceCondition err `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