Index: src/interfaces/jdbc/org/postgresql/errors.properties =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/interfaces/jdbc/org/postgresql/errors.properties,v retrieving revision 1.24 diff -u -c -r1.24 errors.properties *** src/interfaces/jdbc/org/postgresql/errors.properties 8 Sep 2003 17:30:22 -0000 1.24 --- src/interfaces/jdbc/org/postgresql/errors.properties 16 Sep 2003 07:45:41 -0000 *************** *** 110,112 **** --- 110,114 ---- postgresql.format.badtime:The time given: {0} does not match the format required: {1}. postgresql.format.badtimestamp:The timestamp given {0} does not match the format required: {1}. postgresql.input.field.gt0:The maximum field size must be a value greater than or equal to 0. + postgresql.interrupted:The calling thread was interrupted while waiting for an operation to complete. + postgresql.cancelled:The query was cancelled while waiting for the connection to become available. Index: src/interfaces/jdbc/org/postgresql/core/BaseConnection.java =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/interfaces/jdbc/org/postgresql/core/BaseConnection.java,v retrieving revision 1.3 diff -u -c -r1.3 BaseConnection.java *** src/interfaces/jdbc/org/postgresql/core/BaseConnection.java 29 May 2003 03:21:32 -0000 1.3 --- src/interfaces/jdbc/org/postgresql/core/BaseConnection.java 16 Sep 2003 07:45:42 -0000 *************** *** 23,29 **** public void addNotification(PGNotification p_notification); public void addWarning(String msg); ! public void cancelQuery() throws SQLException; public Statement createStatement() throws SQLException; public BaseResultSet execSQL(String s) throws SQLException; public boolean getAutoCommit() throws SQLException; --- 23,49 ---- public void addNotification(PGNotification p_notification); public void addWarning(String msg); ! ! /** ! * Cancels a query in progress. The query is identified by a ! * statementKey that corresponds to the key passed ! * to {@link #beforeQuery(java.lang.Object)}. ! *

! * If the query is not currently executing or waiting for execution, ! * this method is a no-op. If the query is waiting for the connection ! * to become free and has not started execution, it is immediately ! * cancelled. If the query is currently executing, the backend is asked ! * to cancel the query; this may or may not cause the query to terminate ! * with an error, depending on exact timing. ! *

! * If the backend is asked to cancel a query, further query execution is ! * blocked until the cancellation completes to avoid a race condition ! * where the wrong query could be cancelled. ! * ! * @throws SQLException if the backend needed to be contacted to do a ! * query cancellation, but something went wrong during that process. ! */ ! public void cancelQuery(Object statementKey) throws SQLException; public Statement createStatement() throws SQLException; public BaseResultSet execSQL(String s) throws SQLException; public boolean getAutoCommit() throws SQLException; *************** *** 43,47 **** public void setAutoCommit(boolean autoCommit) throws SQLException; public void setCursorName(String cursor) throws SQLException; ! } --- 63,86 ---- public void setAutoCommit(boolean autoCommit) throws SQLException; public void setCursorName(String cursor) throws SQLException; ! /** ! * Called by QueryExecutor before a query is sent to the backend. ! * Performs query ordering, cancelling, & mutual exclusion; only one ! * thread is allowed within a beforeQuery .. afterQuery block at once. ! * ! * @param statementKey the statement key to pass to cancelQuery() to cancel this query. ! * @throws SQLException if the query is cancelled or the calling thread is interrupted ! * while waiting for other queries on this conneciton to complete. ! */ ! public void beforeQuery(Object statementKey) throws SQLException; + /** + * Called by QueryExecutor after a query has completed. Every successful execution + * of beforeQuery() should be followed by an execution of afterQuery(). + * + * @param statementKey the statement key passed to the corresponding beforeQuery() call. + * @throws IllegalStateException if statementKey does not match the currently + * executing statement key. + */ + public void afterQuery(Object statementKey) throws IllegalStateException; + } Index: src/interfaces/jdbc/org/postgresql/core/PGStream.java =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/interfaces/jdbc/org/postgresql/core/PGStream.java,v retrieving revision 1.3 diff -u -c -r1.3 PGStream.java *** src/interfaces/jdbc/org/postgresql/core/PGStream.java 8 Sep 2003 17:30:22 -0000 1.3 --- src/interfaces/jdbc/org/postgresql/core/PGStream.java 16 Sep 2003 07:45:42 -0000 *************** *** 152,157 **** --- 152,173 ---- } /* + * Consume an expected EOF from the backend + * @exception SQLException if we get something other than an EOF + */ + public void ReceiveEOF() throws SQLException + { + try { + int c = pg_input.read(); + if (c < 0) + return; + throw new PSQLException("postgresql.stream.toomuch", PSQLState.COMMUNICATION_ERROR); + } catch (IOException e) { + throw new PSQLException("postgresql.stream.ioerror", PSQLState.COMMUNICATION_ERROR); + } + } + + /* * Receives a single character from the backend * * @return the character received Index: src/interfaces/jdbc/org/postgresql/core/QueryExecutor.java =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/interfaces/jdbc/org/postgresql/core/QueryExecutor.java,v retrieving revision 1.26 diff -u -c -r1.26 QueryExecutor.java *** src/interfaces/jdbc/org/postgresql/core/QueryExecutor.java 13 Sep 2003 04:02:13 -0000 1.26 --- src/interfaces/jdbc/org/postgresql/core/QueryExecutor.java 16 Sep 2003 07:45:42 -0000 *************** *** 95,108 **** */ private BaseResultSet execute() throws SQLException { ! if (connection.getPGProtocolVersionMajor() == 3) { ! if (Driver.logDebug) ! Driver.debug("Using Protocol Version3 to send query"); ! return executeV3(); ! } else { ! if (Driver.logDebug) ! Driver.debug("Using Protocol Version2 to send query"); ! return executeV2(); } } --- 95,119 ---- */ private BaseResultSet execute() throws SQLException { ! if (pgStream == null) ! { ! throw new PSQLException("postgresql.con.closed", PSQLState.CONNECTION_DOES_NOT_EXIST); ! } ! ! connection.beforeQuery(statement); // Might throw. ! ! try { ! if (connection.getPGProtocolVersionMajor() == 3) { ! if (Driver.logDebug) ! Driver.debug("Using Protocol Version3 to send query"); ! return executeV3(); ! } else { ! if (Driver.logDebug) ! Driver.debug("Using Protocol Version2 to send query"); ! return executeV2(); ! } ! } finally { ! connection.afterQuery(statement); } } *************** *** 111,216 **** PSQLException error = null; ! if (pgStream == null) ! { ! throw new PSQLException("postgresql.con.closed", PSQLState.CONNECTION_DOES_NOT_EXIST); ! } ! synchronized (pgStream) { ! ! sendQueryV3(); ! ! int c; ! boolean l_endQuery = false; ! while (!l_endQuery) { ! c = pgStream.ReceiveChar(); ! switch (c) ! { ! case 'A': // Asynchronous Notify ! int pid = pgStream.ReceiveInteger(4); ! String msg = pgStream.ReceiveString(connection.getEncoding()); ! connection.addNotification(new org.postgresql.core.Notification(msg, pid)); ! break; ! case 'B': // Binary Data Transfer ! receiveTupleV3(true); ! break; ! case 'C': // Command Status ! receiveCommandStatusV3(); ! break; ! case 'D': // Text Data Transfer ! receiveTupleV3(false); ! break; ! case 'E': // Error Message ! ! // it's possible to get more than one error message for a query ! // see libpq comments wrt backend closing a connection ! // so, append messages to a string buffer and keep processing ! // check at the bottom to see if we need to throw an exception ! ! int l_elen = pgStream.ReceiveIntegerR(4); ! String totalMessage = connection.getEncoding().decode(pgStream.Receive(l_elen-4)); ! PSQLException l_error = PSQLException.parseServerError(totalMessage); ! ! if (error != null) { ! error.setNextException(l_error); ! } else { ! error = l_error; ! } ! ! // keep processing ! break; ! case 'I': // Empty Query ! int t = pgStream.ReceiveChar(); ! break; ! case 'N': // Error Notification ! int l_nlen = pgStream.ReceiveIntegerR(4); ! statement.addWarning(connection.getEncoding().decode(pgStream.Receive(l_nlen-4))); ! break; ! case 'P': // Portal Name ! String pname = pgStream.ReceiveString(connection.getEncoding()); ! break; ! case 'S': ! //TODO: handle parameter status messages ! int l_len = pgStream.ReceiveIntegerR(4); ! String l_pStatus = connection.getEncoding().decode(pgStream.Receive(l_len-4)); ! if (Driver.logDebug) ! Driver.debug("ParameterStatus="+ l_pStatus); ! break; ! case 'T': // MetaData Field Description ! receiveFieldsV3(); ! break; ! case 'Z': ! // read ReadyForQuery ! //TODO: use size better ! if (pgStream.ReceiveIntegerR(4) != 5) throw new PSQLException("postgresql.con.setup", PSQLState.CONNECTION_UNABLE_TO_CONNECT); ! //TODO: handle transaction status ! char l_tStatus = (char)pgStream.ReceiveChar(); ! l_endQuery = true; ! break; ! default: ! throw new PSQLException("postgresql.con.type", PSQLState.CONNECTION_FAILURE, new Character((char) c)); } ! ! } ! ! // did we get an error during this query? ! if ( error != null ) ! throw error; ! ! //if an existing result set was passed in reuse it, else ! //create a new one ! if (rs != null) ! { ! rs.reInit(fields, tuples, status, update_count, insert_oid, binaryCursor); } ! else { rs = statement.createResultSet(fields, tuples, status, update_count, insert_oid, binaryCursor); } ! return rs; ! } } private BaseResultSet executeV2() throws SQLException --- 122,218 ---- PSQLException error = null; ! sendQueryV3(); ! int c; ! boolean l_endQuery = false; ! while (!l_endQuery) { ! c = pgStream.ReceiveChar(); ! switch (c) { ! case 'A': // Asynchronous Notify ! int pid = pgStream.ReceiveInteger(4); ! String msg = pgStream.ReceiveString(connection.getEncoding()); ! connection.addNotification(new org.postgresql.core.Notification(msg, pid)); ! break; ! case 'B': // Binary Data Transfer ! receiveTupleV3(true); ! break; ! case 'C': // Command Status ! receiveCommandStatusV3(); ! break; ! case 'D': // Text Data Transfer ! receiveTupleV3(false); ! break; ! case 'E': // Error Message ! ! // it's possible to get more than one error message for a query ! // see libpq comments wrt backend closing a connection ! // so, append messages to a string buffer and keep processing ! // check at the bottom to see if we need to throw an exception ! ! int l_elen = pgStream.ReceiveIntegerR(4); ! String totalMessage = connection.getEncoding().decode(pgStream.Receive(l_elen-4)); ! PSQLException l_error = PSQLException.parseServerError(totalMessage); ! ! if (error != null) { ! error.setNextException(l_error); ! } else { ! error = l_error; } ! ! // keep processing ! break; ! case 'I': // Empty Query ! int t = pgStream.ReceiveChar(); ! break; ! case 'N': // Error Notification ! int l_nlen = pgStream.ReceiveIntegerR(4); ! statement.addWarning(connection.getEncoding().decode(pgStream.Receive(l_nlen-4))); ! break; ! case 'P': // Portal Name ! String pname = pgStream.ReceiveString(connection.getEncoding()); ! break; ! case 'S': ! //TODO: handle parameter status messages ! int l_len = pgStream.ReceiveIntegerR(4); ! String l_pStatus = connection.getEncoding().decode(pgStream.Receive(l_len-4)); ! if (Driver.logDebug) ! Driver.debug("ParameterStatus="+ l_pStatus); ! break; ! case 'T': // MetaData Field Description ! receiveFieldsV3(); ! break; ! case 'Z': ! // read ReadyForQuery ! //TODO: use size better ! if (pgStream.ReceiveIntegerR(4) != 5) throw new PSQLException("postgresql.con.setup", PSQLState.CONNECTION_UNABLE_TO_CONNECT); ! //TODO: handle transaction status ! char l_tStatus = (char)pgStream.ReceiveChar(); ! l_endQuery = true; ! break; ! default: ! throw new PSQLException("postgresql.con.type", PSQLState.CONNECTION_FAILURE, new Character((char) c)); } ! ! } ! ! // did we get an error during this query? ! if ( error != null ) ! throw error; ! ! //if an existing result set was passed in reuse it, else ! //create a new one ! if (rs != null) ! { ! rs.reInit(fields, tuples, status, update_count, insert_oid, binaryCursor); ! } ! else { rs = statement.createResultSet(fields, tuples, status, update_count, insert_oid, binaryCursor); } ! return rs; } private BaseResultSet executeV2() throws SQLException *************** *** 218,306 **** StringBuffer errorMessage = null; ! if (pgStream == null) ! { ! throw new PSQLException("postgresql.con.closed", PSQLState.CONNECTION_DOES_NOT_EXIST); ! } ! synchronized (pgStream) { ! ! sendQueryV2(); ! ! int c; ! boolean l_endQuery = false; ! while (!l_endQuery) ! { ! c = pgStream.ReceiveChar(); ! ! switch (c) ! { ! case 'A': // Asynchronous Notify ! int pid = pgStream.ReceiveInteger(4); ! String msg = pgStream.ReceiveString(connection.getEncoding()); ! connection.addNotification(new org.postgresql.core.Notification(msg, pid)); ! break; ! case 'B': // Binary Data Transfer ! receiveTupleV2(true); ! break; ! case 'C': // Command Status ! receiveCommandStatusV2(); ! break; ! case 'D': // Text Data Transfer ! receiveTupleV2(false); ! break; ! case 'E': // Error Message ! ! // it's possible to get more than one error message for a query ! // see libpq comments wrt backend closing a connection ! // so, append messages to a string buffer and keep processing ! // check at the bottom to see if we need to throw an exception ! ! if ( errorMessage == null ) ! errorMessage = new StringBuffer(); ! ! errorMessage.append(pgStream.ReceiveString(connection.getEncoding())); ! // keep processing ! break; ! case 'I': // Empty Query ! int t = pgStream.ReceiveChar(); ! break; ! case 'N': // Error Notification ! statement.addWarning(pgStream.ReceiveString(connection.getEncoding())); ! break; ! case 'P': // Portal Name ! String pname = pgStream.ReceiveString(connection.getEncoding()); ! break; ! case 'T': // MetaData Field Description ! receiveFieldsV2(); ! break; ! case 'Z': ! l_endQuery = true; ! break; ! default: ! throw new PSQLException("postgresql.con.type", PSQLState.CONNECTION_FAILURE, new Character((char) c)); ! } ! } ! ! // did we get an error during this query? ! if ( errorMessage != null ) ! throw new SQLException( errorMessage.toString().trim() ); ! ! ! //if an existing result set was passed in reuse it, else ! //create a new one ! if (rs != null) ! { ! rs.reInit(fields, tuples, status, update_count, insert_oid, binaryCursor); ! } ! else ! { ! rs = statement.createResultSet(fields, tuples, status, update_count, insert_oid, binaryCursor); ! } ! return rs; } } /* --- 220,299 ---- StringBuffer errorMessage = null; ! sendQueryV2(); ! int c; ! boolean l_endQuery = false; ! while (!l_endQuery) { ! c = pgStream.ReceiveChar(); ! ! switch (c) ! { ! case 'A': // Asynchronous Notify ! int pid = pgStream.ReceiveInteger(4); ! String msg = pgStream.ReceiveString(connection.getEncoding()); ! connection.addNotification(new org.postgresql.core.Notification(msg, pid)); ! break; ! case 'B': // Binary Data Transfer ! receiveTupleV2(true); ! break; ! case 'C': // Command Status ! receiveCommandStatusV2(); ! break; ! case 'D': // Text Data Transfer ! receiveTupleV2(false); ! break; ! case 'E': // Error Message ! ! // it's possible to get more than one error message for a query ! // see libpq comments wrt backend closing a connection ! // so, append messages to a string buffer and keep processing ! // check at the bottom to see if we need to throw an exception ! ! if ( errorMessage == null ) ! errorMessage = new StringBuffer(); ! ! errorMessage.append(pgStream.ReceiveString(connection.getEncoding())); ! // keep processing ! break; ! case 'I': // Empty Query ! int t = pgStream.ReceiveChar(); ! break; ! case 'N': // Error Notification ! statement.addWarning(pgStream.ReceiveString(connection.getEncoding())); ! break; ! case 'P': // Portal Name ! String pname = pgStream.ReceiveString(connection.getEncoding()); ! break; ! case 'T': // MetaData Field Description ! receiveFieldsV2(); ! break; ! case 'Z': ! l_endQuery = true; ! break; ! default: ! throw new PSQLException("postgresql.con.type", PSQLState.CONNECTION_FAILURE, new Character((char) c)); } ! ! } ! ! // did we get an error during this query? ! if ( errorMessage != null ) ! throw new SQLException( errorMessage.toString().trim() ); ! ! ! //if an existing result set was passed in reuse it, else ! //create a new one ! if (rs != null) ! { ! rs.reInit(fields, tuples, status, update_count, insert_oid, binaryCursor); ! } ! else ! { ! rs = statement.createResultSet(fields, tuples, status, update_count, insert_oid, binaryCursor); } + return rs; } /* Index: src/interfaces/jdbc/org/postgresql/jdbc1/AbstractJdbc1Connection.java =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/interfaces/jdbc/org/postgresql/jdbc1/AbstractJdbc1Connection.java,v retrieving revision 1.26 diff -u -c -r1.26 AbstractJdbc1Connection.java *** src/interfaces/jdbc/org/postgresql/jdbc1/AbstractJdbc1Connection.java 13 Sep 2003 04:02:15 -0000 1.26 --- src/interfaces/jdbc/org/postgresql/jdbc1/AbstractJdbc1Connection.java 16 Sep 2003 07:45:43 -0000 *************** *** 104,109 **** --- 104,113 ---- */ private int isolationLevel = Connection.TRANSACTION_READ_COMMITTED; + // Query queue and cancellation infrastructure. + private Object currentQuery; + private final Vector queryQueue = new Vector(); // Better as a LinkedList, but that's not in 1.1. + private boolean cancelPending; public abstract Statement createStatement() throws SQLException; public abstract DatabaseMetaData getMetaData() throws SQLException; *************** *** 1775,1786 **** Types.TIMESTAMP, Types.TIMESTAMP, Types.TIMESTAMP }; ! public void cancelQuery() throws SQLException { org.postgresql.core.PGStream cancelStream = null; try { cancelStream = new org.postgresql.core.PGStream(PG_HOST, PG_PORT); } catch (ConnectException cex) { --- 1779,1824 ---- Types.TIMESTAMP, Types.TIMESTAMP, Types.TIMESTAMP }; ! public void cancelQuery(Object statement) throws SQLException { + synchronized (queryQueue) { + if (queryQueue.removeElement(statement)) { + // Query hadn't started executing yet. Remove it and wake up + // the thread that's waiting to start so it notices the + // cancellation. + queryQueue.notifyAll(); + return; + } + + if (currentQuery != statement) { + // Not executing this statement at all. Don't do anything. + return; + } + + if (cancelPending) { + // We are already doing a cancel for the currently running statement. + // Don't do it twice. + return; + } + + // Need to do a real cancel. Make sure new queries don't start while we do this. + cancelPending = true; + } + org.postgresql.core.PGStream cancelStream = null; try { cancelStream = new org.postgresql.core.PGStream(PG_HOST, PG_PORT); + + // Now we need to construct and send a cancel packet + cancelStream.SendInteger(16, 4); + cancelStream.SendInteger(80877102, 4); + cancelStream.SendInteger(pid, 4); + cancelStream.SendInteger(ckey, 4); + cancelStream.flush(); + + // Wait for the backend to close the connection to avoid races. + cancelStream.ReceiveEOF(); } catch (ConnectException cex) { *************** *** 1793,1814 **** { throw new PSQLException ("postgresql.con.failed", PSQLState.CONNECTION_UNABLE_TO_CONNECT, e); } - - // Now we need to construct and send a cancel packet - try - { - cancelStream.SendInteger(16, 4); - cancelStream.SendInteger(80877102, 4); - cancelStream.SendInteger(pid, 4); - cancelStream.SendInteger(ckey, 4); - cancelStream.flush(); - } - catch (IOException e) - { - throw new PSQLException("postgresql.con.failed", PSQLState.CONNECTION_UNABLE_TO_CONNECT, e); - } finally { try { if (cancelStream != null) --- 1831,1844 ---- { throw new PSQLException ("postgresql.con.failed", PSQLState.CONNECTION_UNABLE_TO_CONNECT, e); } finally { + synchronized (queryQueue) { + // Cancellation is done, it's safe to start new queries again. + cancelPending = false; + queryQueue.notifyAll(); + } + try { if (cancelStream != null) *************** *** 1819,1824 **** --- 1849,1913 ---- } } + public void beforeQuery(Object statement) throws SQLException { + synchronized (queryQueue) { + // Fastpath for the common case where we have no contention. + if (!cancelPending && currentQuery == null) { + currentQuery = statement; + return; + } + + // Ok, we have multiple queries and/or cancels involved, do the full logic. + queryQueue.addElement(statement); + + // Wait for no currently running query and no currently running cancellation. + // Once both of these are true, we can start executing. + // If this statement is cancelled while still on the query queue, + // this is signalled by removing the statement from the queue. + while (true) { + if (!queryQueue.contains(statement)) { + // We were cancelled. + throw new PSQLException("postgresql.cancelled", PSQLState.SYSTEM_ERROR); + } + + if (!cancelPending && currentQuery == null) { + // We can start. + queryQueue.removeElement(statement); + currentQuery = statement; + break; + } + + // invariants here: + // queryQueue.contains(statement) + // currentQuery != statement + // currentQuery != null || cancelPending + + try { + queryQueue.wait(); + } catch (InterruptedException ie) { + // As it's hard for a caller to detect this case via + // the thrown exception, reset interruption state too. + Thread.currentThread().interrupt(); + throw new PSQLException("postgresql.interrupted", PSQLState.SYSTEM_ERROR); + } + } + + // invariants here: + // currentQuery == statement + // !cancelPending + // !queryQueue.contains(statement) + } + } + + public void afterQuery(Object statement) { + synchronized (queryQueue) { + if (statement != currentQuery) + throw new IllegalStateException("bad beforeQuery()/afterQuery() ordering"); + + currentQuery = null; + queryQueue.notifyAll(); // Someone can wake up now. + } + } //Methods to support postgres notifications public void addNotification(org.postgresql.PGNotification p_notification) Index: src/interfaces/jdbc/org/postgresql/jdbc1/AbstractJdbc1Statement.java =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/interfaces/jdbc/org/postgresql/jdbc1/AbstractJdbc1Statement.java,v retrieving revision 1.36 diff -u -c -r1.36 AbstractJdbc1Statement.java *** src/interfaces/jdbc/org/postgresql/jdbc1/AbstractJdbc1Statement.java 13 Sep 2003 04:02:15 -0000 1.36 --- src/interfaces/jdbc/org/postgresql/jdbc1/AbstractJdbc1Statement.java 16 Sep 2003 07:45:43 -0000 *************** *** 670,683 **** /* * Cancel can be used by one thread to cancel a statement that * is being executed by another thread. - *

- * Not implemented, this method is a no-op. * * @exception SQLException only because thats the spec. */ public void cancel() throws SQLException { ! throw new PSQLException("postgresql.unimplemented", PSQLState.NOT_IMPLEMENTED); } /* --- 670,681 ---- /* * Cancel can be used by one thread to cancel a statement that * is being executed by another thread. * * @exception SQLException only because thats the spec. */ public void cancel() throws SQLException { ! connection.cancelQuery(this); } /* Index: src/interfaces/jdbc/org/postgresql/jdbc2/AbstractJdbc2Statement.java =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/interfaces/jdbc/org/postgresql/jdbc2/AbstractJdbc2Statement.java,v retrieving revision 1.17 diff -u -c -r1.17 AbstractJdbc2Statement.java *** src/interfaces/jdbc/org/postgresql/jdbc2/AbstractJdbc2Statement.java 9 Sep 2003 10:49:16 -0000 1.17 --- src/interfaces/jdbc/org/postgresql/jdbc2/AbstractJdbc2Statement.java 16 Sep 2003 07:45:43 -0000 *************** *** 118,128 **** return result; } - public void cancel() throws SQLException - { - connection.cancelQuery(); - } - public Connection getConnection() throws SQLException { return (Connection) connection; --- 118,123 ---- Index: src/interfaces/jdbc/org/postgresql/test/jdbc2/MiscTest.java =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/interfaces/jdbc/org/postgresql/test/jdbc2/MiscTest.java,v retrieving revision 1.10 diff -u -c -r1.10 MiscTest.java *** src/interfaces/jdbc/org/postgresql/test/jdbc2/MiscTest.java 29 May 2003 04:39:48 -0000 1.10 --- src/interfaces/jdbc/org/postgresql/test/jdbc2/MiscTest.java 16 Sep 2003 07:45:43 -0000 *************** *** 99,102 **** --- 99,200 ---- fail( ex.getMessage() ); } } + + public void testCancel() throws Exception { + // + // Try to make sure there's no cancel() race where we'd cancel a not-yet-executed query. + // + + Connection con = TestUtil.openDB(); + Statement stmt = con.createStatement(); + for (int i = 0; i < 10000; ++i) { + stmt.executeQuery("select version()").close(); + stmt.cancel(); + } + } + + private class QueryRunnable implements Runnable { + QueryRunnable(Statement stmt, boolean ignoreErrors, int testValue) { + this.stmt = stmt; + this.ignoreErrors = ignoreErrors; + this.testValue = testValue; + } + + public void run() { + while (!kill) { + try { + // Generate a reasonably sized resultset to try to slow the backend + // down a bit, so we have a chance of the cancel actually hitting us. + ResultSet rs = stmt.executeQuery("select " + testValue + " from pg_type t1, pg_type t2"); + int count = 0; + while (rs.next()) { + assertEquals(testValue, rs.getInt(1)); + ++count; + } + assertTrue(count > 0); + rs.close(); + } catch (SQLException e) { + //e.printStackTrace(); + if (!ignoreErrors) { + this.error = e; + break; + } + } catch (Throwable t) { + this.error = t; + break; + } + } + + synchronized (this) { + stopped = true; + notifyAll(); + } + } + + public void stop() throws Exception { + kill = true; + synchronized (this) { + while (!stopped) { + try { this.wait(); } + catch (InterruptedException e) {} + } + } + + if (error != null) { + if (error instanceof Exception) + throw (Exception)error; + else + throw (Error)error; + } + } + + private final Statement stmt; + private final boolean ignoreErrors; + private final int testValue; + + private Throwable error; + private volatile boolean kill = false; + private boolean stopped = false; + } + + public void testThreadedCancel() throws Exception { + Connection con = TestUtil.openDB(); + Statement stmt1 = con.createStatement(); + QueryRunnable qr1 = new QueryRunnable(stmt1, true, 1); // ignore errors; we will repeatedly cancel this one + Statement stmt2 = con.createStatement(); + QueryRunnable qr2 = new QueryRunnable(stmt2, false, 2); // report errors; it should never be cancelled. + + new Thread(qr1).start(); + new Thread(qr2).start(); + + Thread.sleep(1000); // Give them time to start. + + for (int i = 0; i < 500; ++i) { + stmt1.cancel(); + Thread.sleep(10); + } + + qr1.stop(); + qr2.stop(); + } }