-
Notifications
You must be signed in to change notification settings - Fork 12
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
Butterfly classloader module isolation causes Jackson problems #15
Comments
I stumbled across the commented out classloader code (which is not associated with any PR, but is in 0de01cb) the other day and, independently stumbled across the unlinked commentary in OpenRefine/OpenRefine#1889 just now. I'm not sure what other workarounds are possible for Jackson's particular troubles, but the Butterfly classloader organization was intentional. It wasn't a "misinterpretation" of the delegation model, but, as I interpret it, an intentional firewalling mechanism to keep modules from interfering with each other. Many extension models have similar protection mechanisms, so OpenRefine may need a different mechanism than insisting that all code share a common classloader. simile-butterfly/main/src/edu/mit/simile/butterfly/ButterflyClassLoader.java Lines 44 to 77 in 0de01cb
|
Yes I understand that this custom class-loading was intentional. Yes it could be useful to have something that enables extensions to use different versions of libraries, so provide the same sort of isolation. The solutions I have in mind are:
Of course I found out about this problem extremely late in the migration process, and that did not really help find a satisfying solution. The problem only occurs with real-world testing of extensions (units tests are not affected as the class loading mechanisms are different when running tests). |
Thanks for the clarification. That wasn't clear to me from what I read before. I'm still not clear on what the exact problem is/was. Can you expand with a little more detail (perhaps an example)? Is it to do with preferences or communication between the core and extensions or the front end and backend or something else entirely? The purpose in creating this issue wasn't to do something different immediately or even consider the issue now. It was just to get the existing history captured since we use Github as our principal form of institutional memory. |
Here is a summary of the issue: We use Jackson to serialize / deserialize our classes in JSON. Jackson makes it possible to specify how a class is (de)serialized by annotating it with its own set of annotations. Given a class to (de)serialize, Jackson generates a (de)serializer on the fly by looking at the annotations on the class. In Java, annotations are classes themselves, so they are associated to the classloader that loaded them. This classloader is taken into account when checking for equality of classes (and If an extension declares a new operation, or overlay model, or anything else that will be serialized to JSON by the main application, this is a problem. With Butterfly's custom classloader, Jackson annotations are marked as loaded by ButterflyClassLoader instead of WebappClassLoader, so the hashes of the classes would differ and the Jackson introspection code would not find them when generating serializers and deserializers for POJOs coming from extensions. The issue on Jackson's side is here: FasterXML/jackson-databind#542 That being said, one low-hanging solution for this comes to mind as I write this: adding jackson as a |
I have no idea what I am talking about but perhaps the new Java module system introduced in Java 9 could help with this sort of problem. |
I think only you can answer that having some knowledge of what Butterfly currently gives to OpenRefine extensions. |
Revisiting this in the context of the OpenRefine extension ecosystem discussion, I think it would be useful to restore the module isolation which was disabled. While I think exposing Jackson in the extension interface is dangerous for the same reason that having org.json objects in the extension interface was dangerous, as I previously argued
a bandaid fix might be to declare Jackson databind to be a sort of bootstrap dependency that gets loaded first by Butterfly or to otherwise provide special treatment of the necessary classes (effectively make Jackson a core Butterfly facility). |
I took a closer look as I was preparing to implement the bootstrapping scheme described above and discovered that it's exactly as simple as @wetneb described:
There are actually two sources of conflict: slf4j and Jackson. The Jackson conflict seems to be with ObjectMapper declarations rather the annotations, but I pulled in both just to be safe. |
Fixing all the bundled extensions works, but it only takes a single rogue extension to mess things up again for all. For example, an older version of the Contrary to my assumption above, the Butterfly classloader doesn't actually provide intermodule classloading isolation, but merges all the modules classpaths together into a single classloader, simile-butterfly/main/src/edu/mit/simile/butterfly/Butterfly.java Lines 644 to 651 in 79296b6
so the Also, because the order of module loading is effectively random (hashset iteration order), the priority of modules on the classpath relative to each other is random. |
This discussion really encourages me to go for an established plugin system. I mean, honestly, I am really not knowledgeable on Java class loading details (as can be seen with the classloading change I made), and I don't think it's a good use of our time to become experts on that and implement something solid, when there are existing solutions we could adopt (OSGi, PF4J, maybe others). I have written more about this on the forum. |
Fixing the existing bug (calling it bug because I'm pretty sure the implementation doesn't match the authors' implied intentions) is straightforward and other implementation details aren't really as complicated as deciding what the requirements should be. That part needs to be done even if it's just to define requirements for selecting a new and different implementation. |
Sure! If we do decide to adopt an existing plugin system, it's not something that's going to happen overnight, so I wouldn't make that block up any fixes on the current system. |
As reported in OpenRefine/OpenRefine#1882
with analysis in OpenRefine/OpenRefine#1889
The text was updated successfully, but these errors were encountered: