Andreas Sewe created LUCENE-7870:
------------------------------------
Summary: Use cl.loadClass(...) instead of Class.forName(..., cl)
in SPIClassIterator
Key: LUCENE-7870
URL: https://issues.apache.org/jira/browse/LUCENE-7870
Project: Lucene - Core
Issue Type: Bug
Affects Versions: 6.1, 5.2.1
Environment: Eclipse Equinox 4.7
Reporter: Andreas Sewe
This issue is initially described in [Eclipse Bug
517935|https://bugs.eclipse.org/bugs/show_bug.cgi?id=517935] and prevents
multiple versions of Lucene Core coexisting in an Equinox environment (FYI,
Equinox is the OSGi container used by the Eclipse IDE).
Here’s how the problem manifests: While Equinox cleanly separates two versions
of the same Lucene Core bundle (e.g., {{org.apache.lucene.core_5.2.1}} and
{{org.apache.lucene.core_6.1.0}}) using two different bundle class loaders, it
uses a single context classloader for all threads: the
[{{ContextFinder}}|https://wiki.eclipse.org/Context_Class_Loader_Enhancements#Context_Finder_2].
When asked to load a class, the {{ContextFinder}} *initiates* a search through
all bundle class loaders currently “on“ the call stack; the class to be loaded
is then *defined* by the respective bundle’s class loader.
And here is where the use of {{Class.forName(..., classLoader)}} rather than
{{classLoader.loadClass(...)}} causes problems. Consider the following sequence
of events:
# The {{NamedSPILoader}} of bundle {{o.a.l.core_5.2.1}} (re)loads some service
(e.g., the {{Lucene50PostingFormat}}).
## It (through {{SPIClassIterator}}) first uses {{Class.forName}} with the
bundle class loader of {{o.a.l.core_5.2.1}} (as per LUCENE-4713) to
successfully load {{Lucene50PostingFormat}} from the {{o.a.l.core_5.2.1}}
bundle.
## Then (through another {{SPIClassIterator}}) it uses {{Class.forName}} with
the thread’s context class loader (here: {{ContextFinder}}) to load
{{Lucene50PostingFormat}}. The {{ContextFinder}} delegates loading to the
{{o.a.l.core_5.2.1}} bundle’s class loader, as that bundle is topmost on the
call stack. This bundle class loader (again) successfully loads
{{Lucene50PostingFormat}} from the {{o.a.l.core_5.2.1}} bundle.
# Later, the {{NamedSPILoader}} of *another* bundle {{o.a.l.core_6.1.0}} loads
the {{Lucene50PostingFormat}} service.
## It (through {{SPIClassIterator}}) first uses {{Class.forName}} with the
bundle class loader of {{o.a.l.core_6.1.0}} to successfully load
{{Lucene50PostingFormat}} from the {{o.a.l.core_6.1.0}} bundle.
## Then (through another {{SPIClassIterator}}) it uses {{Class.forName}} with
the thread’s context class loader (the same {{ContextFinder}} again) to load
{{Lucene50PostingFormat}}. As {{Class.forName}} remembers that the
{{ContextFinder}} has successfully initiated the loading of
{{Lucene50PostingFormat}} before, it simply returns the {{Class}} instance
defined earlier in step _1.2_. But that class was defined by a different bundle
class loader, namely that of {{o.a.l.core_5.2.1}}! This cache look up happens
in native code; the {{ContextFinder}}‘s {{loadClass}} method isn’t even called,
so there’s no way it can load the class from the {{o.a.l.core_6.1.0}} bundle,
even though it now is topmost on the stack.
## Casting the {{Lucene50PostingFormat}} loading from bundle
{{o.a.l.core_5.2.1}} to {{PostingFormat}} from bundle {{o.a.l.core_6.1.0}} then
fails, leaving the {{o.a.l.core_6.1.0}} bundle in a broken state.
It {{SPIClassIterator.next()}} would use {{classLoader.loadClass(...)}} rather
than {{Class.forName(..., classLoader}}), then class loading in step _1.2_
wouldn’t *lock in* the {{Lucene50PostingFormat}} class from
{{org.apache.lucene.core_5.2.1}}; instead, step _2.2_ would perform a
completely independent look up that retrieves the class from the correct
bundle. The cast in step _2.3_ would then succeed.
At Eclipse Orbit, we plan to distribute a [patched
version|https://git.eclipse.org/r/98804/] of Lucene Core, but obviously we
would like to see the (one-line) change included in the upstream project.
BTW, if you fear that bypassing {{Class.forName}} “caching” affects
performance, then there’s no need to worry: Most {{ClassLoader}}
implementations cache as well ({{findLoadedClass}}); it’s only
{{ContextFinder}} that [overrides {{loadClass}}
wholesale|https://git.eclipse.org/c/equinox/rt.equinox.framework.git/tree/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/framework/ContextFinder.java?h=R4_7_maintenance],
as it dynamically (based on the current call stack) delegates to the (caching)
bundle class loaders.
--
This message was sent by Atlassian JIRA
(v6.3.15#6346)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]