[ijent] Use a low-level primitive for calling suspending code from the non-suspending environment

GitOrigin-RevId: 7ce33b069efe90a379d2143ac3cbbc30eafaece1
This commit is contained in:
Konstantin.Nisht
2024-07-05 12:13:48 +02:00
committed by intellij-monorepo-bot
parent 9eea2c9f87
commit 5260f5edd3

View File

@@ -4,12 +4,15 @@ package com.intellij.platform.ijent.community.impl.nio
import com.intellij.openapi.progress.runBlockingMaybeCancellable
import com.intellij.platform.ijent.*
import com.intellij.platform.ijent.fs.*
import kotlinx.coroutines.isActive
import kotlinx.coroutines.*
import java.nio.file.FileStore
import java.nio.file.FileSystem
import java.nio.file.PathMatcher
import java.nio.file.WatchService
import java.nio.file.attribute.UserPrincipalLookupService
import kotlin.coroutines.Continuation
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.startCoroutine
class IjentNioFileSystem internal constructor(
private val fsProvider: IjentNioFileSystemProvider,
@@ -96,14 +99,58 @@ class IjentNioFileSystem internal constructor(
TODO("Not yet implemented")
}
internal fun <T> fsBlocking(body: suspend () -> T): T =
runBlockingMaybeCancellable {
body()
}
internal fun <T> fsBlocking(body: suspend () -> T): T = invokeSuspending(body)
private fun IjentPath.toNioPath(): IjentNioPath =
IjentNioPath(
ijentPath = this,
nioFs = this@IjentNioFileSystem,
)
/**
* 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].
*
* 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 awaiting 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
*/
private fun <T> invokeSuspending(block: suspend () -> T): T {
val run = RunSuspend<T>()
block.startCoroutine(run)
return run.await()
}
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
}
}
}
}
}
}
}