mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[threading] IJPL-176722: Do not enable read access for runnables in runWithModalProgressBlocking
This violates the mutual exclusion principle of the RW lock, as within `runWithModalProgressBlocking` there can be an upgrade to WA GitOrigin-RevId: 7af9e9dae003cfe11f314a24ed764b9210e93b4d
This commit is contained in:
committed by
intellij-monorepo-bot
parent
3be3c53d34
commit
c8c6becc72
+30
-3
@@ -142,6 +142,9 @@ internal object AnyThreadWriteThreadingSupport: ThreadingSupport {
|
||||
@Volatile
|
||||
private var myWriteAcquired: Thread? = null
|
||||
|
||||
@Volatile
|
||||
private var myWriteIntentAcquired: Thread? = null
|
||||
|
||||
override fun getPermitAsContextElement(baseContext: CoroutineContext, shared: Boolean): CoroutineContext {
|
||||
if (!isLockStoredInContext) {
|
||||
return EmptyCoroutineContext
|
||||
@@ -247,7 +250,10 @@ internal object AnyThreadWriteThreadingSupport: ThreadingSupport {
|
||||
}
|
||||
|
||||
when (ts.permit) {
|
||||
null -> ts.acquire(getWriteIntentPermit())
|
||||
null -> {
|
||||
ts.acquire(getWriteIntentPermit())
|
||||
myWriteIntentAcquired = Thread.currentThread()
|
||||
}
|
||||
is ReadPermit -> error("WriteIntentReadAction can not be called from ReadAction")
|
||||
is WriteIntentPermit -> {
|
||||
// Volatile read
|
||||
@@ -271,6 +277,7 @@ internal object AnyThreadWriteThreadingSupport: ThreadingSupport {
|
||||
ThreadingAssertions.setImplicitLockOnEDT(prevImplicitLock)
|
||||
if (release) {
|
||||
ts.release()
|
||||
myWriteIntentAcquired = null
|
||||
}
|
||||
if (releaseSecondary) {
|
||||
mySecondaryPermits.get().removeLast().release()
|
||||
@@ -282,10 +289,30 @@ internal object AnyThreadWriteThreadingSupport: ThreadingSupport {
|
||||
|
||||
override fun isWriteIntentLocked(): Boolean {
|
||||
val ts = myState.get()
|
||||
return ts.hasWrite || ts.hasWriteIntent
|
||||
val shared = ts.sharedLock
|
||||
if (shared == null) {
|
||||
return ts.hasWrite || ts.hasWriteIntent
|
||||
}
|
||||
else {
|
||||
return myWriteIntentAcquired == Thread.currentThread()
|
||||
}
|
||||
}
|
||||
|
||||
override fun isReadAccessAllowed(): Boolean = getThreadState().hasPermit
|
||||
override fun isReadAccessAllowed(): Boolean {
|
||||
val threadState = getThreadState()
|
||||
val shared = threadState.sharedLock
|
||||
if (shared == null) {
|
||||
// Having any permit (r/w/wi) without the presence of the second lock means that this thread has _some_ permission,
|
||||
// and even the weakest possible permission allows read access
|
||||
return threadState.hasPermit
|
||||
}
|
||||
else {
|
||||
// When there is the second lock installed, it is not enough to look at the primary lock:
|
||||
// the current thread now has inherited write intent permit, which should not give read access.
|
||||
// Otherwise, it would be impossible to upgrade WI to W
|
||||
return !mySecondaryPermits.get().isNullOrEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
override fun executeOnPooledThread(action: Runnable, expired: BooleanSupplier): Future<*> {
|
||||
val actionDecorated = decorateRunnable(action)
|
||||
|
||||
+1
-1
@@ -402,7 +402,7 @@ public class ApplicationImplTest extends LightPlatformTestCase {
|
||||
ProgressManager.getInstance().runProcessWithProgressSynchronously((ThrowableComputable<Void, Exception>)() -> {
|
||||
try {
|
||||
assertFalse(ApplicationManager.getApplication().holdsReadLock());
|
||||
assertEquals(isLockStoredInContext(), ApplicationManager.getApplication().isReadAccessAllowed());
|
||||
assertFalse(ApplicationManager.getApplication().isReadAccessAllowed());
|
||||
assertFalse(ApplicationManager.getApplication().isDispatchThread());
|
||||
ApplicationManager.getApplication().assertIsNonDispatchThread();
|
||||
|
||||
|
||||
+52
@@ -26,6 +26,7 @@ import org.junit.jupiter.api.assertThrows
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.lang.Runnable
|
||||
import kotlin.coroutines.ContinuationInterceptor
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@@ -446,6 +447,57 @@ class RunWithModalProgressBlockingTest : ModalCoroutineTest() {
|
||||
assertTrue { application.isReadAccessAllowed }
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("ForbiddenInSuspectContextMethod")
|
||||
@Test
|
||||
fun `RA and WA are mutually exclusive inside modal progress`(): Unit = timeoutRunBlocking(context = Dispatchers.EDT) {
|
||||
val writeCoroutineStarted = Job()
|
||||
val writeActionCanStart = Job()
|
||||
|
||||
val inWaCounter = AtomicBoolean(false)
|
||||
|
||||
writeIntentReadAction {
|
||||
runWithModalProgressBlocking {
|
||||
launch(Dispatchers.EDT) {
|
||||
writeCoroutineStarted.complete()
|
||||
writeActionCanStart.join()
|
||||
writeAction {
|
||||
inWaCounter.set(true)
|
||||
}
|
||||
}
|
||||
ReadAction.nonBlocking<Unit> {
|
||||
writeCoroutineStarted.asCompletableFuture().join()
|
||||
writeActionCanStart.complete()
|
||||
Thread.sleep(100)
|
||||
assertFalse(inWaCounter.get())
|
||||
}.executeSynchronously()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `read access is not allowed by default within modal progress`() = timeoutRunBlocking(context = Dispatchers.EDT) {
|
||||
writeIntentReadAction {
|
||||
runWithModalProgressBlocking {
|
||||
assertFalse(ApplicationManager.getApplication().isReadAccessAllowed)
|
||||
readAction {
|
||||
assertTrue(ApplicationManager.getApplication().isReadAccessAllowed)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `write intent read access is not allowed by default within modal progress`() = timeoutRunBlocking(context = Dispatchers.EDT) {
|
||||
writeIntentReadAction {
|
||||
runWithModalProgressBlocking {
|
||||
assertFalse(ApplicationManager.getApplication().isWriteIntentLockAcquired)
|
||||
withContext(Dispatchers.EDT) {
|
||||
assertTrue(ApplicationManager.getApplication().isWriteIntentLockAcquired)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun CoroutineScope.runWithModalProgressBlockingCoroutine(action: suspend CoroutineScope.() -> Unit): Job {
|
||||
|
||||
Reference in New Issue
Block a user