[threading] IJPL-211115: Do not confine write actions to the limited dispatcher thread

GitOrigin-RevId: c43c94b56fd5970012966bacd90ca08717df2d8b
This commit is contained in:
Konstantin Nisht
2025-10-03 15:16:43 +00:00
committed by intellij-monorepo-bot
parent 8bba46ffe1
commit 15ca1be9f9
3 changed files with 58 additions and 5 deletions
@@ -21,7 +21,6 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.IOException
import java.nio.file.Files
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.io.path.writeText
import kotlin.math.absoluteValue
import kotlin.random.Random
@@ -82,7 +81,7 @@ internal class PlatformReadWriteActionSupport : ReadWriteActionSupport {
executeWriteActionOnEdt(stamp, readResult.action)
}
else {
executeWriteActionOnBackgroundWithAtomicCheck(lock, stamp, undispatched, readResult.action)
executeWriteActionOnBackgroundWithAtomicCheck(lock, stamp, readResult.action)
}
if (writeResult !== retryMarker) {
@Suppress("UNCHECKED_CAST")
@@ -107,8 +106,8 @@ internal class PlatformReadWriteActionSupport : ReadWriteActionSupport {
}
}
private suspend fun <T> executeWriteActionOnBackgroundWithAtomicCheck(lock: ThreadingSupport, originalStamp: Long, undispatched: Boolean, action: () -> T): /*T or retryMarker */ Any? {
val dispatcher = if (undispatched) EmptyCoroutineContext else Dispatchers.Default
private suspend fun <T> executeWriteActionOnBackgroundWithAtomicCheck(lock: ThreadingSupport, originalStamp: Long, action: () -> T): /*T or retryMarker */ Any? {
val dispatcher = Dispatchers.Default
val ref = withContext(dispatcher + InternalThreading.RunInBackgroundWriteActionMarker) {
lock.runWriteActionWithCheckInWriteIntent(
{
@@ -0,0 +1,52 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.openapi.application.impl
import com.intellij.openapi.application.EDT
import com.intellij.openapi.application.readAndBackgroundWriteActionUndispatched
import com.intellij.openapi.progress.Cancellation
import com.intellij.openapi.progress.runBlockingMaybeCancellable
import com.intellij.testFramework.junit5.StressTestApplication
import com.intellij.tools.ide.metrics.benchmark.Benchmark
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.junit.jupiter.api.Test
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
@StressTestApplication
class ReadWriteActionPerformanceTest {
@Test
fun readWriteActionBenchmark() {
val dispatcher = Dispatchers.Default.limitedParallelism(1, "test limited dispatcher")
Benchmark.newBenchmark("undispatched readAndWriteAction") {
runBlockingMaybeCancellable {
val canEnd = AtomicBoolean(false)
launch(Dispatchers.Default) {
while (!canEnd.get()) {
launch(Dispatchers.EDT) {
} // constant spam with write-intents
}
}
repeat(100) { globalCounter ->
launch(dispatcher) {
val localCounter = AtomicInteger(0)
readAndBackgroundWriteActionUndispatched {
localCounter.incrementAndGet()
val currentTime = System.currentTimeMillis()
while (System.currentTimeMillis() - currentTime < 3) {
Cancellation.checkCancelled()
}
writeAction {
}
}
println("RA $globalCounter: $localCounter restarts")
}
}
canEnd.set(true)
}
}
.attempts(3)
.start()
}
}
@@ -14,6 +14,7 @@ import org.junit.jupiter.api.RepeatedTest
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import kotlin.test.assertContains
import kotlin.test.assertFalse
import kotlin.time.Duration.Companion.seconds
private const val REPETITIONS: Int = 100
@@ -231,7 +232,8 @@ class SuspendingReadAndWriteActionTest {
// contains because in debug mode coroutines append coroutine id
assertContains(Thread.currentThread().name, name)
writeAction {
assertContains(Thread.currentThread().name, name)
// DO NOT run write action in the inherited dispatcher; causes issues like `com.intellij.openapi.application.impl.ReadWriteActionPerformanceTest.readWriteActionBenchmark`
assertFalse { Thread.currentThread().name.contains(name) }
}
}
}