=== modified file 'src/com/mysql/jdbc/CompressedInputStream.java' --- src/com/mysql/jdbc/CompressedInputStream.java revid:alexander.soklakov@oracle.com-20130419054601-lmk8m848941a5u35 +++ src/com/mysql/jdbc/CompressedInputStream.java 2013-05-14 12:55:49 +0000 @@ -1,5 +1,5 @@ /* - Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved. + Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved. The MySQL Connector/J is licensed under the terms of the GPLv2 @@ -28,9 +28,11 @@ import java.io.EOFException; import java.io.IOException; import java.io.InputStream; +import java.lang.ref.WeakReference; import java.sql.SQLException; import java.util.zip.DataFormatException; import java.util.zip.Inflater; +import com.mysql.jdbc.log.Log; /** * Used to de-compress packets from the MySQL server when protocol-level @@ -46,7 +48,7 @@ private byte[] buffer; /** The connection that is using us (used to read config values) */ - private Connection connection; + private WeakReference connection; /** The stream we are reading from the server */ private InputStream in; @@ -71,7 +73,7 @@ * @param streamFromServer */ public CompressedInputStream(Connection conn, InputStream streamFromServer) { - this.connection = conn; + this.connection = new WeakReference(conn); this.in = streamFromServer; this.inflater = new Inflater(); } @@ -91,8 +93,11 @@ * @see java.io.InputStream#close() */ public void close() throws IOException { + this.connection.clear(); + this.connection = null; this.in.close(); this.buffer = null; + this.inflater.end(); this.inflater = null; } @@ -120,9 +125,13 @@ + (((this.packetHeaderBuffer[5] & 0xff)) << 8) + (((this.packetHeaderBuffer[6] & 0xff)) << 16); - if (this.connection.getTraceProtocol()) { + boolean doTrace = this.connection != null && this.connection.get() != null && this.connection.get().getTraceProtocol(); + Log log = null; + + if (doTrace) { try { - this.connection.getLog().logTrace( + log = this.connection.get().getLog(); + log.logTrace( "Reading compressed packet of length " + compressedPacketLength + " uncompressed to " + uncompressedLength); @@ -156,16 +165,9 @@ this.inflater.end(); } else { - if (this.connection.getTraceProtocol()) { - try { - this.connection - .getLog() - .logTrace( - "Packet didn't meet compression threshold, not uncompressing..."); - } catch (SQLException sqlEx) { - throw new IOException(sqlEx.toString()); // should never - // happen - } + if (doTrace && log != null) { + log.logTrace( + "Packet didn't meet compression threshold, not uncompressing..."); } // @@ -176,27 +178,17 @@ readFully(uncompressedData, 0, compressedPacketLength); } - if (this.connection.getTraceProtocol()) { - try { - this.connection.getLog().logTrace( + if (doTrace && log != null) { + log.logTrace( "Uncompressed packet: \n" + StringUtils.dumpAsHex(uncompressedData, compressedPacketLength)); - } catch (SQLException sqlEx) { - throw new IOException(sqlEx.toString()); // should never - // happen - } } if ((this.buffer != null) && (this.pos < this.buffer.length)) { - if (this.connection.getTraceProtocol()) { - try { - this.connection.getLog().logTrace( - "Combining remaining packet with new: "); - } catch (SQLException sqlEx) { - throw new IOException(sqlEx.toString()); // should never - // happen - } + if (doTrace && log != null) { + log.logTrace( + "Combining remaining packet with new: "); } int remaining = this.buffer.length - this.pos; === modified file 'src/com/mysql/jdbc/ConnectionImpl.java' --- src/com/mysql/jdbc/ConnectionImpl.java revid:alexander.soklakov@oracle.com-20130419054601-lmk8m848941a5u35 +++ src/com/mysql/jdbc/ConnectionImpl.java 2013-04-10 15:02:00 +0000 @@ -1444,6 +1444,7 @@ } catch (Throwable t) { // can't do anything about it, and we're forcibly aborting } + this.io.releaseResources(); this.io = null; } @@ -1460,10 +1461,12 @@ */ private void cleanup(Throwable whyCleanedUp) { try { - if ((this.io != null) && !isClosed()) { - realClose(false, false, false, whyCleanedUp); - } else if (this.io != null) { - this.io.forceClose(); + if (this.io != null) { + if (isClosed()) { + this.io.forceClose(); + } else { + realClose(false, false, false, whyCleanedUp); + } } } catch (SQLException sqlEx) { // ignore, we're going away. @@ -4715,6 +4718,7 @@ } } finally { this.openStatements = null; + this.io.releaseResources(); this.io = null; this.statementInterceptors = null; this.exceptionInterceptor = null; === modified file 'src/com/mysql/jdbc/MysqlIO.java' --- src/com/mysql/jdbc/MysqlIO.java revid:alexander.soklakov@oracle.com-20130419054601-lmk8m848941a5u35 +++ src/com/mysql/jdbc/MysqlIO.java 2013-04-10 12:22:00 +0000 @@ -3349,6 +3349,9 @@ byte[] bytesToCompress = packet.getByteBuffer(); compressedBytes = new byte[bytesToCompress.length * 2]; + if (this.deflater == null) { + this.deflater = new Deflater(); + } this.deflater.reset(); this.deflater.setInput(bytesToCompress, offset, packetLen); this.deflater.finish(); @@ -5322,4 +5325,11 @@ throw sqlEx; } } + + protected void releaseResources() { + if (this.deflater != null) { + this.deflater.end(); + this.deflater = null; + } + } } \ No newline at end of file === modified file 'src/testsuite/regression/ConnectionRegressionTest.java' --- src/testsuite/regression/ConnectionRegressionTest.java revid:alexander.soklakov@oracle.com-20130419054601-lmk8m848941a5u35 +++ src/testsuite/regression/ConnectionRegressionTest.java 2013-05-14 12:46:53 +0000 @@ -28,6 +28,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintStream; +import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; @@ -4873,4 +4874,91 @@ } } + /** + * Tests fix for BUG#68400 useCompression=true and connect to server, zip native method cause out of memory + * + * @throws Exception + * if any errors occur + */ + public void testBug68400() throws Exception { + + Field f = com.mysql.jdbc.NonRegisteringDriver.class.getDeclaredField("connectionPhantomRefs"); + f.setAccessible(true); + Map connectionTrackingMap = (Map) f.get(com.mysql.jdbc.NonRegisteringDriver.class); + + Field referentField = java.lang.ref.Reference.class.getDeclaredField("referent"); + referentField.setAccessible(true); + + createTable("testBug68400", "(x VARCHAR(255) NOT NULL DEFAULT '')"); + String s1 = "a very very very very very very very very very very very very very very very very very very very very very very very very large string to ensure compression enabled"; + this.stmt.executeUpdate("insert into testBug68400 values ('"+s1+"')"); + + Properties props = new Properties(); + props.setProperty("useCompression", "true"); + props.setProperty("connectionAttributes", "testBug68400:true"); + + int testSize = 20; + int connectionNumber = 0; + Connection connection=null; + Statement statement = null; + ResultSet resultSet=null; + + for(int cnt=0; cnt< testSize; cnt++){ + System.out.println("\nCNT: "+cnt); + + // 1. Create 100 connections with "testBug68400:true" attribute + for(int j = 0; j<100;j++) { + connection = getConnectionWithProps(props); + statement = connection.createStatement(); + resultSet = statement.executeQuery("select /* a very very very very very very very very very very very very very very very very very very very very very very very very large string to ensure compression enabled */ x from testBug68400"); + if (resultSet.next()) { + String s2 = resultSet.getString(1); + assertEquals(s1, s2); + } + if (resultSet != null) { + resultSet.close(); + } + if (statement != null) { + statement.close(); + } + if (connection != null) { + // don't close last 1000 connections to check abandoned connections clean up + if (cnt < 10 ) { + connection.close(); + } + connection = null; + } + } + + // 2. Count connections before GC + connectionNumber = countTestConnections(connectionTrackingMap, referentField); + System.out.println("Test related connections in MAP before GC: " + connectionNumber); + + // 3. Run GC + Runtime.getRuntime().gc(); + + // 4. Sleep to ensure abandoned connection clean up occurred + Thread.sleep(2000); + + // 5. Count connections before GC + connectionNumber = countTestConnections(connectionTrackingMap, referentField); + System.out.println("Test related connections in MAP after GC: " + connectionNumber); + + assertEquals("No connection with \"testBug68400:true\" connection attribute should exist in NonRegisteringDriver.connectionPhantomRefs map after GC", 0, connectionNumber); + } + System.out.println("Done."); + + } + + private int countTestConnections(Map connectionTrackingMap, Field referentField) throws Exception { + int connectionNumber = 0; + for (Object o1 : connectionTrackingMap.keySet()) { + ConnectionImpl ctmp = (ConnectionImpl) referentField.get(o1); + if (ctmp != null && ctmp.getConnectionAttributes() != null && ctmp.getConnectionAttributes().equals("testBug68400:true")) { + connectionNumber++; + } + } + return connectionNumber; + } + } \ No newline at end of file