mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-13 06:47:40 +07:00
IJPL-172709 [ijent] Do not start coroutines directly in Unconfined dispatcher
Turns out that Unconfined dispatcher still installs an event loop, which was the main reason of avoiding `runBlocking` (cherry picked from commit 6c47ede47e1e0377504052a39bbceba3d2046c4e) GitOrigin-RevId: af77b1b6ba4c640ac2253b51603332392389a3ad
This commit is contained in:
committed by
intellij-monorepo-bot
parent
7d222ff6ed
commit
c3f76c2982
@@ -9,13 +9,9 @@ import com.intellij.platform.eel.fs.EelFsError
|
||||
import com.intellij.platform.eel.fs.EelOpenedFile
|
||||
import com.intellij.platform.eel.path.EelPath
|
||||
import com.intellij.util.text.nullize
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.io.IOException
|
||||
import java.nio.file.*
|
||||
import kotlin.Throws
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.startCoroutine
|
||||
|
||||
@Throws(FileSystemException::class)
|
||||
internal fun <T, E : EelFsError> EelResult<T, E>.getOrThrowFileSystemException(): T =
|
||||
@@ -58,51 +54,20 @@ internal fun Path.toEelPath(): EelPath =
|
||||
else -> EelPath.Relative.parse(toString())
|
||||
}
|
||||
|
||||
internal fun <T> fsBlocking(body: suspend () -> T): T = invokeSuspending(body)
|
||||
|
||||
/**
|
||||
* Runs a suspending IO operation [block] in non-suspending code.
|
||||
* Normally, [kotlinx.coroutines.runBlocking] should be used in such cases,
|
||||
* but it has significant performance overhead: creation and installation of an [kotlinx.coroutines.EventLoop].
|
||||
* We need to use a plain `runBlocking` here.
|
||||
* The IO call is supposed to be fast (several milliseconds in the worst case),
|
||||
* so the cost of spawning and destroying an additional thread in Dispatchers.Default would be too big.
|
||||
* Also, IJent does not require any outer lock in its implementation, so a deadlock is not possible.
|
||||
*
|
||||
* Unfortunately, the execution of [block] may still launch coroutines, although they are very primitive.
|
||||
* To mitigate this, we use [Dispatchers.Unconfined] as an elementary event loop.
|
||||
* It does not change the final thread of execution,
|
||||
* as we are waiting for a monitor on the same thread where [invokeSuspending] was called.
|
||||
*
|
||||
* We manage to save up to 30% (300 microseconds) of performance cost in comparison with [kotlinx.coroutines.runBlocking],
|
||||
* which is important in case of many short IO operations.
|
||||
*
|
||||
* The invoked operation is non-cancellable, as one can expect from regular native-based IO calls.
|
||||
*
|
||||
* @see com.intellij.openapi.application.impl.runSuspend
|
||||
* In addition, we suppress work stealing in this `runBlocking`, as it should return as fast as it can on its own.
|
||||
*/
|
||||
private fun <T> invokeSuspending(block: suspend () -> T): T {
|
||||
val run = RunSuspend<T>()
|
||||
block.startCoroutine(run)
|
||||
return run.await()
|
||||
@Suppress("SSBasedInspection")
|
||||
internal fun <T> fsBlocking(body: suspend () -> T): T = runBlocking(NestedBlockingEventLoop(Thread.currentThread())) {
|
||||
body()
|
||||
}
|
||||
|
||||
private class RunSuspend<T> : Continuation<T> {
|
||||
override val context: CoroutineContext = Dispatchers.Unconfined
|
||||
|
||||
var result: Result<T>? = null
|
||||
|
||||
override fun resumeWith(result: Result<T>) = synchronized(this) {
|
||||
this.result = result
|
||||
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") (this as Object).notifyAll()
|
||||
}
|
||||
|
||||
fun await(): T {
|
||||
synchronized(this) {
|
||||
while (true) {
|
||||
when (val result = this.result) {
|
||||
null -> @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") (this as Object).wait()
|
||||
else -> {
|
||||
return result.getOrThrow() // throw up failure
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "CANNOT_OVERRIDE_INVISIBLE_MEMBER", "ERROR_SUPPRESSION")
|
||||
private class NestedBlockingEventLoop(override val thread: Thread) : kotlinx.coroutines.EventLoopImplBase() {
|
||||
override fun shouldBeProcessedFromContext(): Boolean = true
|
||||
}
|
||||
Reference in New Issue
Block a user