Skip to content

[Bug] AccessDeniedException on WinTray.dll when multiple projects run simultaneously on Windows #426

Description

@XZC1516

Summary

When two or more Compose Multiplatform projects that both depend on composenativetray are running at the same time on Windows (common in multi-project development), the second project to start throws an AccessDeniedException while trying to extract/replace the native library, crashing the entire tray subsystem.

Environment

  • OS: Windows 11 Pro (10.0.26200)
  • composenativetray version: 2.0.3
  • nucleus.core-runtime version: 2.3.2
  • JVM: Java 25

Steps to Reproduce

  1. Have two separate Compose Multiplatform desktop projects, both depending on composenativetray.
  2. Launch the first project — tray initializes successfully.
  3. Launch the second project while the first is still running.
  4. The second project crashes at WindowsTrayManager initialization.

Stack Trace

java.lang.ExceptionInInitializerError
    at dev.nucleusframework.composenativetray.lib.windows.WindowsTrayManager.initialize$lambda$0$0(WindowsTrayManager.kt:92)
    at java.base/java.lang.Thread.run(Thread.java:1474)
Caused by: java.nio.file.AccessDeniedException: C:\Users\<user>\AppData\Local\composetray\native\win32-x86-64\WinTray.dll
    at java.base/sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:91)
    at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:106)
    at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:112)
    at java.base/sun.nio.fs.WindowsFileCopy.move(WindowsFileCopy.java:388)
    at java.base/sun.nio.fs.WindowsFileSystemProvider.move(WindowsFileSystemProvider.java:287)
    at java.base/java.nio.file.Files.move(Files.java:1319)
    at dev.nucleusframework.composenativetray.utils.NativeLibraryLoader.extractToCache(NativeLibraryLoader.kt:78)
    at dev.nucleusframework.composenativetray.utils.NativeLibraryLoader.load(NativeLibraryLoader.kt:40)
    at dev.nucleusframework.composenativetray.lib.windows.WindowsNativeBridge.<clinit>(WindowsNativeBridge.kt:12)

Root Cause

NativeLibraryLoader.extractToCache() always extracts the bundled WinTray.dll to a fixed shared path (AppData\Local\composetray\native\win32-x86-64\WinTray.dll) and replaces it via Files.move().

On Windows, a DLL that is already loaded by a running JVM process is locked by the OS. A second process attempting Files.move() to overwrite the locked file receives AccessDeniedException, causing ExceptionInInitializerError and taking down the entire tray system.

Expected Behavior

If the DLL already exists at the cache path and its content matches the bundled version (e.g. same checksum), the extraction step should be skipped entirely rather than attempting an unnecessary overwrite. This would allow multiple projects to coexist on the same machine without conflict.

Suggested Fix

In NativeLibraryLoader.extractToCache(), before calling Files.move(), check whether the target file already exists and its content is identical to the bundled resource (e.g. compare file size or SHA-256). If they match, skip the move and proceed directly to System.load().

// Skip extraction if the cached DLL is already up-to-date
if (targetFile.exists() && targetFile.isSameContent(bundledResource)) {
    System.load(targetFile.absolutePath)
    return
}
// Otherwise proceed with extraction + move

This is consistent with how most JNA/JNI native-library loaders handle the "already cached" case (e.g. jna, lwjgl).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions