Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Skiko Cache Corruption: java.lang.UnsatisfiedLinkError: Can't load library #2895

Open
ScottPierce opened this issue Mar 18, 2023 · 2 comments
Assignees
Labels
bug Something isn't working enhancement New feature or request

Comments

@ScottPierce
Copy link
Contributor

ScottPierce commented Mar 18, 2023

Describe the bug
It seems that sometimes the skiko cache in ~/.skiko can become corrupted, and the user will become stuck, unable to open the app. The only fix is to get the user to delete the ~/.skiko directory. When this corruption happens, the app will not open, and nothing can be done.

It's unclear how this corruption happens, but I've seen it on at least 4 developers computers.

image

java.lang.UnsatisfiedLinkError: Can't load library: /Users/scottpierce/.skiko/9501f4ff87ab1f362914703c445352b4fa63ad64a4fa1677eebfb03ff03a0cde/libskiko-macos-arm64.dylib
	at java.base/java.lang.ClassLoader.loadLibrary(ClassLoader.java:2393)
	at java.base/java.lang.Runtime.load0(Runtime.java:755)
	at java.base/java.lang.System.load(System.java:1953)
	at org.jetbrains.skiko.Library.loadLibraryOrCopy(Library.kt:20)
	at org.jetbrains.skiko.Library.findAndLoad(Library.kt:113)
	at org.jetbrains.skiko.Library.load(Library.kt:59)
	at org.jetbrains.skiko.SkiaLayer.<clinit>(SkiaLayer.awt.kt:32)
	at androidx.compose.ui.awt.ComposeLayer.<init>(ComposeLayer.desktop.kt:92)
	at androidx.compose.ui.awt.ComposeWindowDelegate.<init>(ComposeWindowDelegate.desktop.kt:59)
	at androidx.compose.ui.awt.ComposeWindow.<init>(ComposeWindow.desktop.kt:61)
	at androidx.compose.ui.awt.ComposeWindow.<init>(ComposeWindow.desktop.kt:59)
	at androidx.compose.ui.window.Window_desktopKt$Window$3.invoke(Window.desktop.kt:162)
	at androidx.compose.ui.window.Window_desktopKt$Window$3.invoke(Window.desktop.kt:156)
	at androidx.compose.ui.window.Window_desktopKt$Window$10$1.invoke(Window.desktop.kt:378)
	at androidx.compose.ui.window.Window_desktopKt$Window$10$1.invoke(Window.desktop.kt:377)
	at androidx.compose.ui.window.AwtWindow_desktopKt$AwtWindow$2.invoke(AwtWindow.desktop.kt:75)
	at androidx.compose.ui.window.AwtWindow_desktopKt$AwtWindow$2.invoke(AwtWindow.desktop.kt:74)
	at androidx.compose.runtime.DisposableEffectImpl.onRemembered(Effects.kt:81)
	at androidx.compose.runtime.CompositionImpl$RememberEventDispatcher.dispatchRememberObservers(Composition.kt:1091)
	at androidx.compose.runtime.CompositionImpl.applyChangesInLocked(Composition.kt:818)
	at androidx.compose.runtime.CompositionImpl.applyChanges(Composition.kt:839)
	at androidx.compose.runtime.Recomposer.composeInitial$runtime(Recomposer.kt:978)
	at androidx.compose.runtime.CompositionImpl.setContent(Composition.kt:519)
	at androidx.compose.ui.window.Application_desktopKt$awaitApplication$2$1$2.invokeSuspend(Application.desktop.kt:219)
	at kotlin.stdlib/kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
	at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:318)
	at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:771)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:722)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:716)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86)
	at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:741)
	at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
	at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)

Affected platforms
Select one of the platforms below:

  • Desktop

Versions

  • Kotlin version*: 1.8.0
  • Compose Multiplatform version*: 1.3.1
  • OS version(s)* (required for Desktop and iOS issues): Mac OS Monterey and Ventura
  • OS architecture (x86 or arm64): x86 and arm64
  • JDK (for desktop issues): Corretto 17

To Reproduce

  1. Open app

Expected behavior
In the case that the skiko cache becomes corrupted like this, skiko should catch the exception itself, and repair the cache automatically.

@ScottPierce ScottPierce added bug Something isn't working submitted labels Mar 18, 2023
@mikehearn
Copy link
Contributor

mikehearn commented Mar 18, 2023

The actual cause of load failure here is almost certainly a signature mismatch, which can occur when shipping a JAR with a signed skiko inside. The error crops up now because Conveyor recently added support for this type of signing to help with other JVM libraries that need special configuration to load their JNI library from inside a bundle/installed app dir.

There's an explanation of the error in more depth here:

https://conveyor.hydraulic.dev/7.1/troubleshooting/troubleshooting-jvm/#unsatisfiedlinkerror-mapping-process-and-mapped-file-non-platform-have-different-team-ids

and a discussion of the overall ecosystem problems that lead to it here:

https://hydraulic.software/blog/11-in-jar-signing.html

There are many solutions, but from Compose's perspective probably the simplest is to catch a failure to load the library from ~/.skiko and delete/re-extract the dylib. That will make sure that whatever's on disk has a signature that matches the signer of the app itself at load time. It's ugly, especially if you have multiple Compose apps running at once, but it should work. The "real" solution here is always to turn on JNI library extraction at packaging time so there's no need for Compose or other libs to do this extract-to-home-dir dance at all. Compose is compatible with this out of the box, the problem is more other libraries and the general effort required to configure them all.

@eymar
Copy link
Collaborator

eymar commented Mar 20, 2023

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

4 participants