diff --git a/platform/core-api/src/com/intellij/openapi/application/Application.java b/platform/core-api/src/com/intellij/openapi/application/Application.java index 651ec15b20ec..aaa62daa8bbf 100644 --- a/platform/core-api/src/com/intellij/openapi/application/Application.java +++ b/platform/core-api/src/com/intellij/openapi/application/Application.java @@ -687,7 +687,7 @@ public interface Application extends ComponentManager { @ApiStatus.Experimental @ApiStatus.Internal - default CoroutineContext getLockStateAsCoroutineContext(boolean shared) { + default CoroutineContext getLockStateAsCoroutineContext(CoroutineContext context, boolean shared) { return EmptyCoroutineContext.INSTANCE; } diff --git a/platform/core-api/src/com/intellij/openapi/application/ThreadingSupport.kt b/platform/core-api/src/com/intellij/openapi/application/ThreadingSupport.kt index 26a5699ac172..465f6d7ae8f7 100644 --- a/platform/core-api/src/com/intellij/openapi/application/ThreadingSupport.kt +++ b/platform/core-api/src/com/intellij/openapi/application/ThreadingSupport.kt @@ -319,7 +319,7 @@ interface ThreadingSupport { fun isInsideUnlockedWriteIntentLock(): Boolean @ApiStatus.Internal - fun getPermitAsContextElement(shared: Boolean): CoroutineContext + fun getPermitAsContextElement(baseContext: CoroutineContext, shared: Boolean): CoroutineContext @ApiStatus.Internal fun returnPermitFromContextElement(ctx: CoroutineContext) diff --git a/platform/core-api/src/com/intellij/openapi/progress/coroutines.kt b/platform/core-api/src/com/intellij/openapi/progress/coroutines.kt index 1e4eee2392a8..173e72fbe3ca 100644 --- a/platform/core-api/src/com/intellij/openapi/progress/coroutines.kt +++ b/platform/core-api/src/com/intellij/openapi/progress/coroutines.kt @@ -13,6 +13,8 @@ import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.util.Computable import com.intellij.openapi.util.IntellijInternalApi import com.intellij.platform.util.progress.* +import com.intellij.platform.util.progress.internalCreateRawHandleFromContextStepIfExistsAndFresh +import com.intellij.platform.util.progress.reportRawProgress import com.intellij.util.concurrency.BlockingJob import com.intellij.util.concurrency.annotations.RequiresBackgroundThread import com.intellij.util.concurrency.annotations.RequiresBlockingContext @@ -480,14 +482,20 @@ private fun assertBackgroundThreadOrWriteAction() { @IntellijInternalApi @Internal fun getLockPermitContext(forSharing: Boolean = false): CoroutineContext { + return getLockPermitContext(currentThreadContext(), forSharing) +} + +@IntellijInternalApi +@Internal +fun getLockPermitContext(baseContext: CoroutineContext, forSharing: Boolean): CoroutineContext { val application = ApplicationManager.getApplication() return if (application != null) { if (isLockStoredInContext) { if (EDT.isCurrentThreadEdt()) { - application.getLockStateAsCoroutineContext(forSharing) + LockLeakedFromEDTMarker + application.getLockStateAsCoroutineContext(baseContext, forSharing) + LockLeakedFromEDTMarker } else { - application.getLockStateAsCoroutineContext(forSharing) + application.getLockStateAsCoroutineContext(baseContext, forSharing) } } else if (application.isReadAccessAllowed) { diff --git a/platform/platform-impl/src/com/intellij/openapi/application/impl/AnyThreadWriteThreadingSupport.kt b/platform/platform-impl/src/com/intellij/openapi/application/impl/AnyThreadWriteThreadingSupport.kt index c47a1a30eecd..6bbfd670148f 100644 --- a/platform/platform-impl/src/com/intellij/openapi/application/impl/AnyThreadWriteThreadingSupport.kt +++ b/platform/platform-impl/src/com/intellij/openapi/application/impl/AnyThreadWriteThreadingSupport.kt @@ -139,12 +139,12 @@ internal object AnyThreadWriteThreadingSupport: ThreadingSupport { @Volatile private var myWriteAcquired: Thread? = null - override fun getPermitAsContextElement(shared: Boolean): CoroutineContext { + override fun getPermitAsContextElement(baseContext: CoroutineContext, shared: Boolean): CoroutineContext { if (!isLockStoredInContext) { return EmptyCoroutineContext } - val element = currentThreadContext()[LockStateContextElement] + val element = baseContext[LockStateContextElement] if (element?.threadState?.permit != null) { if (shared) { element.threadState.fork() diff --git a/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java b/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java index c2ec0b1aae3c..27f700206215 100644 --- a/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java @@ -1285,8 +1285,8 @@ public final class ApplicationImpl extends ClientAwareComponentManager } @Override - public CoroutineContext getLockStateAsCoroutineContext(boolean shared) { - return getThreadingSupport().getPermitAsContextElement(shared); + public CoroutineContext getLockStateAsCoroutineContext(CoroutineContext baseContext, boolean shared) { + return getThreadingSupport().getPermitAsContextElement(baseContext, shared); } @Override diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/progress/RunWithModalProgressBlockingTest.kt b/platform/platform-tests/testSrc/com/intellij/openapi/progress/RunWithModalProgressBlockingTest.kt index 255afce2379e..f48cc6534a48 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/progress/RunWithModalProgressBlockingTest.kt +++ b/platform/platform-tests/testSrc/com/intellij/openapi/progress/RunWithModalProgressBlockingTest.kt @@ -9,9 +9,12 @@ import com.intellij.openapi.application.ex.ApplicationEx import com.intellij.openapi.application.impl.LaterInvocator import com.intellij.openapi.application.impl.ModalCoroutineTest import com.intellij.openapi.application.impl.processApplicationQueue +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.service import com.intellij.platform.ide.progress.ModalTaskOwner import com.intellij.platform.ide.progress.runWithModalProgressBlocking import com.intellij.testFramework.common.timeoutRunBlocking +import com.intellij.util.application import com.intellij.util.concurrency.ImplicitBlockingContextTest import kotlinx.coroutines.* import kotlinx.coroutines.future.asCompletableFuture @@ -21,7 +24,6 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertDoesNotThrow import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.extension.ExtendWith -import java.lang.Runnable import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicInteger import kotlin.coroutines.ContinuationInterceptor @@ -427,6 +429,23 @@ class RunWithModalProgressBlockingTest : ModalCoroutineTest() { } } } + + @Suppress("ForbiddenInSuspectContextMethod") + @Test + fun `service initializer inherits read lock from context`(): Unit = timeoutRunBlocking(context = Dispatchers.EDT) { + runWithModalProgressBlocking { + ReadAction.nonBlocking { + application.service() + }.executeSynchronously() + } + } + + @Service + private class MyTestService { + init { + assertTrue { application.isReadAccessAllowed } + } + } } private fun CoroutineScope.runWithModalProgressBlockingCoroutine(action: suspend CoroutineScope.() -> Unit): Job { diff --git a/platform/service-container/src/com/intellij/serviceContainer/ComponentManagerImpl.kt b/platform/service-container/src/com/intellij/serviceContainer/ComponentManagerImpl.kt index f960d0af2e30..0fe889e74856 100644 --- a/platform/service-container/src/com/intellij/serviceContainer/ComponentManagerImpl.kt +++ b/platform/service-container/src/com/intellij/serviceContainer/ComponentManagerImpl.kt @@ -1676,7 +1676,7 @@ private fun runBlockingInitialization(action: suspend CoroutineScope.() -> X val contextForInitializer = (ctx.contextModality()?.asContextElement() ?: EmptyCoroutineContext) + // leak modality state into initialization coroutine (ctx[Job] ?: EmptyCoroutineContext) + // bind to caller Job - getLockPermitContext() + // capture whether the caller holds the read lock + getLockPermitContext(ctx, false) + // capture whether the caller holds the read lock (currentTemporaryThreadContextOrNull() ?: EmptyCoroutineContext) + // propagate modality state/CurrentlyInitializingInstance NestedBlockingEventLoop(Thread.currentThread()) // avoid processing events from outer runBlocking (if any) @Suppress("RAW_RUN_BLOCKING")