From 65d856f7bfb90a141808df3d4dc6c2f287e86446 Mon Sep 17 00:00:00 2001 From: Anastasia Katsman Date: Tue, 7 Oct 2025 16:39:42 +0200 Subject: [PATCH] [starter] LocalEventsFlow: exit postAndWaitProcessing early if there are no subscribers GitOrigin-RevId: 97a16726c3c8dfa15aab73cd2a967cceb1d9230f --- .../ide/starter/bus/local/LocalEventsFlow.kt | 59 ++++++++++--------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/plugins/performanceTesting/event-bus/src/com/intellij/tools/ide/starter/bus/local/LocalEventsFlow.kt b/plugins/performanceTesting/event-bus/src/com/intellij/tools/ide/starter/bus/local/LocalEventsFlow.kt index 7a25ce4c1963..b10d3c7483e7 100644 --- a/plugins/performanceTesting/event-bus/src/com/intellij/tools/ide/starter/bus/local/LocalEventsFlow.kt +++ b/plugins/performanceTesting/event-bus/src/com/intellij/tools/ide/starter/bus/local/LocalEventsFlow.kt @@ -32,6 +32,7 @@ class LocalEventsFlow : EventsFlow { unsubscribeNoLock(eventClass, subscriber) } } + private fun unsubscribeNoLock(eventClass: Class, subscriber: Any) { val eventClassName = eventClass.simpleName val subscriberName = getSubscriberObject(subscriber) @@ -84,41 +85,45 @@ class LocalEventsFlow : EventsFlow { } } } + if (subscribersForEvent.isNullOrEmpty()) { + return + } + val exceptions = CopyOnWriteArrayList() - (subscribersForEvent as? List>) - ?.map { subscriber -> - // In case the job is interrupted (e.g. due to timeout), the coroutine may enter Cancelling state - // and finish before the 'catch' block is executed. Using CompletableDeferred ensures we wait - // until either successful completion or proper exception handling has occurred. - val result = CompletableDeferred() - LOG.debug("Post event $eventClassName for $subscriber.") - // Launching a new coroutine for each subscriber - scope.launch(Dispatchers.IO) { - LOG.debug("Start execution $eventClassName for $subscriber") - // Enforces a timeout for the entire subscriber execution - withTimeout(subscriber.timeout) { - // Ensures the operation inside is interruptible — if the thread is blocked, - // it will be interrupted when the coroutine is cancelled (e.g. by timeout) - runInterruptible { - try { - runBlocking { - subscriber.callback(event) - } - result.complete(Unit) - } - catch (e: Throwable) { - exceptions.add(e) - result.complete(Unit) + val tasks = (subscribersForEvent as List>).map { subscriber -> + // In case the job is interrupted (e.g. due to timeout), the coroutine may enter Cancelling state + // and finish before the 'catch' block is executed. Using CompletableDeferred ensures we wait + // until either successful completion or proper exception handling has occurred. + val result = CompletableDeferred() + LOG.debug("Post event $eventClassName for $subscriber.") + // Launching a new coroutine for each subscriber + scope.launch(Dispatchers.IO) { + LOG.debug("Start execution $eventClassName for $subscriber") + // Enforces a timeout for the entire subscriber execution + withTimeout(subscriber.timeout) { + // Ensures the operation inside is interruptible — if the thread is blocked, + // it will be interrupted when the coroutine is cancelled (e.g. by timeout) + runInterruptible { + try { + runBlocking { + subscriber.callback(event) } + result.complete(Unit) + } + catch (e: Throwable) { + exceptions.add(e) + result.complete(Unit) } } - LOG.debug("Finished execution $eventClassName for $subscriber") } - return@map result + LOG.debug("Finished execution $eventClassName for $subscriber") } + return@map result + } + runBlocking { // awaitAll shouldn't be used as it would stop after the first exception from any coroutine - tasks?.forEach { + tasks.forEach { try { it.await() }