Skip to content

sbt 1.3.7: Layered class loader fails to load application classes inside Scala Future's global context #5410

@xerial

Description

@xerial

steps

sbt version: 1.3.7

$ git clone https://github.com/xerial/sbt-classloader-future
$ cd sbt-classloader-future
$ sbt run 
// java.lang.ClassNotFoundException will be thrown

problem

  • sbt's layered classloader is not properly used inside Scala Future's global context
  • Future { Thread.currentTHread.getContextClassLoader.loadClass("(class name path)") } fails unless Flat layering strategy or fork in run := true is used.
package sample

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.ExecutionContext
import scala.concurrent.duration.Duration
import scala.concurrent.{Await, Future}

case class A(id:Int)

object FutureSample {
  def main(args:Array[String]): Unit = {
     //	OK
     val cl = Thread.currentThread.getContextClassLoader.loadClass("sample.A")
     println(cl.getName)

     val f = Future {
        // Fails other than setting ClassLoaderLayeringStorategy.Flat or fork in run = true
        val cl = Thread.currentThread.getContextClassLoader.loadClass("sample.A")
        println(cl.getName)
     }
     Await.result(f, Duration.Inf)
  }
}

Workarounds

Use one of the following workarounds:

  • Use Flat strategy (AllLibraryJars didn't work)
  • Use fork in run/test := true
  • Use a user-defined ExecutionContext for Future, instead of scala.concurrent.ExecutionContext.Implicits.global.
    • I guess global belongs to the class loader for loading Scala libraries and it was initialized outside the context of the application.

expectation

  • LayeredClassLoader should be used when initializing import scala.concurrent.ExecutionContext.Implicits.global
  • The above example project should succeed
  • Minimize the mean time to understanding this pecurier behavior of the layered class loader. This issue might not be a bug of sbt as we should not rely on a specific behavior of class loaders, but we needed a long time to figure out that this issue is caused by the layered class loader.

notes

Here are the hierarchies of class loaders:

Class loader hierarchy within applications:
-class sbt.internal.LayeredClassLoader
 -class sbt.internal.classpath.WrappedLoader
  -class sbt.internal.ScalaReflectClassLoader
   -class sbt.internal.classpath.WrappedLoader
    -class sbt.State$UncloseableURLLoader
     -class xsbt.boot.LibraryClassLoader 0s
      -class xsbt.boot.Launch$TestInterfaceLoader$3
       -class xsbt.boot.BootFilteredLoaders
        -class jdk.internal.loader.ClassLoaders$AppClassLoader
         -class jdk.internal.loader.ClassLoaders$PlatformClassLoader

Class loader hierarchy within Future when using the global context. This cannot read application classes:
-class java.net.URLClassLoader
 -class java.net.URLClassLoader
  -class xsbt.boot.LibraryClassLoader
   -class xsbt.boot.Launch$TestInterfaceLoader
    -class xsbt.boot.BootFilteredLoader
     -class jdk.internal.loader.ClassLoaders$AppClassLoader
      -class jdk.internal.loader.ClassLoaders$PlatformClassLoader

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions