From 3e47d60be19f3302c1aa1f4165582c67fb7ed3ec Mon Sep 17 00:00:00 2001 From: "Ilya.Kazakevich" Date: Fri, 26 Dec 2025 08:47:53 +0100 Subject: [PATCH] cleanup [python]: Fix a couple of `VirtualEnvReader` bottlenecks. 1. No need to check if file is directory: FS isn't atomic anyway, and we need to handle exceptions. Let's not do one more useless syscall which might be expensive on eel. 2. Check filename before reading it metadata (one more syscall) GitOrigin-RevId: 0fac159784c2c1d80c3418eb0d9c60a501662b7d --- .../python/venvReader/VirtualEnvReader.kt | 53 ++++++++++++------- .../sdk/flavors/VirtualEnvSdkFlavor.java | 4 +- 2 files changed, 36 insertions(+), 21 deletions(-) diff --git a/python/openapi/src/com/jetbrains/python/venvReader/VirtualEnvReader.kt b/python/openapi/src/com/jetbrains/python/venvReader/VirtualEnvReader.kt index a0efcf226da3..92e1c8efb0d3 100644 --- a/python/openapi/src/com/jetbrains/python/venvReader/VirtualEnvReader.kt +++ b/python/openapi/src/com/jetbrains/python/venvReader/VirtualEnvReader.kt @@ -18,6 +18,8 @@ import org.jetbrains.annotations.ApiStatus import org.jetbrains.annotations.NonNls import org.jetbrains.annotations.TestOnly import java.io.IOException +import java.nio.file.NoSuchFileException +import java.nio.file.NotDirectoryException import java.nio.file.Path import kotlin.io.path.* @@ -57,7 +59,7 @@ class VirtualEnvReader private constructor( */ @RequiresBackgroundThread fun findVEnvInterpreters(): List = - findLocalInterpreters(getVEnvRootDir()) + findVenvsInDir(getVEnvRootDir()) @RequiresBackgroundThread fun getPyenvRootDir(eel: EelApi? = getLocalEelIfApp()): Directory { @@ -66,16 +68,26 @@ class VirtualEnvReader private constructor( @RequiresBackgroundThread fun findPyenvInterpreters(): List = - findLocalInterpreters(getPyenvVersionsDir()) + findVenvsInDir(getPyenvVersionsDir()) + /** + * List contents of [root] looking for envs there, returns all pythons it were able to find + */ @RequiresBackgroundThread - fun findLocalInterpreters(root: Directory): List { - if (!root.isDirectory()) { - return listOf() - } + fun findVenvsInDir(root: Directory): List { val candidates: ArrayList = arrayListOf() - for (dir in root.listDirectoryEntries()) { + val children = try { + root.listDirectoryEntries() + } + catch (_: NoSuchFileException) { + return emptyList() + } + catch (_: NotDirectoryException) { + return emptyList() + } + + for (dir in children) { findPythonInPythonRoot(dir)?.let { candidates.add(it) } } @@ -120,19 +132,12 @@ class VirtualEnvReader private constructor( */ @RequiresBackgroundThread fun findPythonInPythonRoot(dir: PythonHomePath): PythonBinary? { - if (!dir.isDirectory()) { - return null - } val bin = dir.resolve("bin") - if (bin.isDirectory()) { - findInterpreter(bin)?.let { return it } - } + findInterpreter(bin)?.let { return it } val scripts = dir.resolve("Scripts") - if (scripts.isDirectory()) { - findInterpreter(scripts)?.let { return it } - } + findInterpreter(scripts)?.let { return it } return findInterpreter(dir) } @@ -170,10 +175,18 @@ class VirtualEnvReader private constructor( @RequiresBackgroundThread private fun findInterpreter(dir: Path): PythonBinary? { val pythonNames = when (forcedOs ?: dir.getEelDescriptor().osFamily) { - EelOsFamily.Posix -> setOf("pypy", "python") - EelOsFamily.Windows -> setOf("pypy.exe", "python.exe") + EelOsFamily.Posix -> POSIX_BINS + EelOsFamily.Windows -> WIN_BINS + } + return try { + dir.listDirectoryEntries().firstOrNull { it.name.lowercase() in pythonNames && it.isRegularFile() } + } + catch (_: NotDirectoryException) { + return null + } + catch (_: NoSuchFileException) { + return null } - return dir.listDirectoryEntries().firstOrNull { it.isRegularFile() && it.name.lowercase() in pythonNames } } @RequiresBackgroundThread @@ -203,6 +216,8 @@ class VirtualEnvReader private constructor( @Suppress("VENV_IS_OK") // The only place it should be used in prod const val DEFAULT_VIRTUALENV_DIRNAME: String = ".venv" + private val POSIX_BINS = setOf("pypy", "python") + private val WIN_BINS = setOf("pypy.exe", "python.exe") private fun getLocalEelIfApp(): EelApi? = if (ApplicationManager.getApplication() != null) localEel else null } } \ No newline at end of file diff --git a/python/src/com/jetbrains/python/sdk/flavors/VirtualEnvSdkFlavor.java b/python/src/com/jetbrains/python/sdk/flavors/VirtualEnvSdkFlavor.java index 0b0a89a89f5c..0d51c8b4e8b1 100644 --- a/python/src/com/jetbrains/python/sdk/flavors/VirtualEnvSdkFlavor.java +++ b/python/src/com/jetbrains/python/sdk/flavors/VirtualEnvSdkFlavor.java @@ -57,11 +57,11 @@ public final class VirtualEnvSdkFlavor extends CPythonSdkFlavor