From 881963a0a805d26fd5c4de23abec7739e5f4d3bf Mon Sep 17 00:00:00 2001 From: Konstantin Nisht Date: Wed, 18 Jun 2025 09:14:59 +0200 Subject: [PATCH] [threading] RUBY-34356: Throw PCE if lock acquisition was promptly canceled in blocking context GitOrigin-RevId: dd0b5331b316033255115dc3ba65785ca3f534ba --- .../src/NestedLocksThreadingSupport.kt | 13 ++++++-- .../CancellationPropagationTest.kt | 32 +++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/platform/locking.impl/src/NestedLocksThreadingSupport.kt b/platform/locking.impl/src/NestedLocksThreadingSupport.kt index ad114036cae6..b5c6bfb6bd8a 100644 --- a/platform/locking.impl/src/NestedLocksThreadingSupport.kt +++ b/platform/locking.impl/src/NestedLocksThreadingSupport.kt @@ -8,6 +8,7 @@ import com.intellij.core.rwmutex.* import com.intellij.openapi.application.* import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.progress.Cancellation +import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.openapi.util.text.StringUtil import com.intellij.platform.locking.impl.listeners.ErrorHandler import com.intellij.platform.locking.impl.listeners.LegacyProgressIndicatorProvider @@ -925,8 +926,16 @@ class NestedLocksThreadingSupport : ThreadingSupport { finally { writeLockInitResult.release() } - } catch (e : Throwable) { - throw e + } + catch (e: CancellationException) { + val job = currentThreadContext()[Job] + if (job != null && job.isCancelled && e !is ProcessCanceledException) { + // the lock acquisition was promptly canceled, so we need to rethrow a PCE from here to comply with blocking context + throw ProcessCanceledException(e) + } + else { + throw e + } } finally { writeIntentInitResult.release() diff --git a/platform/platform-tests/testSrc/com/intellij/util/concurrency/CancellationPropagationTest.kt b/platform/platform-tests/testSrc/com/intellij/util/concurrency/CancellationPropagationTest.kt index 740c9abdd9c6..646c989f2b49 100644 --- a/platform/platform-tests/testSrc/com/intellij/util/concurrency/CancellationPropagationTest.kt +++ b/platform/platform-tests/testSrc/com/intellij/util/concurrency/CancellationPropagationTest.kt @@ -15,6 +15,7 @@ import com.intellij.openapi.application.impl.assertReferenced import com.intellij.openapi.application.impl.pumpEDT import com.intellij.openapi.application.impl.withModality import com.intellij.openapi.application.readAction +import com.intellij.openapi.application.runWriteAction import com.intellij.openapi.progress.* import com.intellij.openapi.util.Condition import com.intellij.openapi.util.Conditions @@ -28,6 +29,7 @@ import com.intellij.util.application import com.intellij.util.getValue import com.intellij.util.setValue import kotlinx.coroutines.* +import kotlinx.coroutines.future.asCompletableFuture import org.jetbrains.concurrency.AsyncPromise import org.jetbrains.concurrency.asCancellablePromise import org.junit.jupiter.api.Assertions.* @@ -873,6 +875,36 @@ class CancellationPropagationTest { job.cancel() } + @Test + fun `invokeAndWait propagates cancellation 2`(): Unit = timeoutRunBlocking(context = Dispatchers.Default) { + val job1 = Job(coroutineContext.job) + val job2 = Job(coroutineContext.job) + val job3 = Job(coroutineContext.job) + launch { + readAction { + job1.complete() + job2.asCompletableFuture().join() + } + } + job1.join() + val job = launch(Dispatchers.Default) { + blockingContext { + application.invokeAndWait { + job3.complete() + runWriteAction { + } + } + } + } + job3.join() + delay(100) + job.cancelAndJoin() + job2.cancelAndJoin() + } + + + + @Test fun `coroutine non-cancellable section coherence`() = timeoutRunBlocking { withContext(Dispatchers.Default) {