[2.x] fix: Handle CancellationException gracefully with usePipelining (#8718)

When usePipelining is enabled and compilation has errors, CancellationException
was being thrown and showing confusing stack traces to users. This fix catches
the exception in ConcurrentRestrictions.take() and converts it to Incomplete,
which is properly handled by the task execution framework without showing stack
traces.

- Added CancellationException import
- Wrapped jservice.take().get() in try-catch
- Convert CancellationException to Incomplete to prevent stack traces
- Added scripted test to verify the fix

Fixes #7973
This commit is contained in:
PandaMan 2026-02-09 02:28:54 -05:00 committed by GitHub
parent f47afb5b49
commit b2fea15030
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 34 additions and 2 deletions

View File

@ -0,0 +1,8 @@
// Test for issue #7973: CancellationException should not be thrown on compile errors with usePipelining
ThisBuild / usePipelining := true
lazy val root = (project in file("."))
.settings(
name := "usePipelining-cancellation",
scalaVersion := "2.13.15"
)

View File

@ -0,0 +1,7 @@
object A {
def invalidSyntax {
// Missing closing brace - this will cause compile errors
val x = 1
// }
}

View File

@ -0,0 +1,7 @@
# Test for issue #7973: CancellationException should not be thrown on compile errors with usePipelining
# This verifies that compile errors with usePipelining enabled don't show CancellationException stack traces
# Try to compile (should fail with compile errors, but not throw CancellationException)
# The fix ensures CancellationException is caught and handled gracefully
# Using -> to indicate expected failure
-> compile

View File

@ -12,7 +12,7 @@ import java.util.concurrent.atomic.AtomicInteger
import sbt.internal.util.AttributeKey
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.{ Future as JFuture, RejectedExecutionException }
import java.util.concurrent.{ Future as JFuture, RejectedExecutionException, CancellationException }
import scala.collection.mutable
import scala.jdk.CollectionConverters.*
@ -312,7 +312,17 @@ object ConcurrentRestrictions {
throw new RejectedExecutionException(
"Tried to get values for a closed completion service"
)
jservice.take().get()
try {
jservice.take().get()
} catch {
case ce: CancellationException =>
// When tasks are cancelled (e.g., due to compile errors with usePipelining),
// the future's get() throws CancellationException. Convert this to an Incomplete
// to avoid showing stack traces to users. The cancellation is already handled
// by the task execution framework, so we just need to prevent the exception
// from propagating as an unhandled exception.
throw Incomplete(node = None, message = Some("cancelled"), directCause = Some(ce))
}
}
}
}