package com.glide.db; import java.net.URL; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; /** * Demonstrates MySQL bug * * Quick Description: If in streaming mode and something issues a KILL QUERY on your statement, invoking ResultSet.close will hang on socketRead * * - Parent: Start a query that takes a while * - Parent: Make sure the query is in streaming mode * - Parent: Iterate the result set * - Parent: Spawn a child thread * - Child: Sleep a little then kill the query the parent is running * - Parent: rs.next() throws Exception "Query execution was interrupted" * - Parent: Issues rs.close() * - Parent: Now hung on socketRead from rs.close() * */ public class MySQLBugTest { private static final String DEFAULT_MYSQL_HOST = "127.0.0.1"; private static final String DEFAULT_MYSQL_USER = "root"; private static final String DEFAULT_MYSQL_PASS = ""; private static final String DEFAULT_MYSQL_DRIVER = "com.mysql.jdbc.Driver"; private String fURL; private String fTableName; private String fUserName; private String fPassword; /** * @param args */ public static void main(String[] args) { new MySQLBugTest("jdbc:mysql://" + DEFAULT_MYSQL_HOST + "/information_schema", "character_sets", DEFAULT_MYSQL_USER, DEFAULT_MYSQL_PASS).read(10); } public MySQLBugTest(String url, String tableName, String userName, String password) { fURL = url; fTableName = tableName; fUserName = userName; fPassword = password; } public void read() { read(0); } public void read(long limit) { try { Thread.currentThread().setName("bug.test.main"); // Register the JDBC driver for MySQL. Class.forName(DEFAULT_MYSQL_DRIVER); // show where the driver is loaded from URL myClassURL = com.mysql.jdbc.Driver.class.getProtectionDomain().getCodeSource().getLocation(); log("Driver loaded from: " + myClassURL); Connection con = newConnection(); Statement stmt = newStatement(con); int myPid = getConnectionPid(stmt); StringBuffer sb = new StringBuffer(); sb.append("SELECT sleep(5) FROM " + fTableName); if (limit > 0) sb.append(" LIMIT " + limit); log("My pid is: " + myPid); log("Select statement: " + sb.toString()); QueryKilla killa = new QueryKilla(myPid); killa.start(); try { ResultSet rs = stmt.executeQuery(sb.toString()); dump(rs); rs.close(); } catch(SQLException se) { log("Got SQLException: state: " + se.getSQLState() + " msg:" + se.getMessage()); } log("Closing statement"); stmt.close(); log("Closing connection"); con.close(); } catch (Exception e) { log("Unable to read test: " + e.getMessage()); } } public Connection newConnection() { Connection con = null; try { con = DriverManager.getConnection(fURL, fUserName, fPassword); // Display URL and connection information log("URL: " + fURL); log("Connection: " + con); con.setAutoCommit(true); } catch (Exception e) { log("Unable to get new connection: " + e.getMessage()); } return con; } public Statement newStatement(Connection con) { // Get a Statement object try { Statement stmt = con.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); stmt.setFetchSize(Integer.MIN_VALUE); // turn on streaming mode return stmt; } catch(Exception e) { log("Unable to get statement: " + e.getMessage()); } return null; } public int getConnectionPid(Statement stmt) { int pid = -1; try { ResultSet rs = stmt.executeQuery("SELECT CONNECTION_ID()"); if (rs.next()) { // "Table" column is 1, "Create Table" column is 2 pid = Integer.parseInt(rs.getString(1), 10); } rs.close(); } catch (SQLException e) { // nothing } return pid; } private void log(String msg) { System.out.println(Thread.currentThread().getName() + ": " + msg); } private void dump(ResultSet rs) throws SQLException { int rows = 0; int columnCount = rs.getMetaData().getColumnCount(); log("fetched result set, " + columnCount + " columns"); long totalDataCount = 0; long netReadTime = 0; boolean shouldLoop = true; while(shouldLoop) { shouldLoop = rs.next(); if (!shouldLoop) break; rows++; long rowSize = getRowSize(rs, columnCount); totalDataCount += rowSize; } log(fTableName + " total rows " + rows + ", data " + totalDataCount); } private long getRowSize(ResultSet rs, int columnCount) { long size = 0; for(int i = 0; i < columnCount; i++) { try { String s = rs.getString(i + 1); if (s != null) size += s.length(); } catch (SQLException sqle) { sqle.printStackTrace(); } } return size; } class QueryKilla extends Thread { int fThePid; QueryKilla(int thePid) { fThePid = thePid; setName("thread.killa"); } public void run() { log("Running thread..."); try { log("Sleeping..."); Thread.sleep(10000); } catch (Exception e) { } Connection con = newConnection(); Statement stmt = newStatement(con); try { log("Running: KILL QUERY " + fThePid); stmt.execute("KILL QUERY " + fThePid + ";"); stmt.close(); con.close(); Thread.sleep(5000); log("parent thread should be hung now!!!"); } catch(Exception e) { log("Unable to kill query: " + e.getMessage()); } log("Bye bye"); } } }