From 98502d0032d23c0fdaa678f48f5514c9c87dc4b3 Mon Sep 17 00:00:00 2001 From: Dave MacLachlan Date: Fri, 17 Oct 2025 12:09:27 -0700 Subject: [PATCH] Fix for KT-81771 konanc failing to load native libraries This simplifies the logic removing the hashing and only doing the copying if "kotlin.native.tool.safeLoadKonanLibrary" is set to true. The belief is that in the majority of cases the copies are not required and is slowing down startup and causes instability if the rename should fail for some reason. --- .../jvm/kotlin/kotlinx/cinterop/JvmUtils.kt | 94 +++++++++---------- 1 file changed, 45 insertions(+), 49 deletions(-) diff --git a/kotlin-native/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmUtils.kt b/kotlin-native/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmUtils.kt index 117cfac6b8fcf..7100c6ef9fea6 100644 --- a/kotlin-native/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmUtils.kt +++ b/kotlin-native/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmUtils.kt @@ -19,9 +19,12 @@ package kotlinx.cinterop import org.jetbrains.kotlin.utils.KotlinNativePaths +import org.jetbrains.kotlin.utils.rethrow import java.io.File +import java.lang.System import java.nio.file.Files import java.nio.file.Paths +import java.nio.file.Path import java.nio.file.InvalidPathException import java.security.MessageDigest @@ -112,11 +115,14 @@ private fun initializePath() = .split(File.pathSeparatorChar) .map { if (it == "") "." else it } -private val sha256 = MessageDigest.getInstance("SHA-256") -private val systemTmpDir = System.getProperty("java.io.tmpdir") +// Track the libraries that we have already loaded. +private var loadedLibraries = mutableSetOf() -// TODO: File(..).deleteOnExit() does not work on Windows. May be use FILE_FLAG_DELETE_ON_CLOSE? private fun tryLoadKonanLibrary(dir: String, fullLibraryName: String, runFromDaemon: Boolean): Boolean { + if (loadedLibraries.contains(fullLibraryName)) { + // Already loaded a library with this name. + return true + } var fullLibraryPath = try { Paths.get(dir, fullLibraryName) } catch (ignored: InvalidPathException) { @@ -124,41 +130,35 @@ private fun tryLoadKonanLibrary(dir: String, fullLibraryName: String, runFromDae } if (!Files.exists(fullLibraryPath)) return false - fun createTempDirWithLibrary() = if (runFromDaemon) { - Files.createTempDirectory(null).toAbsolutePath().toString().also { - Files.copy(fullLibraryPath, Paths.get(it, fullLibraryName)) - } - } else { - Files.createTempDirectory(Paths.get(dir), null).toAbsolutePath().toString().also { - Files.createLink(Paths.get(it, fullLibraryName), fullLibraryPath) + fun createTemporaryCopyOfLibrary(): Path { + // Create a temporary directory and copy the library into it. + val tmpDirRoot = if (runFromDaemon) System.getProperty("java.io.tmpdir") else dir + val tmpDirPath = Files.createTempDirectory(Paths.get(tmpDirRoot), "konanc") + val dest = tmpDirPath.resolve(fullLibraryName) + if (runFromDaemon) { + Files.copy(fullLibraryPath, dest) + } else { + Files.createLink(dest, fullLibraryPath) } + // TODO: File(..).deleteOnExit() does not always work on Windows with DLLs. Maybe use FILE_FLAG_DELETE_ON_CLOSE? + tmpDirPath.toFile().deleteOnExit() + dest.toFile().deleteOnExit() + return dest } - if (runFromDaemon) { + val safeLoadKonanLibrary = runFromDaemon && System.getProperty("kotlin.native.tool.safeLoadKonanLibrary") == "true" + if (safeLoadKonanLibrary) { + // The block below came in https://github.com/JetBrains/kotlin/commit/f724b5c29da0122a1391af6e28ef3823aa2cb831 + // Not sure what Platform/OS this issue was seen on, but we have been seeing issues where `renameTo` will fail + // on macOS. See the commit above to see the original change. + // Added the `safeLoadKonanLibrary` property to allow this block to be executed, but in general avoiding the copy is far better. + // The copy will trigger virus scans on macOS, and can leave files scattered in temp directories on Windows. + // Original comment: // Sometimes loading library from its original place doesn't work (it gets 'half-loaded' // with relocation table haven't been substituted by the system loader without reporting any error). - // We workaround this by copying the library to some temporary place. + // We work around this by copying the library to some temporary place. // For now this behaviour have only been observed for compilations run from the Gradle daemon on Team City. - val hash = sha256.digest(Files.readAllBytes(fullLibraryPath)) - val defaultTempDirName = buildString { - append(fullLibraryName) - append('_') - hash.forEach { - val hex = it.toUByte().toString(16) - if (hex.length == 1) - append('0') - append(hex) - } - } - val defaultTempDir = Paths.get(systemTmpDir, defaultTempDirName).toAbsolutePath().toString() - val tempDir = File(createTempDirWithLibrary()) - if (tempDir.renameTo(File(defaultTempDir))) { - File(defaultTempDir).deleteOnExit() - File("$defaultTempDir/$fullLibraryName").deleteOnExit() - } else { - tempDir.deleteRecursively() - } - fullLibraryPath = Paths.get(defaultTempDir, fullLibraryName) + fullLibraryPath = createTemporaryCopyOfLibrary() } // System load will throw `UnsatisfiedLinkError` if fullLibraryPath isn't resolved. @@ -177,30 +177,26 @@ private fun tryLoadKonanLibrary(dir: String, fullLibraryName: String, runFromDae |${'\t'}${e.message} """.trimMargin()) } - - // Not sure what this work around is attempting to deal with. - // Note that copying a dylib to a temp directory and then loading it will - // cause the macOS virus scanner (XProtect) to scan it, so try and avoid doing - // this. - val tempDir = createTempDirWithLibrary() - - File(tempDir).deleteOnExit() - File("$tempDir/$fullLibraryName").deleteOnExit() - System.load("$tempDir/$fullLibraryName") + // Try copying the library to a temp directory and then loading it as a fallback. + // Note that this will cause the macOS virus scanner (XProtect) to scan it, and may leave files in temp directories on Windows. + // Not done in `safeLoadKonanLibrary` case because we would've already attempted to load a copy above. + if (!safeLoadKonanLibrary) { + fullLibraryPath = createTemporaryCopyOfLibrary() + System.load(fullLibraryPath.toRealPath().toString()) + } else { + rethrow(e) + } } - + loadedLibraries.add(fullLibraryName) return true } fun loadKonanLibrary(name: String) { val runFromDaemon = System.getProperty("kotlin.native.tool.runFromDaemon") == "true" val fullLibraryName = System.mapLibraryName(name) - val paths = initializePath() + val paths = initializePath() + listOf("${KotlinNativePaths.homePath.absolutePath}/konan/nativelib") for (dir in paths) { if (tryLoadKonanLibrary(dir, fullLibraryName, runFromDaemon)) return } - val defaultNativeLibsDir = "${KotlinNativePaths.homePath.absolutePath}/konan/nativelib" - if (tryLoadKonanLibrary(defaultNativeLibsDir, fullLibraryName, runFromDaemon)) - return - error("Lib $fullLibraryName is not found in $defaultNativeLibsDir and ${paths.joinToString { it }}") -} \ No newline at end of file + error("Lib $fullLibraryName was not found in ${paths.joinToString { it }}") +}