PY-82318: Unlock files locked by pip and python on Windows.

GitOrigin-RevId: 7b72a365964314707b28f6a7f20e3cedbe4bc194
This commit is contained in:
Ilya.Kazakevich
2025-06-30 21:53:26 +00:00
committed by intellij-monorepo-bot
parent fb1a3c893b
commit 530fb3f4c8
3 changed files with 29 additions and 3 deletions
@@ -26,5 +26,5 @@ fun getProcessLockedPath(path: Path): Result<List<ProcessHandle>, @NlsSafe Strin
fun deleteCheckLocking(path: Path, vararg processesToKillIfLocked: Regex = arrayOf(PYTHON)): Unit = deleteCheckLockingImpl(path, *processesToKillIfLocked)
private val PYTHON = Regex("^python[0-9.]*\\.exe$")
private val PYTHON = Regex("^(python|pip)[0-9.]*\\.exe$")
@@ -14,6 +14,8 @@ import kotlin.io.path.isDirectory
import kotlin.jvm.optionals.getOrNull
private val fileLogger = fileLogger()
@OptIn(ExperimentalPathApi::class)
@Throws(IOException::class, FileLockedException::class)
internal fun deleteCheckLockingImpl(path: Path, vararg processesToKill: Regex) {
@@ -26,7 +28,6 @@ internal fun deleteCheckLockingImpl(path: Path, vararg processesToKill: Regex) {
}
val paths = listOf(path) + if (path.isDirectory()) Files.walk(path).use { it.toList() } else emptyList()
// First, kill processes
for (child in paths) {
for (process in getProcessLockedPath(child).orThrow()) {
@@ -34,7 +35,7 @@ internal fun deleteCheckLockingImpl(path: Path, vararg processesToKill: Regex) {
val fileName = Path.of(command).fileName.toString()
if (processesToKill.any { it.matches(fileName) }) {
val processInfo = WinProcessInfo.get(process.pid())
fileLogger().warn("Killing ${process.pid()} ${processInfo}")
fileLogger.warn("Killing ${process.pid()} ${processInfo}")
killProcess(process)
}
}
@@ -3,6 +3,9 @@ package com.intellij.python.junit5Tests.unit.showCase
import com.intellij.python.junit5Tests.framework.winLockedFile.FileLockedException
import com.intellij.python.junit5Tests.framework.winLockedFile.deleteCheckLocking
import com.intellij.python.junit5Tests.framework.winLockedFile.getProcessLockedPath
import com.intellij.util.io.awaitExit
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeoutOrNull
import org.hamcrest.CoreMatchers
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers
@@ -16,7 +19,9 @@ import org.junit.jupiter.api.io.TempDir
import java.nio.file.Files
import java.nio.file.Path
import kotlin.io.path.ExperimentalPathApi
import kotlin.io.path.deleteIfExists
import kotlin.io.path.writeText
import kotlin.time.Duration.Companion.seconds
class WinLockedFilesTest {
@@ -60,4 +65,24 @@ class WinLockedFilesTest {
process.waitFor()
deleteCheckLocking(path)
}
@OptIn(ExperimentalPathApi::class)
@EnabledOnOs(value = [OS.WINDOWS])
@Test
fun testDirectoryLockedUnlock(@TempDir path: Path): Unit = runBlocking {
val systemRoot = System.getenv("SystemRoot") ?: "c:\\windows"
val process = ProcessBuilder("$systemRoot/system32/cmd.exe")
.directory(path.toFile())
.start()
try {
deleteCheckLocking(path, Regex("cmd\\.exe"))
withTimeoutOrNull(10.seconds) {
process.awaitExit()
}
path.deleteIfExists()
}
finally {
process.destroyForcibly()
}
}
}