[2.x] fix: Widen IOException catch in organicTask to cover result serialization (#9050)

**Problem**
When a file referenced during cache serialization is deleted between task
execution and cache storage, sbt crashes with an uncaught NoSuchFileException.
The IOException catch added in #8699 only wraps store.put(), but
Converter.toJsonUnsafe(result) and mkInput() also call
hashedVirtualFileRefToStr which calls Files.readAttributes — and these are
outside the try-catch.

**Solution**
Move the try/catch IOException to wrap the entire post-action cache storage
section, so NoSuchFileException from result serialization or cache key
computation is caught and handled gracefully (skip caching, return result).

Fixes #9044

Co-authored-by: bittoby <218712309+bittoby@users.noreply.github.co>
This commit is contained in:
BitToby 2026-04-09 23:44:08 -06:00 committed by GitHub
parent d765ba263a
commit e0bdb531f7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 29 additions and 29 deletions

View File

@ -82,40 +82,40 @@ object ActionCache:
case e: Exception =>
cacheEventLog.append(ActionCacheEvent.Error)
throw e
val json = Converter.toJsonUnsafe(result)
val normalizedOutputDir = outputDirectory.toAbsolutePath.normalize()
val uncacheableOutputs =
outputs.filter(f =>
f match
case vf if vf.id.endsWith(ActionCache.dirZipExt) =>
false
case _ =>
val outputPath = fileConverter.toPath(f).toAbsolutePath.normalize()
!outputPath.startsWith(normalizedOutputDir)
)
if uncacheableOutputs.nonEmpty then
cacheEventLog.append(ActionCacheEvent.Error)
logger.error(
s"Cannot cache task because its output files are outside the output directory: \n" +
uncacheableOutputs.mkString(" - ", "\n - ", "")
)
result
else
cacheEventLog.append(ActionCacheEvent.OnsiteTask)
val (input, valuePath) = mkInput(key, codeContentHash, extraHash, config.cacheVersion)
val valueFile = StringVirtualFile1(valuePath, CompactPrinter(json))
val newOutputs = Vector(valueFile) ++ outputs.toVector
try
try
val json = Converter.toJsonUnsafe(result)
val normalizedOutputDir = outputDirectory.toAbsolutePath.normalize()
val uncacheableOutputs =
outputs.filter(f =>
f match
case vf if vf.id.endsWith(ActionCache.dirZipExt) =>
false
case _ =>
val outputPath = fileConverter.toPath(f).toAbsolutePath.normalize()
!outputPath.startsWith(normalizedOutputDir)
)
if uncacheableOutputs.nonEmpty then
cacheEventLog.append(ActionCacheEvent.Error)
logger.error(
s"Cannot cache task because its output files are outside the output directory: \n" +
uncacheableOutputs.mkString(" - ", "\n - ", "")
)
result
else
cacheEventLog.append(ActionCacheEvent.OnsiteTask)
val (input, valuePath) = mkInput(key, codeContentHash, extraHash, config.cacheVersion)
val valueFile = StringVirtualFile1(valuePath, CompactPrinter(json))
val newOutputs = Vector(valueFile) ++ outputs.toVector
store.put(UpdateActionResultRequest(input, newOutputs, exitCode = 0)) match
case Right(cachedResult) =>
store.syncBlobs(cachedResult.outputFiles, outputDirectory)
result
case Left(e) => throw e
catch
case e: IOException =>
logger.debug(s"Skipping cache storage due to error: ${e.getMessage}")
cacheEventLog.append(ActionCacheEvent.Error)
result
catch
case e: IOException =>
logger.debug(s"Skipping cache storage due to error: ${e.getMessage}")
cacheEventLog.append(ActionCacheEvent.Error)
result
// Single cache lookup - use exitCode to distinguish success from failure
getWithFailure(key, codeContentHash, extraHash, tags, config) match