[find in files] IJPL-186012 Improve RPC reliability and module state handling

- Add RpcCallUtil with retry mechanism for handling RPC timeouts
- Refactor ModuleUpdatedEvent to use map-based approach for batch module operations
- Streamline ModulesStateService initialization and event subscription

GitOrigin-RevId: 6847e95107abc3776f2e8a706a5d840d31ab00d0
This commit is contained in:
Vera Petrenkova
2025-05-26 19:00:33 +02:00
committed by intellij-monorepo-bot
parent cbc828ab48
commit 3fa289a400
11 changed files with 67 additions and 32 deletions

View File

@@ -52,7 +52,7 @@ internal class FindRemoteApiImpl : FindRemoteApi {
return@coroutineScope
}
val filesToScanInitially = filesToScanInitially.mapNotNull { it.virtualFile() }.toSet()
//TODO there should be read action in case of the loading from a directory
//read action is necessary in case of the loading from a directory
val scope = readAction { FindInProjectUtil.getGlobalSearchScope(project, findModel) }
FindInProjectUtil.findUsages(findModel, project, progressIndicator, presentation, filesToScanInitially) { usageInfo ->
val virtualFile = usageInfo.virtualFile

View File

@@ -17,6 +17,7 @@ jvm_library(
"//platform/editor-ui-api:editor-ui",
"//platform/core-ui",
"//platform/usageView",
"//fleet/rpc",
]
)
### auto-generated section `build intellij.platform.ide.rpc` end

View File

@@ -0,0 +1,3 @@
f:com.intellij.ide.rpc.RpcCallUtil
- sf:INSTANCE:com.intellij.ide.rpc.RpcCallUtil
- f:invokeSafely(kotlin.jvm.functions.Function1,kotlin.coroutines.Continuation):java.lang.Object

View File

@@ -39,5 +39,6 @@
<orderEntry type="module" module-name="intellij.platform.editor.ui" />
<orderEntry type="module" module-name="intellij.platform.core.ui" />
<orderEntry type="module" module-name="intellij.platform.usageView" />
<orderEntry type="module" module-name="fleet.rpc" />
</component>
</module>

View File

@@ -0,0 +1,22 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.ide.rpc
import com.intellij.openapi.diagnostic.Logger
import fleet.rpc.client.RpcTimeoutException
private val log = Logger.getInstance(RpcCallUtil::class.java)
object RpcCallUtil {
suspend fun <T> invokeSafely(rpcCall: suspend () -> T): T {
var attempt = 0
while (true) {
try {
return rpcCall.invoke()
}
catch (_: RpcTimeoutException) {
attempt++
log.error("RPC call timed out. (attempt $attempt)")
}
}
}
}

View File

@@ -15,8 +15,9 @@ import com.intellij.platform.rpc.backend.RemoteApiProvider
import com.intellij.util.Function
import fleet.rpc.remoteApiDescriptor
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.launch
@@ -24,32 +25,33 @@ internal class ModuleStateApiImpl : ModuleStateApi {
@OptIn(ExperimentalCoroutinesApi::class)
override suspend fun getModulesUpdateEvents(projectId: ProjectId): Flow<ModuleUpdatedEvent> {
val project = projectId.findProjectOrNull() ?: return emptyFlow()
val flow = MutableSharedFlow<ModuleUpdatedEvent>()
val connection = project.messageBus.simpleConnect()
val connection = project.messageBus.simpleConnect()
val flow = channelFlow {
val coroutineScope = ModulesStateService.getInstance(project).coroutineScope
connection.subscribe(ModuleListener.TOPIC, object : ModuleListener {
override fun modulesAdded(project: Project, modules: List<Module>) {
coroutineScope.launch {
for (module in modules) {
flow.emit(ModuleUpdatedEvent(ModuleUpdateType.ADD, module.name))
}
send(ModuleUpdatedEvent(ModuleUpdateType.ADD, modules.map { it.name}))
}
}
override fun moduleRemoved(project: Project, module: Module) {
coroutineScope.launch {
flow.emit(ModuleUpdatedEvent(ModuleUpdateType.REMOVE, module.name))
send(ModuleUpdatedEvent(ModuleUpdateType.REMOVE, module.name))
}
}
override fun modulesRenamed(project: Project, modules: List<Module>, oldNameProvider: Function<in Module, String>) {
coroutineScope.launch {
modules.forEach { module ->
flow.emit(ModuleUpdatedEvent(ModuleUpdateType.RENAME, oldNameProvider.`fun`(module), module.name))
}
send(ModuleUpdatedEvent(ModuleUpdateType.RENAME, modules.associate { module ->
module.name to oldNameProvider.`fun`(module)
}))
}
}
})
awaitClose { connection.disconnect() }
}
return flow
}

View File

@@ -20,6 +20,7 @@ jvm_library(
"//platform/util",
"//platform/projectModel-api:projectModel",
"//platform/util/coroutines",
"//platform/platform-impl/rpc",
],
runtime_deps = [":project_resources"]
)

View File

@@ -3,6 +3,5 @@ f:com.intellij.platform.project.module.ModulesStateService
- f:getCoroutineScope():kotlinx.coroutines.CoroutineScope
- sf:getInstance(com.intellij.openapi.project.Project):com.intellij.platform.project.module.ModulesStateService
- f:getModuleNames():java.util.Set
- f:getProject():com.intellij.openapi.project.Project
f:com.intellij.platform.project.module.ModulesStateService$Companion
- f:getInstance(com.intellij.openapi.project.Project):com.intellij.platform.project.module.ModulesStateService

View File

@@ -41,5 +41,6 @@
<orderEntry type="module" module-name="intellij.platform.util" />
<orderEntry type="module" module-name="intellij.platform.projectModel" />
<orderEntry type="module" module-name="intellij.platform.util.coroutines" />
<orderEntry type="module" module-name="intellij.platform.ide.rpc" />
</component>
</module>

View File

@@ -33,4 +33,9 @@ enum class ModuleUpdateType {
@Internal
@Serializable
open class ModuleUpdatedEvent(val moduleUpdateType: ModuleUpdateType, val moduleName: String, val newModuleName: String = moduleName)
class ModuleUpdatedEvent(val moduleUpdateType: ModuleUpdateType, val newToOldModuleNameMap: Map<String, String>){// val moduleName: String, val newModuleName: String = moduleName) {
constructor(moduleUpdateType: ModuleUpdateType, moduleNames: List<String>) : this(moduleUpdateType, moduleNames.associateWith { it })
constructor(moduleUpdateType: ModuleUpdateType, moduleName: String) : this(moduleUpdateType, mapOf(moduleName to moduleName))
val moduleNames: Set<String> = newToOldModuleNameMap.keys
}

View File

@@ -1,6 +1,7 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.platform.project.module
import com.intellij.ide.rpc.RpcCallUtil
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.openapi.module.ModuleManager
@@ -16,13 +17,12 @@ import kotlinx.coroutines.launch
private val LOG = logger<ModulesStateService>()
@Service(Service.Level.PROJECT)
class ModulesStateService private constructor(val project: Project, val coroutineScope: CoroutineScope) {
class ModulesStateService private constructor(private val project: Project, val coroutineScope: CoroutineScope) {
private val state: ModulesState = ModulesState()
private var initializationCompleted: Boolean = false
init {
val initialJob = loadInitialModuleNames()
subscribeToModuleChanges(initialJob)
loadModuleNamesAndSubscribe()
}
fun getModuleNames(): Set<String> {
@@ -33,24 +33,22 @@ class ModulesStateService private constructor(val project: Project, val coroutin
return state.moduleNames
}
private fun loadInitialModuleNames(): Job {
return coroutineScope.childScope("ModulesStateService.loadInitialModuleNames").launch {
LOG.debug("Starting initial module names loading for project: ${project.name}\n")
val moduleNames = ModuleStateApi.getInstance().getCurrentModuleNames(project.projectId())
private fun loadModuleNamesAndSubscribe(): Job {
return coroutineScope.childScope("ModulesStateService.loadModuleNamesAndSubscribe").launch {
LOG.debug("Starting initial module names loading for project: ${project.name}")
val moduleNames = RpcCallUtil.invokeSafely { ModuleStateApi.getInstance().getCurrentModuleNames(project.projectId()) }
state.moduleNames.addAll(moduleNames.toMutableSet())
LOG.debug("Completed loading initial module names. Found ${moduleNames.size} modules")
initializationCompleted = true
subscribeToModuleChanges()
}
}
private fun subscribeToModuleChanges(initialJob: Job) {
coroutineScope.childScope("ModulesStateService.subscribeToModuleChanges").launch {
initialJob.join()
LOG.debug("Starting subscription for module updates in project: ${project.name}")
ModuleStateApi.getInstance().getModulesUpdateEvents(project.projectId()).collect { update ->
LOG.debug("Received module update: $update")
state.applyModuleChange(update)
}
private suspend fun subscribeToModuleChanges() {
LOG.debug("Starting subscription for module updates in project: ${project.name}")
RpcCallUtil.invokeSafely { ModuleStateApi.getInstance().getModulesUpdateEvents(project.projectId()) }.collect { update ->
LOG.debug("Received module update: $update")
state.applyModuleChange(update)
}
}
@@ -68,11 +66,13 @@ private class ModulesState() {
fun applyModuleChange(moduleUpdatedEvent: ModuleUpdatedEvent) {
when (moduleUpdatedEvent.moduleUpdateType) {
ModuleUpdateType.RENAME -> {
moduleNames.remove(moduleUpdatedEvent.moduleName)
moduleNames.add(moduleUpdatedEvent.newModuleName)
moduleUpdatedEvent.newToOldModuleNameMap.forEach { (newName, oldName) ->
moduleNames.remove(oldName)
moduleNames.add(newName)
}
}
ModuleUpdateType.ADD -> moduleNames.add(moduleUpdatedEvent.moduleName)
ModuleUpdateType.REMOVE -> moduleNames.remove(moduleUpdatedEvent.moduleName)
ModuleUpdateType.ADD -> moduleNames.addAll(moduleUpdatedEvent.moduleNames)
ModuleUpdateType.REMOVE -> moduleNames.removeAll(moduleUpdatedEvent.moduleNames)
}
}
}