mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-15 11:53:49 +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.
|
* Render LookupElement in a coroutine. There is no guarantee that the element is valid.
|
||||||
* The method will usually be called without a read lock.
|
* 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)
|
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.openapi.util.Key
|
||||||
import com.intellij.util.indexing.DumbModeAccessType
|
import com.intellij.util.indexing.DumbModeAccessType
|
||||||
import kotlinx.coroutines.*
|
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) {
|
internal class AsyncRendering(private val lookup: LookupImpl) {
|
||||||
companion object {
|
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 getLastComputed(element: LookupElement): LookupElementPresentation = element.getUserData(LAST_COMPUTED_PRESENTATION)!!
|
||||||
|
|
||||||
fun scheduleRendering(element: LookupElement, renderer: LookupElementRenderer<LookupElement>) {
|
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 = lookup.coroutineScope.launch(limitedDispatcher) {
|
||||||
val job = coroutineContext.job
|
val job = coroutineContext.job
|
||||||
if (renderer is SuspendingLookupElementRenderer<LookupElement>) {
|
if (renderer is SuspendingLookupElementRenderer<LookupElement>) {
|
||||||
renderInBackgroundSuspending(element, renderer)
|
// Suspending renderers work on the `limitedDispatcher`, so there is no need to limit the throughput.
|
||||||
} else
|
// However, the user code may still move the coroutine to a different dispatcher, so just in case
|
||||||
readAction {
|
// limit the throughput.
|
||||||
if (element.isValid) {
|
suspendingRenderersSemaphore.withPermit {
|
||||||
renderInBackground(element, renderer)
|
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) {
|
synchronized(LAST_COMPUTATION) {
|
||||||
element.replace(LAST_COMPUTATION, job, null)
|
element.replace(LAST_COMPUTATION, job, null)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user