Bug 63612 - PooledConnection#connectUsingDriver, Thread.currentThread().getContextClassLoader() may be null
Summary: PooledConnection#connectUsingDriver, Thread.currentThread().getContextClassLo...
Status: NEEDINFO
Alias: None
Product: Tomcat Modules
Classification: Unclassified
Component: jdbc-pool (show other bugs)
Version: unspecified
Hardware: All Linux
: P2 critical (vote)
Target Milestone: ---
Assignee: Tomcat Developers Mailing List
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2019-07-26 10:14 UTC by clemensdev
Modified: 2019-08-06 08:16 UTC (History)
0 users



Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description clemensdev 2019-07-26 10:14:07 UTC
From time to time we are facing the follwing exception (call stack):
...
Caused by: java.sql.SQLException: Unable to load class: org.mariadb.jdbc.Driver from ClassLoader:http://java.net.URLClassLoader@4c873330;ClassLoader:null
        at org.apache.tomcat.jdbc.pool.PooledConnection.connectUsingDriver(PooledConnection.java:292)
        at org.apache.tomcat.jdbc.pool.PooledConnection.connect(PooledConnection.java:212)
        at org.apache.tomcat.jdbc.pool.ConnectionPool.createConnection(ConnectionPool.java:736)
        at org.apache.tomcat.jdbc.pool.ConnectionPool.borrowConnection(ConnectionPool.java:668)
        at org.apache.tomcat.jdbc.pool.ConnectionPool.getConnection(ConnectionPool.java:198)
        at org.apache.tomcat.jdbc.pool.DataSourceProxy.getConnection(DataSourceProxy.java:132)
        at org.apache.torque.Torque.getConnection(Torque.java:924)
        ... 53 common frames omitted
Caused by: java.lang.ClassNotFoundException: Unable to load class: org.mariadb.jdbc.Driver from ClassLoader:http://java.net.URLClassLoader@4c873330;ClassLoader:null
        at org.apache.tomcat.jdbc.pool.ClassLoaderUtil.loadClass(ClassLoaderUtil.java:56)
        at org.apache.tomcat.jdbc.pool.PooledConnection.connectUsingDriver(PooledConnection.java:280)
        ... 59 common frames omitted
Caused by: java.lang.ClassNotFoundException: Classloader is null
        at org.apache.tomcat.jdbc.pool.ClassLoaderUtil.loadClass(ClassLoaderUtil.java:40)
        ... 60 common frames omitted

According to the code (in PooledConnection# connectUsingDriver) Thread.currentThread().getContextClassLoader() returns null

Googling for " Thread.currentThread().getContextClassLoader() is null" the common demoniator seems to be `getContextClassLoader can be null`. If this is true there should be
a) a null-check in PooledConnection#connectUsingDriver
b) if null, then there should be a fallback-Classloader (the system class laoder?)
Comment 1 clemensdev 2019-07-26 10:15:32 UTC
Context:
Debian GNU/Linux 9 \n \l
java version 1.8.0_162
Tomcat 8.5.35
Comment 2 clemensdev 2019-07-29 05:36:47 UTC
Unfortunately not easily extractable/reproducible

Maybe :

th = new Thread() {
@Override
public void run() {
« do something that requires a connection from the pool »
}
} ;
th.set getContextClassLoader( null ) ; 
th.run() ;
Comment 3 Christopher Schultz 2019-07-29 20:09:52 UTC
(In reply to clemensdev from comment #2)
> th.set getContextClassLoader( null ) ; 
> th.run() ;

Um... why would you do such a thing?
Comment 4 romain.manni-bucau 2019-07-29 20:25:57 UTC
Some libs do but pool and pool tries first tomcatjdbc classloader so if the driver is in the webapp it fails. Init should cache the value per webapp imho.
Comment 5 clemensdev 2019-07-30 11:31:51 UTC
>Um... why would you do such a thing?
for the unit test only to force Thread.currentThread().getContextClassLoader() to be null ;-)
Comment 6 Konstantin Kolinko 2019-07-30 12:45:32 UTC
1. If "Thread.currentThread().getContextClassLoader()" is null, it essentially means that this thread is not associated with a web application.

(Thus it cannot access the classes provided by web application, cannot use log configuration specific to this web application and so on).


2. ClassLoaderUtil.loadClass(ClassLoaderUtil.java:56) does a loop over an array of ClassLoaders.

The following line

   throw new ClassNotFoundException("Classloader is null");

just simulates a ClassNotFoundException that is is thrown in the non-null branch (by the "Class.forName(..)" call) when the class is not found.

3. The TCCL is the last ClassLoader in the array, so there are no more classloaders to try.


The handling of the null value may be improved, but I see nothing more than providing a different error message. The result will be the same ClassNotFoundException.

I guess that this is not what you are looking for.


1. I think that you can avoid this error if you put the database driver into the same classloader where tomcat-jdbc.jar was loaded from. That is: into the "lib" directory of Tomcat, like people did in the good old times.

(I guess that you bundled the database driver with you web application.)

2. You would better find the rood cause behind the null TCCL in that thread.

Even if you avoid the driver loading issue, the thread may experience other oddities. What comes to mind:
- a wrong logging configuration,
- being unable to access JNDI directory.
Comment 7 clemensdev 2019-08-06 07:20:31 UTC
Thx Konstatin
>I think that you can avoid this error if you put the 
>database driver into the same classloader where 
>tomcat-jdbc.jar was loaded from
mariadb-connector.jar is indeed not in the same directory as tomcat-jdbc.jar, i.e. not in the "lib" directory of Tomcat.
The site we are seeing theses exceptions only in "certain circumstances" (whtever these are, high load?) throws the exceptions ...

>You would better find the rood cause behind the null TCCL in that thread
I completely agree

>If "Thread.currentThread().getContextClassLoader()" is 
>null, it essentially means that this thread is not associated 
>with a web application
how can a thread 
a) not be associated with the web application?
b) have no class loader?
Comment 8 clemensdev 2019-08-06 07:34:59 UTC
looking into the logs of the "affected site" I guess to have made up a "common denominator" for the "the null TCCL in that thread".

All callstacks "originate" from a parallelStream execution (java.util.Collection.parallelStream())!
Comment 9 Mark Thomas 2019-08-06 08:16:03 UTC
That is likely because of the fix to this memory leak:
https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8172726

The Fork/Join framework in the JRE does not play nicely with the per web application classloader environment. In Java 7/8 you get memory leaks and security issues (Tomcat protects against these by default) and in Java 9 you get a null TCCL.

There really isn't a simple solution to this issue for code that depends on ForkJoinPool.commonPool().

I'll note that JNDI (which depends on the TCCL to identify the correct InitialContext) is also going to fail for similar reasons.

If you only have a single web application then you can work-around it by using a custom thread factory for the common fork/join pool that essentially restores the Java 7/8 behaviour but even then you won't be able to reload the web application (as that triggers a change in class loader).

You may also be able to work-around it be explicitly passing the correct class loader to the task and setting the TCCL for the duration of the task.

I'm leaning towards closing this as INVALID as it is not an issue with jdbc-pool.