Litote / kmongo

[deprecated] KMongo - a Kotlin toolkit for Mongo
https://litote.org/kmongo/
Apache License 2.0
781 stars 74 forks source link

Consider adding a classpath argument to the ServiceLoader.load call in ClassMappingType.kt #407

Open bitspittle opened 1 year ago

bitspittle commented 1 year ago

Specifically, I think you can change this:

ServiceLoader.load(ClassMappingTypeService::class.java)

to this:

ServiceLoader.load(ClassMappingTypeService::class.java, ClassMappingTypeService::class.java.classLoader)
//                                                      ^ or KMongo::class.java.classLoader, or whatever

For comparsion, look at what Jetbrains Exposed does with their service loaders: https://github.com/search?q=repo%3AJetBrains%2FExposed%20ServiceLoader&type=code


Background context

I'm working on a project that provides a webserver that users can register simple API endpoints against. They do this by building an uber jar which my server loads on demand at runtime. One of my users is using KMongo.

So what happens is, my server finds their jar and loads it with a custom classloader. That class loader is now the owner of the classes they bundled, which include KMongo, ClassMappingTypeService, etc.

My custom classloader can also find resources, one of which is the META-INF/services resource for ClassMappingTypeService.

If you provide ClassMappingTypeService::class.java.classLoader as a second argument (which in this case happens to be my class loader), then I can find that resource and return it to whatever Java machinery is looking for it.

Without passing in a specific classloader into ServiceLoader.load, the method ends up using a classloader associated with the parent thread. As classloaders don't search children classloaders, that means my uber jar has the service definition resource that the JVM wants but can never find.

I am pretty sure that specifying this second argument will help people in the uber jar situation, without affecting existing users. Since, in their case, the class loader for the parent thread ends up being the same as the classloader for KMongo. That's basically exactly the same case that you have now. (Of course, please don't blindly trust me, and double check!)

Finally -- if you make this change, I'm pretty sure that would fix stuff like https://github.com/Litote/kmongo/issues/98


Workaround

Here's how I worked around this in my own code:

val prevContextClassLoader = Thread.currentThread().contextClassLoader
try {
    Thread.currentThread().contextClassLoader = KMongo::class.java.classLoader
    val client = KMongo.createClient().coroutine
    /* ... other KMongo init stuff ... */
} finally {
    Thread.currentThread().contextClassLoader = prevContextClassLoader
}

which I can remove if this issue ever gets fixed.