Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix | Fix possible Statement Leak in SQLServerConnection.isValid() API #955

Merged
merged 15 commits into from
Mar 11, 2019
73 changes: 41 additions & 32 deletions src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java
Original file line number Diff line number Diff line change
Expand Up @@ -2911,31 +2911,41 @@ final void terminate(int driverErrorCode, String message, Throwable throwable) t
*/
boolean executeCommand(TDSCommand newCommand) throws SQLServerException {
synchronized (schedulerLock) {
// Detach (buffer) the response from any previously executing
// command so that we can execute the new command.
//
// Note that detaching the response does not process it. Detaching just
// buffers the response off of the wire to clear the TDS channel.
/*
* Detach (buffer) the response from any previously executing command so that we can execute the new
* command. Note that detaching the response does not process it. Detaching just buffers the response off of
* the wire to clear the TDS channel.
*/
if (null != currentCommand) {
currentCommand.detach();
currentCommand = null;
try {
currentCommand.detach();
} catch (SQLServerException e) {
/*
* If any exception occurs during detach, need not do anything, simply log it. Our purpose to detach
* the response and empty buffer is done here. If there is anything wrong with the connection
* itself, let the exception pass below to be thrown during 'execute()'.
*/
if (connectionlogger.isLoggable(Level.FINE)) {
connectionlogger.fine("Failed to detach current command : " + e.getMessage());
}
} finally {
currentCommand = null;
}
}

// The implementation of this scheduler is pretty simple...
// Since only one command at a time may use a connection
// (to avoid TDS protocol errors), just synchronize to
// serialize command execution.
/*
* The implementation of this scheduler is pretty simple... Since only one command at a time may use a
* connection (to avoid TDS protocol errors), just synchronize to serialize command execution.
*/
boolean commandComplete = false;
try {
commandComplete = newCommand.execute(tdsChannel.getWriter(), tdsChannel.getReader(newCommand));
} finally {
// We should never displace an existing currentCommand
// assert null == currentCommand;

// If execution of the new command left response bytes on the wire
// (e.g. a large ResultSet or complex response with multiple results)
// then remember it as the current command so that any subsequent call
// to executeCommand will detach it before executing another new command.
/*
* If execution of the new command left response bytes on the wire (e.g. a large ResultSet or complex
* response with multiple results) then remember it as the current command so that any subsequent call
* to executeCommand will detach it before executing another new command.
*/
if (!commandComplete && !isSessionUnAvailable())
currentCommand = newCommand;
}
Expand Down Expand Up @@ -5553,8 +5563,6 @@ public void setClientInfo(String name, String value) throws SQLClientInfoExcepti
*/
@Override
public boolean isValid(int timeout) throws SQLException {
boolean isValid = false;

loggerExternal.entering(getClassNameLogging(), "isValid", timeout);

// Throw an exception if the timeout is invalid
Expand All @@ -5568,25 +5576,26 @@ public boolean isValid(int timeout) throws SQLException {
if (isSessionUnAvailable())
return false;

try {
SQLServerStatement stmt = new SQLServerStatement(this, ResultSet.TYPE_FORWARD_ONLY,
ResultSet.CONCUR_READ_ONLY, SQLServerStatementColumnEncryptionSetting.UseConnectionSetting);
boolean isValid = true;
try (SQLServerStatement stmt = new SQLServerStatement(this, ResultSet.TYPE_FORWARD_ONLY,
ResultSet.CONCUR_READ_ONLY, SQLServerStatementColumnEncryptionSetting.UseConnectionSetting)) {

// If asked, limit the time to wait for the query to complete.
if (0 != timeout)
stmt.setQueryTimeout(timeout);

// Try to execute the query. If this succeeds, then the connection is valid.
// If it fails (throws an exception), then the connection is not valid.
// If a timeout was provided, execution throws an "query timed out" exception
// if the query fails to execute in that time.
/*
* Try to execute the query. If this succeeds, then the connection is valid. If it fails (throws an
* exception), then the connection is not valid. If a timeout was provided, execution throws an
* "query timed out" exception if the query fails to execute in that time.
*/
stmt.executeQueryInternal("SELECT 1");
stmt.close();
isValid = true;
} catch (SQLException e) {
// Do not propagate SQLExceptions from query execution or statement closure.
// The connection is considered to be invalid if the statement fails to close,
// even though query execution succeeded.
isValid = false;
cheenamalhotra marked this conversation as resolved.
Show resolved Hide resolved
/*
* Do not propagate SQLExceptions from query execution or statement closure. The connection is considered to
* be invalid if the statement fails to close, even though query execution succeeded.
*/
connectionlogger.fine(toString() + " Exception checking connection validity: " + e.getMessage());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -267,8 +267,7 @@ public void testIsWrapperFor() throws SQLException, ClassNotFoundException {
isWrapper = ssconn.isWrapperFor(Class.forName("com.microsoft.sqlserver.jdbc.ISQLServerConnection"));
Object[] msgArgs2 = {"ISQLServerConnection"};
assertTrue(isWrapper, form.format(msgArgs2));
ISQLServerConnection iSql = (ISQLServerConnection) ssconn
.unwrap(Class.forName("com.microsoft.sqlserver.jdbc.ISQLServerConnection"));
ssconn.unwrap(Class.forName("com.microsoft.sqlserver.jdbc.ISQLServerConnection"));
assertEquals(ISQLServerConnection.TRANSACTION_SNAPSHOT, ISQLServerConnection.TRANSACTION_SNAPSHOT,
TestResource.getResource("R_cantAccessSnapshot"));

Expand Down