Python: Do not access the Registry in SystemPythonServiceImpl ctor as it might not be available at that moment.

If we access `SystemPythonServiceImpl` before the Application is fully loaded (i.e. `com.intellij.python.junit5Tests.env.systemPython.SystemPythonRootsFixer.execute`), we might encounter an error:
```
 Registry key python.system.refresh.minutes is not defined
 ```

 With this change we access Registry lazily and, unlike `Registry.get`, `RegistryManager` async service is fully safe: it just suspends until the Registry is available.


Merge-request: IJ-MR-160727
Merged-by: Ilya Kazakevich <ilya.kazakevich@jetbrains.com>

GitOrigin-RevId: ef56cfee7b15907c7c99b859b5704b6fcef66b01
This commit is contained in:
Ilya Kazakevich
2025-04-23 18:32:37 +00:00
committed by intellij-monorepo-bot
parent 252bfa2111
commit 87a5090585
2 changed files with 21 additions and 19 deletions
@@ -6,14 +6,14 @@ import com.intellij.openapi.project.Project
import com.intellij.openapi.startup.ProjectActivity
import com.intellij.platform.eel.provider.getEelDescriptor
import com.intellij.python.community.services.systemPython.SystemPythonService
import com.intellij.python.community.services.systemPython.cacheTimeout
import com.intellij.python.community.services.systemPython.getCacheTimeout
private val logger = fileLogger()
// Preload pythons as soon as a project gets loaded
internal class SystemPythonInitialLoader : ProjectActivity {
override suspend fun execute(project: Project) {
if (cacheTimeout == null) return // Cache is disabled, no need to preload it
if (getCacheTimeout() == null) return // Cache is disabled, no need to preload it
logger.debug("Preloading pythons for $project")
SystemPythonService().findSystemPythons(project.getEelDescriptor().upgrade())
}
@@ -5,7 +5,7 @@ import com.intellij.openapi.application.EDT
import com.intellij.openapi.components.*
import com.intellij.openapi.components.Service.Level.APP
import com.intellij.openapi.diagnostic.fileLogger
import com.intellij.openapi.util.registry.Registry
import com.intellij.openapi.util.registry.RegistryManager
import com.intellij.platform.eel.EelApi
import com.intellij.platform.eel.EelDescriptor
import com.intellij.platform.eel.provider.getEelDescriptor
@@ -18,11 +18,9 @@ import com.intellij.python.community.services.systemPython.impl.CoreSystemPython
import com.jetbrains.python.PythonBinary
import com.jetbrains.python.Result
import com.jetbrains.python.sdk.installer.installBinary
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import org.jetbrains.annotations.ApiStatus.Internal
import org.jetbrains.annotations.Nls
import java.nio.file.InvalidPathException
@@ -35,8 +33,9 @@ import kotlin.time.Duration.Companion.minutes
private val logger = fileLogger()
// null means "disabled"
internal val cacheTimeout: Duration?
get() = Registry.get("python.system.refresh.minutes").asInteger().let { i ->
internal suspend fun getCacheTimeout(): Duration? =
// This function is suspending because registry might not be available before the application is fully loaded.
RegistryManager.getInstanceAsync().get("python.system.refresh.minutes").asInteger().let { i ->
if (i > 0) i.minutes else null
}
@@ -47,18 +46,24 @@ internal val cacheTimeout: Duration?
@Internal
internal class SystemPythonServiceImpl(scope: CoroutineScope) : SystemPythonService, SimplePersistentStateComponent<MyServiceState>(MyServiceState()) {
private val findPythonsMutex = Mutex()
private val cache: Cache<EelDescriptor, SystemPython>? = cacheTimeout?.let { interval ->
Cache(scope, interval) { eelDescriptor ->
searchPythonsPhysicallyNoCache(eelDescriptor.upgrade())
private val _cacheImpl: CompletableDeferred<Cache<EelDescriptor, SystemPython>?> = CompletableDeferred()
private suspend fun cache() = _cacheImpl.await()
init {
scope.launch {
_cacheImpl.complete(getCacheTimeout()?.let { interval ->
Cache<EelDescriptor, SystemPython>(scope, interval) { eelDescriptor ->
searchPythonsPhysicallyNoCache(eelDescriptor.upgrade())
}
})
}
}
override suspend fun registerSystemPython(pythonPath: PythonBinary): Result<SystemPython, @Nls String> {
val pythonWithLangLevel = PythonWithLanguageLevelImpl.createByPythonBinary(pythonPath).getOr { return it }
val systemPython = SystemPython(pythonWithLangLevel, null)
state.userProvidedPythons.add(pythonPath.pathString)
cache?.get(pythonPath.getEelDescriptor())?.add(systemPython)
cache()?.get(pythonPath.getEelDescriptor())?.add(systemPython)
return Result.success(systemPython)
}
@@ -66,7 +71,7 @@ internal class SystemPythonServiceImpl(scope: CoroutineScope) : SystemPythonServ
if (eelApi == localEel) LocalPythonInstaller else null
override suspend fun findSystemPythons(eelApi: EelApi, forceRefresh: Boolean): List<SystemPython> =
if (cache != null) {
cache()?.let { cache ->
// Cache enabled
cache.startUpdate()
if (forceRefresh) {
@@ -76,11 +81,8 @@ internal class SystemPythonServiceImpl(scope: CoroutineScope) : SystemPythonServ
else {
cache.get(eelApi.descriptor)
}.sorted()
}
else {
// Cache disabled
searchPythonsPhysicallyNoCache(eelApi)
}
} ?: searchPythonsPhysicallyNoCache(eelApi)
class MyServiceState : BaseState() {
// Only strings are supported by serializer