Bug #38060 NullPointerException in getServerCharacterEncoding(), caused by isValid()
Submitted: 11 Jul 2008 21:15 Modified: 14 Oct 2010 8:54
Reporter: Ondra Zizka Email Updates:
Status: Can't repeat Impact on me:
None 
Category:Connector / J Severity:S2 (Serious)
Version:5.1.6 OS:Any
Assigned to: CPU Architecture:Any
Tags: JDBC Connector NPE NullPointerException getServerCharacterEncoding()

[11 Jul 2008 21:15] Ondra Zizka
Description:
MySQL:  5.0.51a-3ubuntu5.1
JDBC:   5.1.5 (NetBeans bundled)

I'm getting an exception while processing some "documents" in a loop:

...
stmtInsertSpis.setString(1, sSpis);
...

INFO:   Processing item no. 2023 - folder INS 46/2008
SEVERE: null
java.lang.NullPointerException
at com.mysql.jdbc.ConnectionImpl.getServerCharacterEncoding(ConnectionImpl.java:3040)
 at com.mysql.jdbc.PreparedStatement.setString(PreparedStatement.java:4072)
 at cz.dynawest.isir.Conversion.ZpracujArchiv(Conversion.java:166)
 at cz.dynawest.isir.xmlvypis.Main.main(Main.java:49)

(sSpis is NOT null.)

How to repeat:
Hard to tell, this exception appears after several hundreds of rounds, but the exact number differs. I'll download the Connector/J sources and try to investigate a bit.

In the project, I mix JDBC and IBatis, which use two different Connections.

Few last queries from the log are: (this is also typical sequence of one loop)

    9 Query       INSERT isir_spisy SET znacka = 'INS 46/2008', zalozeni = NOW()
   10 Query       rollback
   10 Query       INSERT INTO isir_osoby       (uid, jmeno, prijmeni, id_adresa_trvala, id_adresa_prechodna, rc, nar, titul_pred, titul_za)       VALUES       ('MIVSJOSE161145  1', 'Josef', 'Mivsk', null, null,         '451116036', null, '', '')       ON DUPLICATE KEY UPDATE id = LAST_INSERT_ID(id), jmeno = VALUES(jmeno), prijmeni = VALUES(prijmeni),          id_adresa_trvala = VALUES(id_adresa_trvala), id_adresa_prechodna = VALUES(id_adresa_prechodna),          rc = VALUES(rc), nar = VALUES(nar), titul_pred = VALUES(titul_pred), titul_za = VALUES(titul_za)
    10 Query       SELECT LAST_INSERT_ID()
    10 Query       commit
    10 Query       rollback

Suggested fix:
.
[11 Jul 2008 22:04] Ondra Zizka
Also happens with Connector/J 5.1.6.

java.lang.NullPointerException
        at com.mysql.jdbc.ConnectionImpl.getServerCharacterEncoding(ConnectionImpl.java:3040)
        at com.mysql.jdbc.PreparedStatement.setString(PreparedStatement.java:4072)
        at cz.dynawest.isir.Conversion.ZpracujArchiv(Conversion.java:166)
        at cz.dynawest.isir.xmlvypis.Main.main(Main.java:49)

public String getServerCharacterEncoding() {
  if (this.io.versionMeetsMinimum(4, 1, 0)) {
	return (String) this.serverVariables.get("character_set_server");
  } else {
  	return (String) this.serverVariables.get("character_set");
  }
}

Code lines reported by the exception differs from those in sources in mysql-connector-java-5.1.6.tar.gz.
[11 Jul 2008 22:18] Ondra Zizka
So, it's the this.io - which is null upon that exception.

Maybe I should also mention that I use 64-bit linux kernel.
[11 Jul 2008 23:15] Ondra Zizka
Seems like Connection::isValid(int) is causing this.
In every round, I checked for the connection:

  conn.isValid(3);

When I commented this out, the NullPointerException does not appear.

Still, I consider it being a serious bug -> changing from S1 to S2.
[11 Jul 2008 23:39] Ondra Zizka
I think I've found the reason: It's the MySQL's implementation of isValid, which calls abortInternal() in a thread without any synchronization of that thread, which in turn runs for the period given by TimeOut. That's the reason for the random-ness of the bug.

I suggest:

1) Making abortInternal()  synchronized
2) Synchronize launched threads somehow; e.g. launching them via singleton which would not allow more than one isValid()-thread running.
[11 Jul 2008 23:44] Ondra Zizka
And finally, I must say, I wonder how can anyone compile the Connector/J. I've tried, but there are many overloading ambiguities and other problems blocking the build.
[16 Jul 2008 14:49] Ondra Zizka
I've created a JUnit test case, which reproduces the bug; adding as an attachment.

Libraries needed: JUnit and Connector/J 5.1.x.
[16 Jul 2008 14:51] Ondra Zizka
JUnit test case reproducing the bug.

Attachment: ConnectorJ_Bug_isValid_NPE_Test.java (application/octet_stream, text), 3.47 KiB.

[17 Jul 2008 12:10] Tonci Grgin
Hi Ondra and thanks for your report. Building c/J against JDK 1.6 is mistery to me too so I'll forward this to c/J developers...
[17 Jul 2008 13:41] Mark Matthews
The issue is that you can't compile Connector/J with *just* JDK6, as the driver supports JDBC4 and pre-JDBC4 APIs from the same codebase, and there are new classes and methods in JDBC4 that aren't available pre-JDK6.

Therefore, the way it works is that you have to set JAVA_HOME to point to JDK-1.4 or 1.5, and then set the Ant properties "com.mysql.jdbc.java6.javac" with the full path to your JDK6 java compiler, and "com.mysql.jdbc.java6.rtjar" with the full path to your JDK6 runtime class libraries, when invoking Ant.
[26 Jul 2010 11:34] Thorsten Dierkes
I am getting the same error using Connector/J 5.1.13

26.07.10 10:25 : Caused by: java.lang.NullPointerException
26.07.10 10:25 : 	at com.mysql.jdbc.ConnectionImpl.getServerCharacterEncoding(ConnectionImpl.java:3058)
26.07.10 10:25 : 	at com.mysql.jdbc.PreparedStatement.setInternal(PreparedStatement.java:3731)
26.07.10 10:25 : 	at com.mysql.jdbc.PreparedStatement.setLong(PreparedStatement.java:3753)

Looking at the source, "io" seems to be "null":

	/**
	 * Returns the server's character set
	 * 
	 * @return the server's character set.
	 */
	public String getServerCharacterEncoding() {
		if (this.io.versionMeetsMinimum(4, 1, 0)) {
			return (String) this.serverVariables.get("character_set_server");
		} else {
			return (String) this.serverVariables.get("character_set");
		}
	}
[26 Jul 2010 11:41] Tonci Grgin
Has *anyone* actually tried what Mark suggested? After all, he wrote the connector...
[26 Jul 2010 12:40] Thorsten Dierkes
He just described how to compile the source. This works as described,
for example:

> export JAVA_HOME=/opt/java/jdk1.5.0_22
> ant -Dcom.mysql.jdbc.java6.javac=/opt/java/jdk1.6.0_20/bin/javac -Dcom.mysql.jdbc.java6.rtjar=/opt/java/jdk1.6.0_20/jre/lib/rt.jar
[14 Oct 2010 6:56] Tonci Grgin
Ondra, Thorsten, anyone tried isValid(0)?
[14 Oct 2010 8:54] Tonci Grgin
Guys, can't repeat using latest sources and this test case (using server-side PS):
		System.out.println("java.vm.version         : " + System.getProperty("java.vm.version"));
		System.out.println("java.vm.vendor          : " + System.getProperty("java.vm.vendor"));
		System.out.println("java.runtime.version    : " + System.getProperty("java.runtime.version"));
		System.out.println("os.name                 : " + System.getProperty("os.name"));
		System.out.println("os.version              : " + System.getProperty("os.version "));
		System.out.println("sun.management.compiler : " + System.getProperty("sun.management.compiler"));

		if (!com.mysql.jdbc.Util.isJdbc4()) {
			System.out.println("Exited");
			return;
		}
		
		Connection newConn = getConnectionWithProps((Properties)null);

		try {
		    try{
		        this.pstmt = newConn.prepareStatement( "SELECT ?" );
		        
		        for( int i = 0; i < 10000; i++ ) {
		          newConn.isValid(1);
		          this.pstmt.setInt( 1, 1 );
		          this.rs = this.pstmt.executeQuery();
		          Thread.sleep( 2 ); //50
		          this.rs.close();
		        }
		      }
		      catch( NullPointerException ex ){
		        fail("NPE occured, too bad..");
		      }
		      catch( InterruptedException ex ){
		      } 

		} finally {
			closeMemberJDBCResources();
		}

Output:
.Loading JDBC driver 'com.mysql.jdbc.Driver'
Done.

Done.

java.vm.version         : 11.0-b16
java.vm.vendor          : Sun Microsystems Inc.
java.runtime.version    : 1.6.0_11-b03
os.name                 : Windows Server 2008
os.version              : null
sun.management.compiler : HotSpot Client Compiler
Connected to 5.1.31-log
Time: 31,804
OK (1 test)

Changing timeout values doesn't seem to change anything but the time I wait for test to finish:
		          newConn.isValid(1);
		          this.pstmt.setInt( 1, 1 );
		          this.rs = this.pstmt.executeQuery();
		          Thread.sleep( 4 );
Time: 51,903
OK (1 test)

		          newConn.isValid(1);
		          this.pstmt.setInt( 1, 1 );
		          this.rs = this.pstmt.executeQuery();
		          Thread.sleep( 50 );
Time: 517,713
OK (1 test)

...
[14 Oct 2010 9:29] Thorsten Dierkes
Hi Tonci,

I am getting this bug while using about 30 Connections, each on has its own java thread, running on a quad-core cpu. It is hard to repeat this bug, sometimes it takes hours.
[14 Oct 2010 9:36] Tonci Grgin
Thorsten, what can I say to that?

Hours are not a problem, problem is code that reliably reproduces the problem... Originally, there was nothing suggesting multi-threading could cause this. Mind you, things can be thread safe but not necessarily shareable, which is what happens imo.

So, if you attach complete test case I will give it a try.