-
Bug
-
Resolution: Unresolved
-
P5
-
8u60, 9
-
generic
FULL PRODUCT VERSION :
openjdk version "1.8.0_45-internal"
OpenJDK Runtime Environment (build 1.8.0_45-internal-b14)
OpenJDK 64-Bit Server VM (build 25.45-b02, mixed mode)
ADDITIONAL OS VERSION INFORMATION :
Linux n1user572-linux 3.19.0-26-generic #28-Ubuntu SMP Tue Aug 11 14:16:32 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux
A DESCRIPTION OF THE PROBLEM :
Base problem:
* Create some classloader and with JDBC driver .jar-files (for example: org.h2.Driver);
* Execute: DriverManager.getDrivers() - it return Enumeration with org.h2.Driver instance;
* Create another classloader and with JDBC driver .jar-files (for example: org.h2.Driver);
* Execute: DriverManager.getDrivers() - it empty Enumeration <== this is incorrect value;
* Execute again: DriverManager.getDrivers() - it return Enumeration with org.h2.Driver instance <== this is correct value.
This bug occures also with other DriverManager methods:
* java.sql.DriverManager#getDrivers
* java.sql.DriverManager#getConnection(java.lang.String, java.util.Properties)
* java.sql.DriverManager#getConnection(java.lang.String, java.lang.String, java.lang.String)
* java.sql.DriverManager#getConnection(java.lang.String)
* java.sql.DriverManager#getDriver
The core of problem:
* DriverManager.registeredDrivers is initialized by static initialization on first access to DriverManager class;
* When you first time call getDrivers method from another classloader:
* java.sql.Driver classes from this classloader is not present in DriverManager.registeredDrivers yet;
* DriverManager.registeredDrivers skips previously loaded driver by check in method isDriverAllowed like: driver.getClass() != Class.forName(driver.getClass().getName(), true, classLoader), because this drivers was loaded by another classloder;
* Class.forName(driver.getClass().getName(), true, classLoader) invokes static initializer and register driver from currenct classloder (concurrent modification don't happend, because DriverManager.registeredDrivers is copy-on-write collection);
* method returns incorrect empty result;
* When you second time call getDrivers method from another classloader there are java.sql.Driver classes from both namespaces present in DriverManager.registeredDrivers and getDrivers return correct result.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
You can reproduce error with minimal project: https://github.com/bozaro/jdk-bug-drivermanager-classloader
by executing command: ./gradlew test
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Test passed
ACTUAL -
Test failed
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
Minimal project https://github.com/bozaro/jdk-bug-drivermanager-classloader/
Test class:
```
package ru.bozaro;
import org.junit.Assert;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.sql.Driver;
import java.sql.DriverManager;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
/**
* Test for reproducing bug with DriverManager and classloader bug.
*/
public class DriverManagerTest {
@Test
public void testDriverManager() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// Get drivers.
Set<Class<? extends Driver>> drivers = getDrivers();
Assert.assertFalse(drivers.isEmpty());
WrapperClassLoader loader = new WrapperClassLoader(Thread.currentThread().getContextClassLoader());
// Load worker in another class loader.
Class<?> workerClass = loader.loadClass(Worker.class.getName());
Assert.assertNotSame(Worker.class, workerClass);
// Run worker.
Runnable worker = (Runnable) workerClass.getConstructor().newInstance();
worker.run();
}
public static class Worker implements Runnable {
@Override
public void run() {
Set<Class<? extends Driver>> drivers1 = getDrivers();
Set<Class<? extends Driver>> drivers2 = getDrivers();
Assert.assertFalse(drivers2.isEmpty());
Assert.assertFalse(drivers1.isEmpty()); // <!==== FAILURE
}
}
public static Set<Class<? extends Driver>> getDrivers() {
final Set<Class<? extends Driver>> result = new HashSet<>();
final Enumeration<Driver> drivers = DriverManager.getDrivers();
System.out.println("DriverManager.getDrivers() - " + DriverManagerTest.class.getClassLoader());
while (drivers.hasMoreElements()) {
Driver driver = drivers.nextElement();
result.add(driver.getClass());
System.out.println(" * " + driver.getClass().getName());
}
return result;
}
public static class WrapperClassLoader extends ClassLoader {
public WrapperClassLoader(ClassLoader parent) {
super(parent);
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
if (name.startsWith("ru.bozaro.")) {
try {
InputStream resource = getResourceAsStream(name.replace(".", "/") + ".class");
if (resource == null) throw new ClassCastException(name);
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
byte[] block = new byte[1024];
while (true) {
int size = resource.read(block);
if (size <= 0) break;
buffer.write(block, 0, size);
}
return defineClass(name, buffer.toByteArray(), 0, buffer.size());
} catch (IOException e) {
throw new ClassCastException(name);
}
}
return super.loadClass(name, resolve);
}
}
}
```
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
I found two workarounds:
* Move JDBC driver to parent classloader;
* Run java.util.ServiceLoader.load(java.sql.Driver.class) before using JDBC.
openjdk version "1.8.0_45-internal"
OpenJDK Runtime Environment (build 1.8.0_45-internal-b14)
OpenJDK 64-Bit Server VM (build 25.45-b02, mixed mode)
ADDITIONAL OS VERSION INFORMATION :
Linux n1user572-linux 3.19.0-26-generic #28-Ubuntu SMP Tue Aug 11 14:16:32 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux
A DESCRIPTION OF THE PROBLEM :
Base problem:
* Create some classloader and with JDBC driver .jar-files (for example: org.h2.Driver);
* Execute: DriverManager.getDrivers() - it return Enumeration with org.h2.Driver instance;
* Create another classloader and with JDBC driver .jar-files (for example: org.h2.Driver);
* Execute: DriverManager.getDrivers() - it empty Enumeration <== this is incorrect value;
* Execute again: DriverManager.getDrivers() - it return Enumeration with org.h2.Driver instance <== this is correct value.
This bug occures also with other DriverManager methods:
* java.sql.DriverManager#getDrivers
* java.sql.DriverManager#getConnection(java.lang.String, java.util.Properties)
* java.sql.DriverManager#getConnection(java.lang.String, java.lang.String, java.lang.String)
* java.sql.DriverManager#getConnection(java.lang.String)
* java.sql.DriverManager#getDriver
The core of problem:
* DriverManager.registeredDrivers is initialized by static initialization on first access to DriverManager class;
* When you first time call getDrivers method from another classloader:
* java.sql.Driver classes from this classloader is not present in DriverManager.registeredDrivers yet;
* DriverManager.registeredDrivers skips previously loaded driver by check in method isDriverAllowed like: driver.getClass() != Class.forName(driver.getClass().getName(), true, classLoader), because this drivers was loaded by another classloder;
* Class.forName(driver.getClass().getName(), true, classLoader) invokes static initializer and register driver from currenct classloder (concurrent modification don't happend, because DriverManager.registeredDrivers is copy-on-write collection);
* method returns incorrect empty result;
* When you second time call getDrivers method from another classloader there are java.sql.Driver classes from both namespaces present in DriverManager.registeredDrivers and getDrivers return correct result.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
You can reproduce error with minimal project: https://github.com/bozaro/jdk-bug-drivermanager-classloader
by executing command: ./gradlew test
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Test passed
ACTUAL -
Test failed
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
Minimal project https://github.com/bozaro/jdk-bug-drivermanager-classloader/
Test class:
```
package ru.bozaro;
import org.junit.Assert;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.sql.Driver;
import java.sql.DriverManager;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
/**
* Test for reproducing bug with DriverManager and classloader bug.
*/
public class DriverManagerTest {
@Test
public void testDriverManager() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// Get drivers.
Set<Class<? extends Driver>> drivers = getDrivers();
Assert.assertFalse(drivers.isEmpty());
WrapperClassLoader loader = new WrapperClassLoader(Thread.currentThread().getContextClassLoader());
// Load worker in another class loader.
Class<?> workerClass = loader.loadClass(Worker.class.getName());
Assert.assertNotSame(Worker.class, workerClass);
// Run worker.
Runnable worker = (Runnable) workerClass.getConstructor().newInstance();
worker.run();
}
public static class Worker implements Runnable {
@Override
public void run() {
Set<Class<? extends Driver>> drivers1 = getDrivers();
Set<Class<? extends Driver>> drivers2 = getDrivers();
Assert.assertFalse(drivers2.isEmpty());
Assert.assertFalse(drivers1.isEmpty()); // <!==== FAILURE
}
}
public static Set<Class<? extends Driver>> getDrivers() {
final Set<Class<? extends Driver>> result = new HashSet<>();
final Enumeration<Driver> drivers = DriverManager.getDrivers();
System.out.println("DriverManager.getDrivers() - " + DriverManagerTest.class.getClassLoader());
while (drivers.hasMoreElements()) {
Driver driver = drivers.nextElement();
result.add(driver.getClass());
System.out.println(" * " + driver.getClass().getName());
}
return result;
}
public static class WrapperClassLoader extends ClassLoader {
public WrapperClassLoader(ClassLoader parent) {
super(parent);
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
if (name.startsWith("ru.bozaro.")) {
try {
InputStream resource = getResourceAsStream(name.replace(".", "/") + ".class");
if (resource == null) throw new ClassCastException(name);
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
byte[] block = new byte[1024];
while (true) {
int size = resource.read(block);
if (size <= 0) break;
buffer.write(block, 0, size);
}
return defineClass(name, buffer.toByteArray(), 0, buffer.size());
} catch (IOException e) {
throw new ClassCastException(name);
}
}
return super.loadClass(name, resolve);
}
}
}
```
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
I found two workarounds:
* Move JDBC driver to parent classloader;
* Run java.util.ServiceLoader.load(java.sql.Driver.class) before using JDBC.