mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[threading] QD-10938: Leak read access to service initializer inside modal progress
GitOrigin-RevId: edd8b8f31320adfa04fc54827e1f146b763b6fb7
This commit is contained in:
committed by
intellij-monorepo-bot
parent
4c83dc3b61
commit
02933bbb0c
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+2
-2
@@ -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()
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
+20
-1
@@ -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<Unit> {
|
||||
application.service<MyTestService>()
|
||||
}.executeSynchronously()
|
||||
}
|
||||
}
|
||||
|
||||
@Service
|
||||
private class MyTestService {
|
||||
init {
|
||||
assertTrue { application.isReadAccessAllowed }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun CoroutineScope.runWithModalProgressBlockingCoroutine(action: suspend CoroutineScope.() -> Unit): Job {
|
||||
|
||||
+1
-1
@@ -1676,7 +1676,7 @@ private fun <X> 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")
|
||||
|
||||
Reference in New Issue
Block a user