From d799a086fd789b85e747ef4e24f7e28ecceac382 Mon Sep 17 00:00:00 2001 From: Sergei Vorobyov Date: Wed, 20 Mar 2024 18:22:49 +0100 Subject: [PATCH] IDEA-321510 [Gradle|Sync] fix: Gradle sync can be canceled during handling Gradle call completion. GitOrigin-RevId: f12b77ddd6e7febe461bb557c8088d589fd4bbb4 --- .../GradleBuildActionResultHandler.kt | 28 +++++++--- .../buildActionRunner/GradlePhasedSyncTest.kt | 51 +++++++++++++++++++ .../GradlePhasedSyncTestCase.kt | 23 +++++++++ 3 files changed, 95 insertions(+), 7 deletions(-) diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/buildActionRunner/GradleBuildActionResultHandler.kt b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/buildActionRunner/GradleBuildActionResultHandler.kt index c3a456a24523..cd24c1dfc39d 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/buildActionRunner/GradleBuildActionResultHandler.kt +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/buildActionRunner/GradleBuildActionResultHandler.kt @@ -65,18 +65,17 @@ class GradleBuildActionResultHandler( fun createProjectLoadedHandler(): IntermediateResultHandler { return IntermediateResultHandler { state -> - try { + runCancellable { onProjectLoaded(state) } - catch (e: ProcessCanceledException) { - resolverCtx.cancel() - } } } fun createBuildFinishedHandler(): IntermediateResultHandler { return IntermediateResultHandler { state -> - onBuildCompleted(state) + runCancellable { + onBuildCompleted(state) + } } } @@ -91,7 +90,9 @@ class GradleBuildActionResultHandler( override fun onComplete(result: Any?) { try { if (result is GradleModelHolderState) { - onBuildCompleted(result) + runCancellable { + onBuildCompleted(result) + } } } finally { @@ -101,7 +102,9 @@ class GradleBuildActionResultHandler( override fun onFailure(failure: GradleConnectionException) { try { - onBuildFailed(failure) + runCancellable { + onBuildFailed(failure) + } } finally { buildFinishWaiter.countDown() @@ -109,4 +112,15 @@ class GradleBuildActionResultHandler( } } } + + private fun runCancellable(action: () -> Unit) { + try { + if (!resolverCtx.isCancellationRequested) { + action() + } + } + catch (e: ProcessCanceledException) { + resolverCtx.cancel() + } + } } \ No newline at end of file diff --git a/plugins/gradle/testSources/org/jetbrains/plugins/gradle/service/buildActionRunner/GradlePhasedSyncTest.kt b/plugins/gradle/testSources/org/jetbrains/plugins/gradle/service/buildActionRunner/GradlePhasedSyncTest.kt index 91a20904d122..4f903a8032ac 100644 --- a/plugins/gradle/testSources/org/jetbrains/plugins/gradle/service/buildActionRunner/GradlePhasedSyncTest.kt +++ b/plugins/gradle/testSources/org/jetbrains/plugins/gradle/service/buildActionRunner/GradlePhasedSyncTest.kt @@ -1,6 +1,7 @@ // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.plugins.gradle.service.buildActionRunner +import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.openapi.util.Disposer import com.intellij.openapi.util.use import org.jetbrains.plugins.gradle.importing.BuildFinishedModel @@ -9,6 +10,8 @@ import org.jetbrains.plugins.gradle.importing.ProjectLoadedModel import org.jetbrains.plugins.gradle.importing.ProjectLoadedModelProvider import org.jetbrains.plugins.gradle.tooling.builder.ProjectPropertiesTestModelBuilder import org.junit.Test +import org.junit.jupiter.api.Assertions +import java.util.concurrent.atomic.AtomicBoolean class GradlePhasedSyncTest : GradlePhasedSyncTestCase() { @@ -71,4 +74,52 @@ class GradlePhasedSyncTest : GradlePhasedSyncTestCase() { buildCompletionAssertion.assertListenerState(1) { "Build action should be finished only once" } } } + + @Test + fun `test one-phased Gradle sync cancellation`() { + Disposer.newDisposable().use { disposable -> + val isSyncCancelled = AtomicBoolean(false) + + whenBuildCompleted(disposable) { + throw ProcessCanceledException() + } + + createSettingsFile("") + importProject(errorHandler = { _, _ -> + isSyncCancelled.set(true) + }) + + Assertions.assertTrue(isSyncCancelled.get()) { + "Gradle sync should be cancelled." + } + } + } + + @Test + fun `test two-phased Gradle sync cancellation`() { + Disposer.newDisposable().use { disposable -> + val isBuildFinished = AtomicBoolean(false) + val isSyncCancelled = AtomicBoolean(false) + + whenProjectLoaded(disposable) { + throw ProcessCanceledException() + } + whenBuildCompleted(disposable) { + isBuildFinished.set(true) + } + + createSettingsFile("") + importProject(errorHandler = { _, _ -> + isSyncCancelled.set(true) + }) + + Assertions.assertTrue(isSyncCancelled.get()) { + "Gradle sync should be cancelled during the project loaded action." + } + Assertions.assertFalse(isBuildFinished.get()) { + "Gradle sync should be cancelled during the project loaded action.\n" + + "Therefore the project build action shouldn't be completed." + } + } + } } \ No newline at end of file diff --git a/plugins/gradle/testSources/org/jetbrains/plugins/gradle/service/buildActionRunner/GradlePhasedSyncTestCase.kt b/plugins/gradle/testSources/org/jetbrains/plugins/gradle/service/buildActionRunner/GradlePhasedSyncTestCase.kt index 628aa27b3d5d..6b7402a43911 100644 --- a/plugins/gradle/testSources/org/jetbrains/plugins/gradle/service/buildActionRunner/GradlePhasedSyncTestCase.kt +++ b/plugins/gradle/testSources/org/jetbrains/plugins/gradle/service/buildActionRunner/GradlePhasedSyncTestCase.kt @@ -15,13 +15,36 @@ import org.jetbrains.plugins.gradle.service.project.GradleProjectResolverExtensi import org.jetbrains.plugins.gradle.service.project.ProjectResolverContext import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.atomic.AtomicInteger +import java.util.concurrent.atomic.AtomicReference abstract class GradlePhasedSyncTestCase : GradleImportingTestCase() { + private lateinit var syncErrorHandler: AtomicReference<(String, String?) -> Unit> + private val defaultSyncErrorHandler = { errorMessage: String, errorDetails: String? -> + super.handleImportFailure(errorMessage, errorDetails) + } + override fun setUp() { super.setUp() + myProject.registerServiceInstance(TestProjectResolverExtensionService::class.java, TestProjectResolverExtensionService()) GradleProjectResolverExtension.EP_NAME.point.registerExtension(TestProjectResolverExtension(), testRootDisposable) + + syncErrorHandler = AtomicReference(defaultSyncErrorHandler) + } + + fun importProject(errorHandler: (String, String?) -> Unit) { + syncErrorHandler.set(errorHandler) + try { + importProject() + } + finally { + syncErrorHandler.set(defaultSyncErrorHandler) + } + } + + override fun handleImportFailure(errorMessage: String, errorDetails: String?) { + syncErrorHandler.get()(errorMessage, errorDetails) } fun addToolingExtensionClasses(parentDisposable: Disposable, vararg toolingExtensionClasses: Class<*>) {