Hello,

Tuscany's use of classloaders doesn't seem to be well-defined, even though
the concept of a runtime classLoader and contribution classloaders should
have made it easy to isolate these namespaces. All Tuscany samples and tests
are currently run with a single application classloader containing
absolutely everything including Tuscany, all the jar files Tuscany requires
and all the Java contribution jars/folders.

I am running Tuscany as a bundle inside an OSGi runtime, and came across
some issues with classloading when multiple classloaders are used.
Even though these can be fixed for OSGi without changes to the non-OSGi
related code in Tuscany, it will be good to fix these properly in Tuscany
since these issues affect anyone who wants to use Tuscany without a great
big classloader hierarchy containing Tuscany, all its dependencies and all
contributions.
Here is my understanding of classloaders used by Tuscany:

1) Tuscany Runtime classloader
This is the classloader used to load Tuscany itself. It should be able to
locate all Tuscany jar files and all its dependent jar files (eg. axis2).
There are many locations in Tuscany which explicitly read the
classloader used to load one Tuscany class in order to load another Tuscany
class. For example, many of the files defined in META-INF/services  and the
classes they refer to are loaded in this way.

2) Contribution classloaders
Referred to as application classloader in the DefaultSCADomain, a separate
classloader can (in theory) be used to refer to the contributions - in the
case of DefaultSCADomain, this is a single classloader associated with a
single contribution.

There is currently no concept of a contribution classloader for each
contribution (except for OSGi contributions), and all resolution of classes
from contributions is done using a single classloader. This does not reflect
the SCA specification, which requires contribution imports/exports to be
explicitly specified - requiring multiple classloaders to enforce the
import/export definition during class resolution.

3) Thread context classloader
Tuscany sets thread context classloader only in one class -
HotUpdatableSCADomain (which sets Context classloader to a single
URLClassLoader containing all the contribution jar files, with the original
context classloader as parent).

Tuscany uses the thread context classloader in around 30 different
locations. Around half of these have FIXMEs saying that the classloader
should be passed in. Many of these uses of the context classloader are
trying to obtain the runtime classloader. And at least one is trying to
obtain the contribution classloader.

Many libraries which are used by Tuscany require the thread context
classloader to be able to load the library classes. Most of these use the
factory pattern and dynamically load implementation classes to create
instances using the context classloader (eg.
javax.xml.datatype.DataTypeFactory.newInstance()). The context classloader
should in these cases to be able to find classes in Tuscany's dependent jar
files, and hence the classloader used to load these dependent jars should be
on the thread context classloader hierarchy.

Axis2 libraries (and possibly others) use the thread context classloader to
load Tuscany classes. Axis2 needs to find Tuscany's message receivers
org.apache.tuscany.sca.binding.ws.axis2.Axis2ServiceInOutSyncMessageReceiverand
org.apache.tuscany.sca.binding.ws.axis2.Axis2ServiceInMessageReceiver, and
these are loaded by Axis2 using the  context classloader. So Tuscany
runtimeClassloader should be on the thread context classloader hierarchy.

4) Java Application classloader
 Tuscany doesn't directly rely on the application classloader (the
classloader corresponding to CLASSPATH). But since Tuscany doesn't set the
thread context classloader, for any application that doesn't explicitly set
thread context classloader, there is an indirect dependency on the
application classloader, and hence CLASSPATH should contain everything
required by the thread context classloader (which at the moment includes the
entire world).

So we have the following requirements on classloaders:


   1. Tuscany runtime classloader should find Tuscany and all its
   dependencies (essentially tuscany-sca-manifest.jar).
   2. Contributions are not required to be on the classpath, nor do they
   have to be visible to the thread context classloader. One classloader per
   contribution is required to enforce SCA contribution import/export
   semantics.
   3. Thread context classloader should be able to see Tuscany's
   dependencies and also Tuscany classes - so this can be the same as (or a
   child of) the Tuscany runtime classloader.
   4. Any code using Tuscany should either have Tuscany runtime and its
   dependencies on the CLASSPATH, or should set the thread context classloader
   for its threads using Tuscany. All Tuscany tests rely on CLASSPATH, while
   Tuscany can be run inside OSGi without CLASSPATH, by setting the context
   classloader.


I would like to propose the following fixes to Tuscany classloading (my aim
is to implement neater isolation without too much impact on the code):

1) Fix contribution classloading to enforce SCA contribution specification
by using one classloader per contribution. Remove the requirement for
contributions to be present in the classpath - Tuscany runtime classloader,
thread context classloader and the application classloader should not be
required to see the contribution classes.

2) Remove unnecessary usages of thread context classloader in Tuscany where
the classloader that is required is the  Tuscany Runtime classloader. It
should be possible in many cases to determine the classloader directly
without having to pass classloaders around. This is already done in some
places, so this is just making the code consistent.

3) There are many uses of the thread context classloader where it is not
very clear which classloader is actually required. I would suggest that as
far as possible, Tuscany should avoid using the thread context classloader,
and in places where it cannot be avoided without major changes, the code
should be commented to show which classloader is required.
As an example (the FIXME is from the existing code)

*public JAXBElement<?> create(ElementInfo element, TransformationContext
context) {*
*    .....*
*    ClassLoader classLoader = context != null ? context.getClassLoader() :
null;*
*    if (classLoader == null) {*
*        //FIXME Understand why we need this, the classloader should be
passed in*
*        classLoader = Thread.currentThread().getContextClassLoader();*
*    }*
*    Class<?> factoryClass = Class.forName(factoryClassName, true,
classLoader);*
*    ....*
*}*

Given that Tuscany and its dependencies need to be visible from the thread
context classloader anyway because other libraries need them there, this
change is to merely to clean up Tuscany classloading and provide more
clarity. It does not aim to remove the external dependencies on the thread
context classloader.

4) It will be good to ensure that all uses of the Tuscany runtime
classloader (eg. to load META-INF/services) continue to work even if each
Tuscany module was loaded by a different classloader (ie, Tuscany runtime
classloader is a hierarchy of module classloaders rather than a single one).
This is not strictly necessary at the moment, but it is necessary if we want
to package Tuscany modules as separate OSGi bundles.

5) Document Tuscany's requirements on classloaders (CLASSPATH and thread
context classloader).


I would appreciate your feedback, both regarding whether there is better way
to fix this, and on areas which I have missed out or
misunderstood. I will be happy to provide a patch if this is an acceptable
solution.




Thank you...

Regards,

Rajini

Reply via email to