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

Scala Native crash when wrong cached ClassLoader is used #4177

Open
lolgab opened this issue Dec 24, 2024 · 1 comment
Open

Scala Native crash when wrong cached ClassLoader is used #4177

lolgab opened this issue Dec 24, 2024 · 1 comment
Labels
bug The issue represents an bug workaround-available

Comments

@lolgab
Copy link
Member

lolgab commented Dec 24, 2024

Given a Cross ScalaNativeModule, multiple classloaders are used,
Since we cache the output nativeConfig, it can happen that a nativeConfig generated
with a Classloader is used when linking with another classloader.
This can lead to ClassCastException since objects from different classloader can't be shared.

Repro

import mill._
import mill.scalalib._
import mill.scalanativelib._

object app extends Cross[App]("a", "b", "c", "d")
trait App extends ScalaNativeModule with Cross.Module[String] {
  def scalaVersion = "3.5.2"
  def scalaNativeVersion = "0.5.6"
}
mill __.nativeLink

Output

app[aarch64-macos-none].nativeLink java.lang.ClassCastException: class scala.scalanative.build.Config$Impl cannot be cast to class scala.scalanative.build.Config (scala.scalanative.build.Config$Impl is in unnamed module of loader mill.api.ClassLoader$$anon$1 @278c6ad9; scala.scalanative.build.Config is in unnamed module of loader mill.api.ClassLoader$$anon$1 @3d4e7cb1)
    mill.scalanativelib.worker.ScalaNativeWorkerImpl.nativeLink(ScalaNativeWorkerImpl.scala:105)
    mill.scalanativelib.ScalaNativeModule.$anonfun$nativeLink$4(ScalaNativeModule.scala:286)
    mill.scalanativelib.ScalaNativeModule$ScalaNativeBridge.$anonfun$apply$1(ScalaNativeModule.scala:159)
    mill.api.CachedFactory.withValue(CachedFactory.scala:39)
    mill.scalanativelib.ScalaNativeModule$ScalaNativeBridge.apply(ScalaNativeModule.scala:158)
    mill.scalanativelib.ScalaNativeModule.$anonfun$nativeLink$3(ScalaNativeModule.scala:287)
app[aarch64-windows-gnu].nativeLink scala.scalanative.build.BuildException: Failed to compile /Users/lorenzo/scala/scala-native-cross/out/app/aarch64-windows-gnu/nativeWorkdir.dest/native/dependencies/nativelib_native0.5_3-0.5.6-0/scala-native/nativeThreadTLS.c
app[x86_64-linux-gnu].nativeLink java.lang.ClassCastException: class scala.scalanative.build.Config$Impl cannot be cast to class scala.scalanative.build.Config (scala.scalanative.build.Config$Impl is in unnamed module of loader mill.api.ClassLoader$$anon$1 @2bd047bc; scala.scalanative.build.Config is in unnamed module of loader mill.api.ClassLoader$$anon$1 @709c8fd6)
    mill.scalanativelib.worker.ScalaNativeWorkerImpl.nativeLink(ScalaNativeWorkerImpl.scala:105)
    mill.scalanativelib.ScalaNativeModule.$anonfun$nativeLink$4(ScalaNativeModule.scala:286)
    mill.scalanativelib.ScalaNativeModule$ScalaNativeBridge.$anonfun$apply$1(ScalaNativeModule.scala:159)
    mill.api.CachedFactory.withValue(CachedFactory.scala:39)
    mill.scalanativelib.ScalaNativeModule$ScalaNativeBridge.apply(ScalaNativeModule.scala:158)
    mill.scalanativelib.ScalaNativeModule.$anonfun$nativeLink$3(ScalaNativeModule.scala:287)

Possible solutions

  • Add the module as the cache key, to make a module always use the same ClassLoader.

Workaround

Pass -j 1 so a single Classloader is used.

@lolgab lolgab added bug The issue represents an bug workaround-available labels Dec 24, 2024
@lefou
Copy link
Member

lefou commented Jan 26, 2025

This issue is rather in the design space. The NativeConfig is shared API and its runtime class is also shared by all classloaders that are supposed to load it. Yet, it's (currently) not designed to be shared, as it seems to depend on some non-shared classes.

I think your proposed solutions are rather workarounds since they just try to limit the scope of the config object to the originating classloader. Any proper solution need to somehow brigde the actual configuration from one classloader to the other. Does the actual config implementation already support some kind of serialization or file persistence? If yes, saving and loading a config file might be the robustest solution, which probably can happen on demand. If not we could either use some JSON serialization or fall back to use reflection to copy the classloader-specific config holder class over. There is also the option to expose all config values in the NativeConfig class and re-read it over that API. But that depends on the kind of config objects, which I'm not familiar with.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug The issue represents an bug workaround-available
Projects
None yet
Development

No branches or pull requests

2 participants