diff --git a/sbt-app/src/sbt-test/project/usePipelining-cancellation/build.sbt b/sbt-app/src/sbt-test/project/usePipelining-cancellation/build.sbt new file mode 100644 index 000000000..69344aa97 --- /dev/null +++ b/sbt-app/src/sbt-test/project/usePipelining-cancellation/build.sbt @@ -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" + ) diff --git a/sbt-app/src/sbt-test/project/usePipelining-cancellation/src/main/scala/A.scala b/sbt-app/src/sbt-test/project/usePipelining-cancellation/src/main/scala/A.scala new file mode 100644 index 000000000..465e4255e --- /dev/null +++ b/sbt-app/src/sbt-test/project/usePipelining-cancellation/src/main/scala/A.scala @@ -0,0 +1,7 @@ +object A { + def invalidSyntax { + // Missing closing brace - this will cause compile errors + val x = 1 + // } +} + diff --git a/sbt-app/src/sbt-test/project/usePipelining-cancellation/test b/sbt-app/src/sbt-test/project/usePipelining-cancellation/test new file mode 100644 index 000000000..5394963f4 --- /dev/null +++ b/sbt-app/src/sbt-test/project/usePipelining-cancellation/test @@ -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 diff --git a/tasks/src/main/scala/sbt/ConcurrentRestrictions.scala b/tasks/src/main/scala/sbt/ConcurrentRestrictions.scala index 1f35f8e7a..bb5ae2257 100644 --- a/tasks/src/main/scala/sbt/ConcurrentRestrictions.scala +++ b/tasks/src/main/scala/sbt/ConcurrentRestrictions.scala @@ -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)) + } } } }