mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-08 15:09:39 +07:00
[ijent] Use a low-level primitive for calling suspending code from the non-suspending environment
GitOrigin-RevId: 7ce33b069efe90a379d2143ac3cbbc30eafaece1
This commit is contained in:
committed by
intellij-monorepo-bot
parent
9eea2c9f87
commit
5260f5edd3
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user