mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-13 06:47:40 +07:00
IJPL-165058 Further improvements to AsyncRendering - make sure non-suspending renderers do not spin hundreds of threads
(cherry picked from commit 4dd4fa0920ad178d5ae6a0809b27d034160b4a94) IJ-CR-151474 GitOrigin-RevId: 674f829893359bb8f4e6f6e4bac59abf47075f89
This commit is contained in:
committed by
intellij-monorepo-bot
parent
050782b5ea
commit
ebac977c88
@@ -7,6 +7,11 @@ abstract class SuspendingLookupElementRenderer<T : LookupElement> : LookupElemen
|
||||
/**
|
||||
* Render LookupElement in a coroutine. There is no guarantee that the element is valid.
|
||||
* The method will usually be called without a read lock.
|
||||
*
|
||||
* Avoid changing the dispatcher on which the coroutine runs, as this may cause
|
||||
* spawning of hundreds of worker threads on unlimited dispatchers.
|
||||
* E.g. `withContext`, `readAction`, `runBlocking`, `runBlockingCancellable` may move
|
||||
* the coroutine to a different dispatcher.
|
||||
*/
|
||||
abstract suspend fun renderElementSuspending(element: T, presentation: LookupElementPresentation)
|
||||
|
||||
|
||||
@@ -11,6 +11,10 @@ import com.intellij.openapi.application.readAction
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.util.indexing.DumbModeAccessType
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.Semaphore
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.sync.withPermit
|
||||
|
||||
internal class AsyncRendering(private val lookup: LookupImpl) {
|
||||
companion object {
|
||||
@@ -31,6 +35,9 @@ internal class AsyncRendering(private val lookup: LookupImpl) {
|
||||
}
|
||||
}
|
||||
|
||||
private val nonSuspendingRenderersMutex = Mutex(false)
|
||||
private val suspendingRenderersSemaphore = Semaphore(10)
|
||||
|
||||
fun getLastComputed(element: LookupElement): LookupElementPresentation = element.getUserData(LAST_COMPUTED_PRESENTATION)!!
|
||||
|
||||
fun scheduleRendering(element: LookupElement, renderer: LookupElementRenderer<LookupElement>) {
|
||||
@@ -44,13 +51,25 @@ internal class AsyncRendering(private val lookup: LookupImpl) {
|
||||
val job = lookup.coroutineScope.launch(limitedDispatcher) {
|
||||
val job = coroutineContext.job
|
||||
if (renderer is SuspendingLookupElementRenderer<LookupElement>) {
|
||||
renderInBackgroundSuspending(element, renderer)
|
||||
} else
|
||||
readAction {
|
||||
if (element.isValid) {
|
||||
renderInBackground(element, renderer)
|
||||
// Suspending renderers work on the `limitedDispatcher`, so there is no need to limit the throughput.
|
||||
// However, the user code may still move the coroutine to a different dispatcher, so just in case
|
||||
// limit the throughput.
|
||||
suspendingRenderersSemaphore.withPermit {
|
||||
renderInBackgroundSuspending(element, renderer)
|
||||
}
|
||||
} else {
|
||||
// `readAction` redirects the coroutine to the `Dispatcher.default` leaving `limitedDispatcher` free.
|
||||
// The next coroutine is then processed on the `limitedDispatcher` and so on. Ultimately, this can spin
|
||||
// a new dispatcher worker thread for almost each item on the list. We should only allow a single
|
||||
// non-suspending renderer to run within the read action.
|
||||
nonSuspendingRenderersMutex.withLock {
|
||||
readAction {
|
||||
if (element.isValid) {
|
||||
renderInBackground(element, renderer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
synchronized(LAST_COMPUTATION) {
|
||||
element.replace(LAST_COMPUTATION, job, null)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user