From f91650c42ff89f7dce5c80e9b1bd0cafa68ef31f Mon Sep 17 00:00:00 2001 From: Richard Li Date: Tue, 16 Apr 2024 17:30:43 -0700 Subject: [PATCH 1/4] Deprecate plugin scope tracking Plugin intersection scopes may cause plugin leaks https://plugins.jetbrains.com/docs/intellij/coroutine-scopes.html#do-not-use-intersection-scopes --- .../src/main/resources/META-INF/plugin.xml | 5 ---- .../jetbrains/core/coroutines/scopes.kt | 28 +++++++++++++++---- .../jetbrains/core/coroutines/ScopeTest.kt | 10 +++---- .../src/main/resources/META-INF/plugin.xml | 5 ---- .../main/resources/META-INF/plugin-shim.xml | 6 ---- .../META-INF/plugin-shim.xml | 4 --- 6 files changed, 27 insertions(+), 31 deletions(-) diff --git a/plugins/amazonq/src/main/resources/META-INF/plugin.xml b/plugins/amazonq/src/main/resources/META-INF/plugin.xml index b2c1921b1c5..544b826e343 100644 --- a/plugins/amazonq/src/main/resources/META-INF/plugin.xml +++ b/plugins/amazonq/src/main/resources/META-INF/plugin.xml @@ -13,9 +13,4 @@ aws.toolkit.core - - - - - diff --git a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/coroutines/scopes.kt b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/coroutines/scopes.kt index 119863c7da7..21f6eab9cbb 100644 --- a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/coroutines/scopes.kt +++ b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/coroutines/scopes.kt @@ -5,6 +5,7 @@ package software.aws.toolkits.jetbrains.core.coroutines import com.intellij.openapi.Disposable import com.intellij.openapi.application.Application +import com.intellij.openapi.components.Service import com.intellij.openapi.components.service import com.intellij.openapi.project.Project import com.intellij.openapi.util.Disposer @@ -19,16 +20,18 @@ import java.util.concurrent.CancellationException * * Use this if the coroutine needs to live past a project being closed or across projects such as an Application Service */ +@Deprecated("Application x plugin intersection scope should not be used https://plugins.jetbrains.com/docs/intellij/coroutine-scopes.html#use-service-scopes") fun applicationCoroutineScope(coroutineName: String): CoroutineScope = - PluginCoroutineScopeTracker.getInstance().applicationThreadPoolScope(coroutineName) + ApplicationPluginCoroutineScopeTracker.getInstance().applicationThreadPoolScope(coroutineName) /** * Coroutine scope that is tied to a project closing, or plugin unloading. Default to dispatching to background thread pool. * * Use this if the coroutine needs to live past a UI being closed, or tied to a project's life cycle such as a Project Service. */ +@Deprecated("Project x plugin intersection scope should not be used https://plugins.jetbrains.com/docs/intellij/coroutine-scopes.html#use-service-scopes") fun projectCoroutineScope(project: Project, coroutineName: String): CoroutineScope = - PluginCoroutineScopeTracker.getInstance(project).applicationThreadPoolScope(coroutineName) + ProjectPluginCoroutineScopeTracker.getInstance(project).applicationThreadPoolScope(coroutineName) /** * Coroutine scope that is tied to a disposable or a plugin unloading. Default to dispatching to background thread pool. @@ -38,9 +41,10 @@ fun projectCoroutineScope(project: Project, coroutineName: String): CoroutineSco * **Note: If a call lives past the closing of a UI such as kicking off a resource creation, use [projectCoroutineScope]. * Otherwise, the coroutine will be canceled when the UI is closed!** */ +@Deprecated("Coroutine scope should not be shared across entire plugin lifecycle https://plugins.jetbrains.com/docs/intellij/coroutine-scopes.html#use-service-scopes") fun disposableCoroutineScope(disposable: Disposable, coroutineName: String): CoroutineScope { check(disposable !is Project && disposable !is Application) { "disposable should not be a project or application" } - return PluginCoroutineScopeTracker.getInstance().applicationThreadPoolScope(coroutineName).also { + return ApplicationPluginCoroutineScopeTracker.getInstance().applicationThreadPoolScope(coroutineName).also { Disposer.register(disposable) { it.cancel(CancellationException("Parent disposable was disposed")) } @@ -65,15 +69,27 @@ inline fun T.projectCoroutineScope(project: Project): Coroutin inline fun T.disposableCoroutineScope(disposable: Disposable): CoroutineScope = disposableCoroutineScope(disposable, T::class.java.name) -class PluginCoroutineScopeTracker : Disposable { +@Service(Service.Level.APP) +class ApplicationPluginCoroutineScopeTracker : Disposable { @PublishedApi internal fun applicationThreadPoolScope(coroutineName: String): CoroutineScope = BackgroundThreadPoolScope(coroutineName, this) override fun dispose() {} companion object { - fun getInstance() = service() - fun getInstance(project: Project) = project.service() + fun getInstance() = service() + } +} + +@Service(Service.Level.PROJECT) +class ProjectPluginCoroutineScopeTracker : Disposable { + @PublishedApi + internal fun applicationThreadPoolScope(coroutineName: String): CoroutineScope = BackgroundThreadPoolScope(coroutineName, this) + + override fun dispose() {} + + companion object { + fun getInstance(project: Project) = project.service() } } diff --git a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/coroutines/ScopeTest.kt b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/coroutines/ScopeTest.kt index 7c443ac5e7a..2bd65e5c9a1 100644 --- a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/coroutines/ScopeTest.kt +++ b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/coroutines/ScopeTest.kt @@ -132,9 +132,9 @@ class ScopeTest { try { assertThat( listOf( - PluginCoroutineScopeTracker.getInstance(), - PluginCoroutineScopeTracker.getInstance(projectRule.project), - PluginCoroutineScopeTracker.getInstance(project2) + ApplicationPluginCoroutineScopeTracker.getInstance(), + ProjectPluginCoroutineScopeTracker.getInstance(projectRule.project), + ProjectPluginCoroutineScopeTracker.getInstance(project2) ) ).doesNotHaveDuplicates() } finally { @@ -154,8 +154,8 @@ class ScopeTest { private fun createFakePluginScope(componentManager: ComponentManager = ApplicationManager.getApplication()): Disposable { // We can't unload the real plugin in tests, so make another instance of the service and replace it for the tests - val tracker = PluginCoroutineScopeTracker() - componentManager.replaceService(PluginCoroutineScopeTracker::class.java, tracker, disposableRule.disposable) + val tracker = ApplicationPluginCoroutineScopeTracker() + componentManager.replaceService(ApplicationPluginCoroutineScopeTracker::class.java, tracker, disposableRule.disposable) return tracker } diff --git a/plugins/core/src/main/resources/META-INF/plugin.xml b/plugins/core/src/main/resources/META-INF/plugin.xml index b759aa1fb8d..cff38cf8e95 100644 --- a/plugins/core/src/main/resources/META-INF/plugin.xml +++ b/plugins/core/src/main/resources/META-INF/plugin.xml @@ -10,9 +10,4 @@ - - - - - diff --git a/plugins/toolkit/intellij/src/main/resources/META-INF/plugin-shim.xml b/plugins/toolkit/intellij/src/main/resources/META-INF/plugin-shim.xml index 80c52b7d556..cfdc2b29ca0 100644 --- a/plugins/toolkit/intellij/src/main/resources/META-INF/plugin-shim.xml +++ b/plugins/toolkit/intellij/src/main/resources/META-INF/plugin-shim.xml @@ -3,11 +3,5 @@ - - - - - - diff --git a/plugins/toolkit/jetbrains-gateway/resources-gatewayOnly/META-INF/plugin-shim.xml b/plugins/toolkit/jetbrains-gateway/resources-gatewayOnly/META-INF/plugin-shim.xml index 4b97b570f66..94c87cd6890 100644 --- a/plugins/toolkit/jetbrains-gateway/resources-gatewayOnly/META-INF/plugin-shim.xml +++ b/plugins/toolkit/jetbrains-gateway/resources-gatewayOnly/META-INF/plugin-shim.xml @@ -4,8 +4,4 @@ - - - - From 2f7513e9921df72089d97ad88679d130dc1fba90 Mon Sep 17 00:00:00 2001 From: Richard Li Date: Wed, 17 Apr 2024 00:20:39 -0700 Subject: [PATCH 2/4] move to injected scope --- .../jetbrains/core/coroutines/scopes.kt | 28 ++++++------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/coroutines/scopes.kt b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/coroutines/scopes.kt index 21f6eab9cbb..e01f9316404 100644 --- a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/coroutines/scopes.kt +++ b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/coroutines/scopes.kt @@ -11,8 +11,8 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.util.Disposer import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel +import kotlinx.coroutines.plus import java.util.concurrent.CancellationException /** @@ -41,7 +41,9 @@ fun projectCoroutineScope(project: Project, coroutineName: String): CoroutineSco * **Note: If a call lives past the closing of a UI such as kicking off a resource creation, use [projectCoroutineScope]. * Otherwise, the coroutine will be canceled when the UI is closed!** */ -@Deprecated("Coroutine scope should not be shared across entire plugin lifecycle https://plugins.jetbrains.com/docs/intellij/coroutine-scopes.html#use-service-scopes") +@Deprecated( + "Coroutine scope should not be shared across entire plugin lifecycle https://plugins.jetbrains.com/docs/intellij/coroutine-scopes.html#use-service-scopes" +) fun disposableCoroutineScope(disposable: Disposable, coroutineName: String): CoroutineScope { check(disposable !is Project && disposable !is Application) { "disposable should not be a project or application" } return ApplicationPluginCoroutineScopeTracker.getInstance().applicationThreadPoolScope(coroutineName).also { @@ -70,11 +72,9 @@ inline fun T.disposableCoroutineScope(disposable: Disposable): disposableCoroutineScope(disposable, T::class.java.name) @Service(Service.Level.APP) -class ApplicationPluginCoroutineScopeTracker : Disposable { +class ApplicationPluginCoroutineScopeTracker(private val cs: CoroutineScope) { @PublishedApi - internal fun applicationThreadPoolScope(coroutineName: String): CoroutineScope = BackgroundThreadPoolScope(coroutineName, this) - - override fun dispose() {} + internal fun applicationThreadPoolScope(coroutineName: String): CoroutineScope = cs + CoroutineName(coroutineName) + getCoroutineBgContext() companion object { fun getInstance() = service() @@ -82,23 +82,11 @@ class ApplicationPluginCoroutineScopeTracker : Disposable { } @Service(Service.Level.PROJECT) -class ProjectPluginCoroutineScopeTracker : Disposable { +class ProjectPluginCoroutineScopeTracker(private val cs: CoroutineScope) { @PublishedApi - internal fun applicationThreadPoolScope(coroutineName: String): CoroutineScope = BackgroundThreadPoolScope(coroutineName, this) - - override fun dispose() {} + internal fun applicationThreadPoolScope(coroutineName: String): CoroutineScope = cs + CoroutineName(coroutineName) + getCoroutineBgContext() companion object { fun getInstance(project: Project) = project.service() } } - -private class BackgroundThreadPoolScope(coroutineName: String, disposable: Disposable) : CoroutineScope { - override val coroutineContext = SupervisorJob() + CoroutineName(coroutineName) + getCoroutineBgContext() - - init { - Disposer.register(disposable) { - coroutineContext.cancel(CancellationException("Parent disposable was disposed")) - } - } -} From 7867859742b3b42e491c3b787aec09ddc8b2eb3c Mon Sep 17 00:00:00 2001 From: Richard Li Date: Wed, 17 Apr 2024 11:48:05 -0700 Subject: [PATCH 3/4] tst --- .../jetbrains/core/coroutines/ScopeTest.kt | 46 +++++++++++++++---- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/coroutines/ScopeTest.kt b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/coroutines/ScopeTest.kt index 2bd65e5c9a1..5f0c1c22144 100644 --- a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/coroutines/ScopeTest.kt +++ b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/coroutines/ScopeTest.kt @@ -16,12 +16,17 @@ import com.intellij.testFramework.TestApplicationManager import com.intellij.testFramework.createTestOpenProjectOptions import com.intellij.testFramework.replaceService import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job import kotlinx.coroutines.async +import kotlinx.coroutines.cancel import kotlinx.coroutines.future.asCompletableFuture import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext +import kotlinx.coroutines.yield import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy +import org.junit.After +import org.junit.Before import org.junit.Ignore import org.junit.Rule import org.junit.Test @@ -51,9 +56,21 @@ class ScopeTest { @JvmField val testName = TestName() + private lateinit var applicationScopeDisposable: Disposable + + @Before + fun setUp() { + applicationScopeDisposable = createFakeApplicationProjectScope() + } + + @After + fun tearDown() { + Disposer.dispose(applicationScopeDisposable) + } + @Test fun `plugin being uploaded cancels application scope`() { - val fakePluginScope = createFakePluginScope() + val fakePluginScope = createFakeApplicationProjectScope() assertScopeIsCanceled(applicationCoroutineScope()) { Disposer.dispose(fakePluginScope) @@ -62,7 +79,7 @@ class ScopeTest { @Test fun `plugin being uploaded cancels project scope`() { - val fakePluginScope = createFakePluginScope(projectRule.project) + val fakePluginScope = createFakePluginProjectScope(projectRule.project) assertScopeIsCanceled(projectCoroutineScope(projectRule.project)) { Disposer.dispose(fakePluginScope) @@ -71,7 +88,7 @@ class ScopeTest { @Test fun `plugin being uploaded cancels disposable scope`() { - val fakePluginScope = createFakePluginScope() + val fakePluginScope = createFakeApplicationProjectScope() // Use fake disposable so we dont accidentally trigger false positive, nor disposable leak detector val fakeDisposable = Disposable { } @@ -152,13 +169,24 @@ class ScopeTest { assertThatThrownBy { disposableCoroutineScope(ApplicationManager.getApplication()) }.isInstanceOf() } - private fun createFakePluginScope(componentManager: ComponentManager = ApplicationManager.getApplication()): Disposable { + private fun createFakePluginScope(componentManager: ComponentManager = ApplicationManager.getApplication(), tracker: Class): Disposable { // We can't unload the real plugin in tests, so make another instance of the service and replace it for the tests - val tracker = ApplicationPluginCoroutineScopeTracker() - componentManager.replaceService(ApplicationPluginCoroutineScopeTracker::class.java, tracker, disposableRule.disposable) - return tracker + val disposable = Disposer.newDisposable() + val scope = CoroutineScope(Job()) + Disposer.register(disposable) { + scope.cancel(CancellationException("Parent disposable was disposed")) + } + val trackerInstance = tracker.getConstructor(CoroutineScope::class.java).newInstance(scope) + componentManager.replaceService(tracker, trackerInstance, disposableRule.disposable) + return disposable } + private fun createFakeApplicationProjectScope(componentManager: ComponentManager = ApplicationManager.getApplication()) = + createFakePluginScope(componentManager, ApplicationPluginCoroutineScopeTracker::class.java) + + private fun createFakePluginProjectScope(componentManager: ComponentManager = ApplicationManager.getApplication()) = + createFakePluginScope(componentManager, ProjectPluginCoroutineScopeTracker::class.java) + private fun assertScopeIsCorrectThread(scope: CoroutineScope) { val ran = AtomicBoolean(false) runBlocking(scope.coroutineContext) { @@ -189,7 +217,9 @@ class ScopeTest { fun computeAsync() = scope.async { computationStarted.countDown() - cancelFired.await() + while (!cancelFired.await(10, TimeUnit.MILLISECONDS)) { + yield() + } doTask() } From 4c6e5a3f771cec9752716aae90995e089d081bda Mon Sep 17 00:00:00 2001 From: Richard Li Date: Thu, 18 Apr 2024 15:28:30 -0700 Subject: [PATCH 4/4] revert --- .../src/main/resources/META-INF/plugin.xml | 5 ++ .../jetbrains/core/coroutines/scopes.kt | 36 +++++++------ .../jetbrains/core/coroutines/ScopeTest.kt | 52 ++++--------------- .../src/main/resources/META-INF/plugin.xml | 5 ++ .../main/resources/META-INF/plugin-shim.xml | 6 +++ .../META-INF/plugin-shim.xml | 4 ++ 6 files changed, 52 insertions(+), 56 deletions(-) diff --git a/plugins/amazonq/src/main/resources/META-INF/plugin.xml b/plugins/amazonq/src/main/resources/META-INF/plugin.xml index 544b826e343..b2c1921b1c5 100644 --- a/plugins/amazonq/src/main/resources/META-INF/plugin.xml +++ b/plugins/amazonq/src/main/resources/META-INF/plugin.xml @@ -13,4 +13,9 @@ aws.toolkit.core + + + + + diff --git a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/coroutines/scopes.kt b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/coroutines/scopes.kt index e01f9316404..8873477a54a 100644 --- a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/coroutines/scopes.kt +++ b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/coroutines/scopes.kt @@ -5,14 +5,13 @@ package software.aws.toolkits.jetbrains.core.coroutines import com.intellij.openapi.Disposable import com.intellij.openapi.application.Application -import com.intellij.openapi.components.Service import com.intellij.openapi.components.service import com.intellij.openapi.project.Project import com.intellij.openapi.util.Disposer import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel -import kotlinx.coroutines.plus import java.util.concurrent.CancellationException /** @@ -22,7 +21,7 @@ import java.util.concurrent.CancellationException */ @Deprecated("Application x plugin intersection scope should not be used https://plugins.jetbrains.com/docs/intellij/coroutine-scopes.html#use-service-scopes") fun applicationCoroutineScope(coroutineName: String): CoroutineScope = - ApplicationPluginCoroutineScopeTracker.getInstance().applicationThreadPoolScope(coroutineName) + PluginCoroutineScopeTracker.getInstance().applicationThreadPoolScope(coroutineName) /** * Coroutine scope that is tied to a project closing, or plugin unloading. Default to dispatching to background thread pool. @@ -31,7 +30,7 @@ fun applicationCoroutineScope(coroutineName: String): CoroutineScope = */ @Deprecated("Project x plugin intersection scope should not be used https://plugins.jetbrains.com/docs/intellij/coroutine-scopes.html#use-service-scopes") fun projectCoroutineScope(project: Project, coroutineName: String): CoroutineScope = - ProjectPluginCoroutineScopeTracker.getInstance(project).applicationThreadPoolScope(coroutineName) + PluginCoroutineScopeTracker.getInstance(project).applicationThreadPoolScope(coroutineName) /** * Coroutine scope that is tied to a disposable or a plugin unloading. Default to dispatching to background thread pool. @@ -46,7 +45,7 @@ fun projectCoroutineScope(project: Project, coroutineName: String): CoroutineSco ) fun disposableCoroutineScope(disposable: Disposable, coroutineName: String): CoroutineScope { check(disposable !is Project && disposable !is Application) { "disposable should not be a project or application" } - return ApplicationPluginCoroutineScopeTracker.getInstance().applicationThreadPoolScope(coroutineName).also { + return PluginCoroutineScopeTracker.getInstance().applicationThreadPoolScope(coroutineName).also { Disposer.register(disposable) { it.cancel(CancellationException("Parent disposable was disposed")) } @@ -56,37 +55,44 @@ fun disposableCoroutineScope(disposable: Disposable, coroutineName: String): Cor /** * Version of [applicationCoroutineScope] the class name as the coroutine name. */ +@Deprecated("Application x plugin intersection scope should not be used https://plugins.jetbrains.com/docs/intellij/coroutine-scopes.html#use-service-scopes") inline fun T.applicationCoroutineScope(): CoroutineScope = applicationCoroutineScope(T::class.java.name) /** * Version of [projectCoroutineScope] the class name as the coroutine name. */ +@Deprecated("Project x plugin intersection scope should not be used https://plugins.jetbrains.com/docs/intellij/coroutine-scopes.html#use-service-scopes") inline fun T.projectCoroutineScope(project: Project): CoroutineScope = projectCoroutineScope(project, T::class.java.name) /** * Version of [disposableCoroutineScope] the class name as the coroutine name. */ +@Deprecated( + "Coroutine scope should not be shared across entire plugin lifecycle https://plugins.jetbrains.com/docs/intellij/coroutine-scopes.html#use-service-scopes" +) inline fun T.disposableCoroutineScope(disposable: Disposable): CoroutineScope = disposableCoroutineScope(disposable, T::class.java.name) -@Service(Service.Level.APP) -class ApplicationPluginCoroutineScopeTracker(private val cs: CoroutineScope) { +class PluginCoroutineScopeTracker : Disposable { @PublishedApi - internal fun applicationThreadPoolScope(coroutineName: String): CoroutineScope = cs + CoroutineName(coroutineName) + getCoroutineBgContext() + internal fun applicationThreadPoolScope(coroutineName: String): CoroutineScope = BackgroundThreadPoolScope(coroutineName, this) + + override fun dispose() {} companion object { - fun getInstance() = service() + fun getInstance() = service() + fun getInstance(project: Project) = project.service() } } -@Service(Service.Level.PROJECT) -class ProjectPluginCoroutineScopeTracker(private val cs: CoroutineScope) { - @PublishedApi - internal fun applicationThreadPoolScope(coroutineName: String): CoroutineScope = cs + CoroutineName(coroutineName) + getCoroutineBgContext() +private class BackgroundThreadPoolScope(coroutineName: String, disposable: Disposable) : CoroutineScope { + override val coroutineContext = SupervisorJob() + CoroutineName(coroutineName) + getCoroutineBgContext() - companion object { - fun getInstance(project: Project) = project.service() + init { + Disposer.register(disposable) { + coroutineContext.cancel(CancellationException("Parent disposable was disposed")) + } } } diff --git a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/coroutines/ScopeTest.kt b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/coroutines/ScopeTest.kt index 5f0c1c22144..7c443ac5e7a 100644 --- a/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/coroutines/ScopeTest.kt +++ b/plugins/core/jetbrains-community/tst/software/aws/toolkits/jetbrains/core/coroutines/ScopeTest.kt @@ -16,17 +16,12 @@ import com.intellij.testFramework.TestApplicationManager import com.intellij.testFramework.createTestOpenProjectOptions import com.intellij.testFramework.replaceService import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job import kotlinx.coroutines.async -import kotlinx.coroutines.cancel import kotlinx.coroutines.future.asCompletableFuture import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext -import kotlinx.coroutines.yield import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy -import org.junit.After -import org.junit.Before import org.junit.Ignore import org.junit.Rule import org.junit.Test @@ -56,21 +51,9 @@ class ScopeTest { @JvmField val testName = TestName() - private lateinit var applicationScopeDisposable: Disposable - - @Before - fun setUp() { - applicationScopeDisposable = createFakeApplicationProjectScope() - } - - @After - fun tearDown() { - Disposer.dispose(applicationScopeDisposable) - } - @Test fun `plugin being uploaded cancels application scope`() { - val fakePluginScope = createFakeApplicationProjectScope() + val fakePluginScope = createFakePluginScope() assertScopeIsCanceled(applicationCoroutineScope()) { Disposer.dispose(fakePluginScope) @@ -79,7 +62,7 @@ class ScopeTest { @Test fun `plugin being uploaded cancels project scope`() { - val fakePluginScope = createFakePluginProjectScope(projectRule.project) + val fakePluginScope = createFakePluginScope(projectRule.project) assertScopeIsCanceled(projectCoroutineScope(projectRule.project)) { Disposer.dispose(fakePluginScope) @@ -88,7 +71,7 @@ class ScopeTest { @Test fun `plugin being uploaded cancels disposable scope`() { - val fakePluginScope = createFakeApplicationProjectScope() + val fakePluginScope = createFakePluginScope() // Use fake disposable so we dont accidentally trigger false positive, nor disposable leak detector val fakeDisposable = Disposable { } @@ -149,9 +132,9 @@ class ScopeTest { try { assertThat( listOf( - ApplicationPluginCoroutineScopeTracker.getInstance(), - ProjectPluginCoroutineScopeTracker.getInstance(projectRule.project), - ProjectPluginCoroutineScopeTracker.getInstance(project2) + PluginCoroutineScopeTracker.getInstance(), + PluginCoroutineScopeTracker.getInstance(projectRule.project), + PluginCoroutineScopeTracker.getInstance(project2) ) ).doesNotHaveDuplicates() } finally { @@ -169,24 +152,13 @@ class ScopeTest { assertThatThrownBy { disposableCoroutineScope(ApplicationManager.getApplication()) }.isInstanceOf() } - private fun createFakePluginScope(componentManager: ComponentManager = ApplicationManager.getApplication(), tracker: Class): Disposable { + private fun createFakePluginScope(componentManager: ComponentManager = ApplicationManager.getApplication()): Disposable { // We can't unload the real plugin in tests, so make another instance of the service and replace it for the tests - val disposable = Disposer.newDisposable() - val scope = CoroutineScope(Job()) - Disposer.register(disposable) { - scope.cancel(CancellationException("Parent disposable was disposed")) - } - val trackerInstance = tracker.getConstructor(CoroutineScope::class.java).newInstance(scope) - componentManager.replaceService(tracker, trackerInstance, disposableRule.disposable) - return disposable + val tracker = PluginCoroutineScopeTracker() + componentManager.replaceService(PluginCoroutineScopeTracker::class.java, tracker, disposableRule.disposable) + return tracker } - private fun createFakeApplicationProjectScope(componentManager: ComponentManager = ApplicationManager.getApplication()) = - createFakePluginScope(componentManager, ApplicationPluginCoroutineScopeTracker::class.java) - - private fun createFakePluginProjectScope(componentManager: ComponentManager = ApplicationManager.getApplication()) = - createFakePluginScope(componentManager, ProjectPluginCoroutineScopeTracker::class.java) - private fun assertScopeIsCorrectThread(scope: CoroutineScope) { val ran = AtomicBoolean(false) runBlocking(scope.coroutineContext) { @@ -217,9 +189,7 @@ class ScopeTest { fun computeAsync() = scope.async { computationStarted.countDown() - while (!cancelFired.await(10, TimeUnit.MILLISECONDS)) { - yield() - } + cancelFired.await() doTask() } diff --git a/plugins/core/src/main/resources/META-INF/plugin.xml b/plugins/core/src/main/resources/META-INF/plugin.xml index cff38cf8e95..b759aa1fb8d 100644 --- a/plugins/core/src/main/resources/META-INF/plugin.xml +++ b/plugins/core/src/main/resources/META-INF/plugin.xml @@ -10,4 +10,9 @@ + + + + + diff --git a/plugins/toolkit/intellij/src/main/resources/META-INF/plugin-shim.xml b/plugins/toolkit/intellij/src/main/resources/META-INF/plugin-shim.xml index cfdc2b29ca0..80c52b7d556 100644 --- a/plugins/toolkit/intellij/src/main/resources/META-INF/plugin-shim.xml +++ b/plugins/toolkit/intellij/src/main/resources/META-INF/plugin-shim.xml @@ -3,5 +3,11 @@ + + + + + + diff --git a/plugins/toolkit/jetbrains-gateway/resources-gatewayOnly/META-INF/plugin-shim.xml b/plugins/toolkit/jetbrains-gateway/resources-gatewayOnly/META-INF/plugin-shim.xml index 94c87cd6890..4b97b570f66 100644 --- a/plugins/toolkit/jetbrains-gateway/resources-gatewayOnly/META-INF/plugin-shim.xml +++ b/plugins/toolkit/jetbrains-gateway/resources-gatewayOnly/META-INF/plugin-shim.xml @@ -4,4 +4,8 @@ + + + +