Bug #34518 Memory leak: StatementImpl.execute() leaks the executed sql string.
Submitted: 13 Feb 2008 12:08 Modified: 25 Feb 2008 13:12
Reporter: Shadow Caster
Status: Closed
Category:Connector/J Severity:S2 (Serious)
Version: OS:Any
Assigned to: Mark Matthews Target Version:
Triage: D2 (Serious)

[13 Feb 2008 12:08] Shadow Caster
Description:
The method "StatementImpl.createResultSetUsingServerFetch" creates a
"JDBC4ServerPreparedStatement" which it never closes. The statement registers itself with
the connection when it is created and remains in the "openStatements" table forever.

What makes it worse is that it keeps a reference to the executed sql string, which might
be really really large. The memory leak occurs in the default configuration and is likely
to happen only in production systems, not with small test data, so it's kind of nasty.

How to repeat:
Connection c = ...;

for (;;) {
  Statement s = c.createStatement();
  s.execute("INSERT INTO SomeTable VALUES(x'00000000000....')"); // many zeros
  s.close();
}

Versions:
Connector: mysql-connector-java-5.1.5.zip
Engine: mysql-6.0.3-alpha-win32.zip

Suggested fix:
Disabling resource tracking ("dontTrackOpenResources") serves as a workaround.

Perhaps the JDBC4ServerPreparedStatement should not be registered at all, because it is
not created explicitly by a user.
[13 Feb 2008 12:12] Tonci Grgin
Hi and thanks for your report. I think this behavior is intentional but will have to
check.
[13 Feb 2008 12:37] Shadow Caster
As it turns out, this does not happen in the default configuration. It only occurs when
"useCursorFetch" is enabled.
[13 Feb 2008 14:43] Shadow Caster
It also requires setFetchSize. To prevent that any ohter condition is missing, I have
created a complete test case.

You should see OutOfMemoryError within a few seconds. Additionally, increasing the size of
the blob to like 100k causes the mysql server itself to start using insane amounts of
memory.

public static void main(String[] args) {
  try {
    Class.forName("com.mysql.jdbc.Driver");
    Properties p = new Properties();
    p.put("user", "root");
    p.put("password", "***");
    p.put("useCursorFetch", "true");

    Connection c = DriverManager.getConnection(
        "jdbc:mysql://localhost:3306/", p);

    Statement s = c.createStatement();
    s.execute("CREATE DATABASE memtest");
    s.execute("USE memtest");
    s.execute("CREATE TABLE foo (bar LONGBLOB) Engine=MyISAM");
    s.close();

    char[] zero = new char[1000];
    Arrays.fill(zero, '0');
    String sql = "INSERT INTO foo VALUES (x'" + new String(zero) + "')";

    for (;;) {
      Statement t = c.createStatement();
      t.setFetchSize(10);
      t.execute(sql);
      t.close();
     }
   } catch (Exception exc) {
     exc.printStackTrace();
   }
}
[13 Feb 2008 14:58] Tonci Grgin
Verified as described.
[13 Feb 2008 15:06] Bugs System
A patch for this bug has been committed. After review, it may
be pushed to the relevant source trees for release in the next
version. You can access the patch from:

  http://lists.mysql.com/commits/42217
[13 Feb 2008 15:14] Mark Matthews
Because of the way cursors work in MySQL, we do have to hold onto the "internal" prepared
statement until the result set is closed, so that's what this fix does.

The string used for the statement will be held as well, because we have to have the
ability to "re-prepare" the statement should we lose the connection. Without the original
SQL, it can not be done.
[25 Feb 2008 13:12] MC Brown
A note has been added to the 5.1.6 changelog: 

When using a cursor fetch for a statement, the internal prepared statement could cause a
memory leak until the connection was closed. The internal prepared statement is now
deleted when the corresponding result set is closed.
[27 Feb 2008 18:12] Bugs System
A patch for this bug has been committed. After review, it may
be pushed to the relevant source trees for release in the next
version. You can access the patch from:

  http://lists.mysql.com/commits/43086