JDK-8060068/JDK-8067904 DriverManager clean-up introduces bootstrap problem that breaks production drivers
Robert Gibson
robbiexgibson at yahoo.com
Fri Apr 3 09:33:12 UTC 2015
Hi there,
We are doing some early testing with JDK 9 and have discovered that the changes made to java.sql.DriverManager back in November/December have introduced an incompatibility with our JDBC driver (that we have used unchanged since Java 6) when it is pre-loaded with Class.forName (as recommended by Tomcat and specified as being harmless in the JavaDoc). We filed a bug report but it got an incident ID JI-9019539 and then disappeared into a black hole.
In the hope of the problem getting a bit more attention I attach a reproducible test case that is representative of the commercially-available JDBC driver that we use (hint: the name rhymes with MyFace and it's used in a lot of banks). I guess the problem is that the driver is calling DriverManager.getDrivers() as part of its registration process, which is now causing the stack overflow given at the end of this mail, after the code.
If you compile the attached files into test.jar and run
java -cp test.jar JDBCTest n
java -cp test.jar JDBCTest y
then the test should print
Trying to connect to jdbc:mydriver
OK
in both cases (as it does with Java 8).
When running with Java 9, the second command prints
Trying to connect to jdbc:mydriver
FAIL
No suitable driver found for jdbc:mydriver
Hope this is helpful.
Regards,
Robert
JDBCTest.java
import java.io.*;
import java.sql.*;
import java.util.*;
public class JDBCTest
{
public static void main(String... argv) throws Exception
{
if (argv.length > 1) DriverManager.setLogWriter(new PrintWriter(System.out));
String[] urls = new String[] {"jdbc:mydriver"};
if (argv.length > 0 && argv[0].equals("y")) {
loadClass("MyDriver");
}
for (String url : urls) {
System.out.println("Trying to connect to " + url);
try {
Connection c = DriverManager.getConnection (url);
if (c == null) System.out.println("FAIL");
else System.out.println("OK");
}catch(SQLException e) {
System.out.println("FAIL");
System.out.println(e.getMessage());
}
}
}
public static void loadClass(String classname) {
try { Class.forName(classname); } catch(ClassNotFoundException e) { System.out.println("Class not found: " + classname); }
}
}
MyDriver.java
import java.sql.*;
import java.util.Enumeration;
import java.util.Properties;
import java.util.logging.Logger;
public class MyDriver implements java.sql.Driver {
static {
new MyDriver();
}
public MyDriver() {
this.registerWithDriverManager();
}
public final Connection connect(String url, Properties props) throws SQLException {
return acceptsURL(url) ? new MyConnection() : null;
}
public boolean acceptsURL(String url) throws SQLException {
return url.startsWith("jdbc:mydriver");
}
public DriverPropertyInfo[] getPropertyInfo(String var1, Properties props) throws SQLException {
return null;
}
public int getMajorVersion() {
return 0;
}
public int getMinorVersion() {
return 1;
}
public boolean jdbcCompliant() {
return false;
}
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
throw new SQLFeatureNotSupportedException("No logging for you");
}
protected void registerWithDriverManager() {
try {
synchronized(DriverManager.class) {
DriverManager.registerDriver(this);
Enumeration e = DriverManager.getDrivers();
while(e.hasMoreElements()) {
Driver d = (Driver)e.nextElement();
if(d instanceof MyDriver && d != this) {
DriverManager.deregisterDriver(d);
}
}
}
} catch (SQLException ex) {
}
}
}
MyConnection.java
import java.sql.*;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;
public class MyConnection implements Connection {
@Override
public Statement createStatement() throws SQLException {
return null;
}
@Override
public PreparedStatement prepareStatement(String sql) throws SQLException {
return null;
}
@Override
public CallableStatement prepareCall(String sql) throws SQLException {
return null;
}
@Override
public String nativeSQL(String sql) throws SQLException {
return null;
}
@Override
public void setAutoCommit(boolean autoCommit) throws SQLException {
}
@Override
public boolean getAutoCommit() throws SQLException {
return false;
}
@Override
public void commit() throws SQLException {
}
@Override
public void rollback() throws SQLException {
}
@Override
public void close() throws SQLException {
}
@Override
public boolean isClosed() throws SQLException {
return false;
}
@Override
public DatabaseMetaData getMetaData() throws SQLException {
return null;
}
@Override
public void setReadOnly(boolean readOnly) throws SQLException {
}
@Override
public boolean isReadOnly() throws SQLException {
return false;
}
@Override
public void setCatalog(String catalog) throws SQLException {
}
@Override
public String getCatalog() throws SQLException {
return null;
}
@Override
public void setTransactionIsolation(int level) throws SQLException {
}
@Override
public int getTransactionIsolation() throws SQLException {
return 0;
}
@Override
public SQLWarning getWarnings() throws SQLException {
return null;
}
@Override
public void clearWarnings() throws SQLException {
}
@Override
public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
return null;
}
@Override
public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
return null;
}
@Override
public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
return null;
}
@Override
public Map<String, Class<?>> getTypeMap() throws SQLException {
return null;
}
@Override
public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
}
@Override
public void setHoldability(int holdability) throws SQLException {
}
@Override
public int getHoldability() throws SQLException {
return 0;
}
@Override
public Savepoint setSavepoint() throws SQLException {
return null;
}
@Override
public Savepoint setSavepoint(String name) throws SQLException {
return null;
}
@Override
public void rollback(Savepoint savepoint) throws SQLException {
}
@Override
public void releaseSavepoint(Savepoint savepoint) throws SQLException {
}
@Override
public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
return null;
}
@Override
public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
return null;
}
@Override
public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
return null;
}
@Override
public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
return null;
}
@Override
public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
return null;
}
@Override
public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
return null;
}
@Override
public Clob createClob() throws SQLException {
return null;
}
@Override
public Blob createBlob() throws SQLException {
return null;
}
@Override
public NClob createNClob() throws SQLException {
return null;
}
@Override
public SQLXML createSQLXML() throws SQLException {
return null;
}
@Override
public boolean isValid(int timeout) throws SQLException {
return false;
}
@Override
public void setClientInfo(String name, String value) throws SQLClientInfoException {
}
@Override
public void setClientInfo(Properties properties) throws SQLClientInfoException {
}
@Override
public String getClientInfo(String name) throws SQLException {
return null;
}
@Override
public Properties getClientInfo() throws SQLException {
return null;
}
@Override
public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
return null;
}
@Override
public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
return null;
}
@Override
public void setSchema(String schema) throws SQLException {
}
@Override
public String getSchema() throws SQLException {
return null;
}
@Override
public void abort(Executor executor) throws SQLException {
}
@Override
public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {
}
@Override
public int getNetworkTimeout() throws SQLException {
return 0;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
}
Stack overflow (recursive call to new MyDriver())
[1] MyDriver.registerWithDriverManager (MyDriver.java:47)
[2] MyDriver.<init> (MyDriver.java:12)
[3] sun.reflect.NativeConstructorAccessorImpl.newInstance0 (native method)
[4] sun.reflect.NativeConstructorAccessorImpl.newInstance (NativeConstructorAccessorImpl.java:62)
[5] sun.reflect.DelegatingConstructorAccessorImpl.newInstance (DelegatingConstructorAccessorImpl.java:45)
[6] java.lang.reflect.Constructor.newInstance (Constructor.java:425)
[7] java.lang.Class.newInstance (Class.java:464)
[8] java.util.ServiceLoader$LazyIterator.nextService (ServiceLoader.java:378)
[9] java.util.ServiceLoader$LazyIterator.next (ServiceLoader.java:402)
[10] java.util.ServiceLoader$1.next (ServiceLoader.java:478)
[11] java.sql.DriverManager$2.run (DriverManager.java:614)
[12] java.sql.DriverManager$2.run (DriverManager.java:594)
[13] java.security.AccessController.doPrivileged (native method)
[14] java.sql.DriverManager.ensureDriversInitialized (DriverManager.java:594)
[15] java.sql.DriverManager.getDrivers (DriverManager.java:437)
[16] MyDriver.registerWithDriverManager (MyDriver.java:47)
[17] MyDriver.<init> (MyDriver.java:12)
[18] MyDriver.<clinit> (MyDriver.java:8)
[19] java.lang.Class.forName0 (native method)
[20] java.lang.Class.forName (Class.java:286)
[21] JDBCTest.loadClass (JDBCTest.java:28)
[22] JDBCTest.main (JDBCTest.java:12)
More information about the core-libs-dev
mailing list