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
ClassLoader streamlining and composability #3049
Comments
My current thinking is that we need to evolve better patterns which are more explicit, and that we need to reduce the reliance on the "ContextClassloader". The latter being a thread-local and mutable field is where a lot of our predicament comes from. Some initial guidelines (subject to change, discussion welcome):
|
enrich-classpath doesn't use classloaders at all |
I may be misunderstanding something. Isn't it enrich-classpath that adds |
It doesn't do that at the moment, the day I get to implement it I wouldn't use classloaders either. Associating this library to https://clojure.atlassian.net/browse/CLASSPATH-9 is therefore misleading. That issue is caused by Orchard's classloader-based adding of src.zip, which we currently intend to get rid of.
that's a different topic, one thing is performing Of course, adding things to the classpath will make those items reachable by classloaders; that's the whole point (and a fairly clean thing to do: items are placed at the tail of the classpath for minimum interference). If you believe it's problematic feel free to expand on it, but please be mindful of conflating issues. Other than that kudos for the writeup / initiative! |
@plexus Thanks for summarizing the state of affairs. This was long overdue. Here a few thoughts from me:
The guidelines outlined by you make sense to me, and they should be fairly easy to adopt. I have to admit that in the early days of nREPL/CIDER I didn't give much thought to the ramifications of our approach, plus things worked reasonably well until JDK 9, which was a wake up call. 😆 |
Thanks for clarifying @vemv, it seems I did indeed misunderstand enrich-classpath.
Agreed, manipulating the classpath outside of the JVM process is outside of the scope of what we are talking about here. Adding items at runtime is. Even if the code does not create or replace classloaders, it can make assumptions about the classloader state that don't always hold. |
I did some more testing on this, and my main impression is that we should start getting rid of some of the noise.
Similarly when cloning a session we create a new DynamicClassloader and set it on the session original PR. A thread inherits the context classloader from the thread it was forked from, and generally by that time the CCL has been set (we run a no-op :eval very early on), so if it's already there we don't need to set a new one. I briefly ran this by @cgrand and he didn't see a reason not to share the CL between threads, since they are stateless. Then there's Even better would perhaps be to not touch the contextclassloader, and only bind I have a local PoC branch that I'm using to try to dogfood this stuff while working on real world projects. Will keep folks posted and submit the changes I feel most confident about in incremental PRs. Maybe of interest, a debug log of what currently happens when you start nREPL and connect from cider (no cider-nrepl). Every two lines is a call to
nREPL starts, we immediately call
Now the server is ready, and the client connects. You can see it immediately
First clone request
Second clone request
The user hasn't done anything yet and we have instantiated 9 classloaders and called |
Originally nREPL used its own REPL implementation to avoid problems like this one. I recall that after adopting |
This was the comment from Chas I was thinking about - nrepl/nrepl#8 (comment) |
Interesting observation today. This means that when you call future, you don't know what he contextclassloader inside that future will be. It may be the one you currently have, it may be one that was the contextclassloader at some time in the past, or it may be one that was set during a previous execution of a future. This also means that if any future/agent threads were created before |
Hey @plexus , is this issue good to close by now? |
Not sure what the current situation is, but I'm fine with closing this. I'm no longer actively following up on it. |
As part of #3037 I'm trying to get a better sense of how classloaders are used across nREPL, Orchard,
clojure.main
, and elsewhere. I've been doing some deep diving into this topic, and have collected a bunch of helpers to help inspect and diagnose classloader related issues.This is a meta issue where I will for starters try to better document the situation, and collect links to various issues and PRs that have been created over time.
There's a lot of getting/setting of the thread-local context classloader, and some binding of
clojure.compiler.LOADER
. This doesn't compose well, and issues are hard to diagnose. It's easy to end up in a situation where different parts see different classloaders. E.g. when you(require ...)
it uses one, but when you do a var lookup in Orchard it uses an other one. It's also easy for one tool to set a classloader, which then gets replaced by a different one, or which no longer is accessible (because it has been replaced or because we're on a different thread) by the time it's supposed to be used.Tools that currently do classloader tricks
Issues
And many more (cider-nrepl issues, nrepl issues, orchard issue).
The text was updated successfully, but these errors were encountered: