diff --git a/platform/platform-impl/src/com/intellij/openapi/application/rw/PlatformReadWriteActionSupport.kt b/platform/platform-impl/src/com/intellij/openapi/application/rw/PlatformReadWriteActionSupport.kt index 7b721a78dd2a..02b2ff0f39ad 100644 --- a/platform/platform-impl/src/com/intellij/openapi/application/rw/PlatformReadWriteActionSupport.kt +++ b/platform/platform-impl/src/com/intellij/openapi/application/rw/PlatformReadWriteActionSupport.kt @@ -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 executeWriteActionOnBackgroundWithAtomicCheck(lock: ThreadingSupport, originalStamp: Long, undispatched: Boolean, action: () -> T): /*T or retryMarker */ Any? { - val dispatcher = if (undispatched) EmptyCoroutineContext else Dispatchers.Default + private suspend fun executeWriteActionOnBackgroundWithAtomicCheck(lock: ThreadingSupport, originalStamp: Long, action: () -> T): /*T or retryMarker */ Any? { + val dispatcher = Dispatchers.Default val ref = withContext(dispatcher + InternalThreading.RunInBackgroundWriteActionMarker) { lock.runWriteActionWithCheckInWriteIntent( { diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/application/impl/ReadWriteActionPerformanceTest.kt b/platform/platform-tests/testSrc/com/intellij/openapi/application/impl/ReadWriteActionPerformanceTest.kt new file mode 100644 index 000000000000..333a86fb5ce5 --- /dev/null +++ b/platform/platform-tests/testSrc/com/intellij/openapi/application/impl/ReadWriteActionPerformanceTest.kt @@ -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() + } +} \ No newline at end of file diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/application/impl/SuspendingReadAndWriteActionTest.kt b/platform/platform-tests/testSrc/com/intellij/openapi/application/impl/SuspendingReadAndWriteActionTest.kt index da65840bbf45..77612879815b 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/application/impl/SuspendingReadAndWriteActionTest.kt +++ b/platform/platform-tests/testSrc/com/intellij/openapi/application/impl/SuspendingReadAndWriteActionTest.kt @@ -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) } } } }