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

Add cancelTimeout to cancel the query timeout #560

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 36 additions & 7 deletions src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java
Original file line number Diff line number Diff line change
Expand Up @@ -6349,7 +6349,8 @@ final class TDSReaderMark {
final class TDSReader {
private final static Logger logger = Logger.getLogger("com.microsoft.sqlserver.jdbc.internals.TDS.Reader");
final private String traceID;

private TimeoutTimer tcpKeepAliveTimeoutTimer;

final public String toString() {
return traceID;
}
Expand Down Expand Up @@ -6390,6 +6391,8 @@ private static int nextReaderID() {
this.tdsChannel = tdsChannel;
this.con = con;
this.command = command; // may be null
//if cancelTimeout is set, we should wait for the total amount of queryTimeout+cancelTimeout to terminate the connection.
this.tcpKeepAliveTimeoutTimer = (con.getCancelQueryTimeoutSeconds() > 0 && con.getQueryTimeoutSeconds()> 0 ) ? (new TimeoutTimer(con.getCancelQueryTimeoutSeconds() + con.getQueryTimeoutSeconds(), null, con)) : null;
// if the logging level is not detailed than fine or more we will not have proper readerids.
if (logger.isLoggable(Level.FINE))
traceID = "TDSReader@" + nextReaderID() + " (" + con.toString() + ")";
Expand Down Expand Up @@ -6488,7 +6491,12 @@ synchronized final boolean readPacket() throws SQLServerException {
+ tdsChannel.numMsgsSent;

TDSPacket newPacket = new TDSPacket(con.getTDSPacketSize());

if (null != tcpKeepAliveTimeoutTimer) {
if (logger.isLoggable(Level.FINEST)) {
logger.finest(this.toString() + ":starting timer...");
}
tcpKeepAliveTimeoutTimer.start();
}
// First, read the packet header.
for (int headerBytesRead = 0; headerBytesRead < TDS.PACKET_HEADER_SIZE;) {
int bytesRead = tdsChannel.read(newPacket.header, headerBytesRead, TDS.PACKET_HEADER_SIZE - headerBytesRead);
Expand All @@ -6502,7 +6510,14 @@ synchronized final boolean readPacket() throws SQLServerException {

headerBytesRead += bytesRead;
}


// if execution was subject to timeout then stop timing
if (null != tcpKeepAliveTimeoutTimer) {
if (logger.isLoggable(Level.FINEST)) {
logger.finest(this.toString() + ":stopping timer...");
}
tcpKeepAliveTimeoutTimer.stop();
}
// Header size is a 2 byte unsigned short integer in big-endian order.
int packetLength = Util.readUnsignedShortBigEndian(newPacket.header, TDS.PACKET_HEADER_MESSAGE_LENGTH);

Expand Down Expand Up @@ -7110,7 +7125,8 @@ final class TimeoutTimer implements Runnable {
private final int timeoutSeconds;
private final TDSCommand command;
private volatile Future<?> task;

private final SQLServerConnection con;

private static final ExecutorService executor = Executors.newCachedThreadPool(new ThreadFactory() {
private final AtomicReference<ThreadGroup> tgr = new AtomicReference<>();
private final AtomicInteger threadNumber = new AtomicInteger(0);
Expand All @@ -7135,12 +7151,14 @@ public Thread newThread(Runnable r)
private volatile boolean canceled = false;

TimeoutTimer(int timeoutSeconds,
TDSCommand command) {
TDSCommand command,
SQLServerConnection con) {
assert timeoutSeconds > 0;
assert null != command;

this.timeoutSeconds = timeoutSeconds;
this.command = command;
this.con = con;
}

final void start() {
Expand Down Expand Up @@ -7174,12 +7192,23 @@ public void run() {
// If the timer wasn't canceled before it ran out of
// time then interrupt the registered command.
try {
command.interrupt(SQLServerException.getErrString("R_queryTimedOut"));
// if connection is silently dropped, the query timeout hangs too and does not throw the query timeout exception.
// The application hangs until the connection timeout is thrown. In this case, manually terminate the connection.
if (null == command) {
assert null != con;
con.terminate(SQLServerException.DRIVER_ERROR_IO_FAILED, SQLServerException.getErrString("R_connectionIsClosed"));
}
else {
// If the timer wasn't canceled before it ran out of
// time then interrupt the registered command.
command.interrupt(SQLServerException.getErrString("R_queryTimedOut"));
}
}
catch (SQLServerException e) {
// Unfortunately, there's nothing we can do if we
// fail to time out the request. There is no way
// to report back what happened.
assert null != command;
command.log(Level.FINE, "Command could not be timed out. Reason: " + e.getMessage());
}
}
Expand Down Expand Up @@ -7323,7 +7352,7 @@ final boolean readingResponse() {
TDSCommand(String logContext,
int timeoutSeconds) {
this.logContext = logContext;
this.timeoutTimer = (timeoutSeconds > 0) ? (new TimeoutTimer(timeoutSeconds, this)) : null;
this.timeoutTimer = (timeoutSeconds > 0) ? (new TimeoutTimer(timeoutSeconds, this, null)) : null;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,18 @@ final String getResponseBuffering() {
final int getQueryTimeoutSeconds() {
return queryTimeoutSeconds;
}
/**
* timeout value for canceling the query timeout
*/
private int cancelQueryTimeoutSeconds;

/**
* Retrieves the cancelTimeout in seconds
* @return
*/
final int getCancelQueryTimeoutSeconds() {
return cancelQueryTimeoutSeconds;
}

private int socketTimeoutMilliseconds;

Expand Down Expand Up @@ -1689,6 +1701,28 @@ else if (0 == requestedPacketSize)
}
}

sPropKey = SQLServerDriverIntProperty.CANCEL_QUERY_TIMEOUT.toString();
int defaultCancelTimeout = SQLServerDriverIntProperty.CANCEL_QUERY_TIMEOUT.getDefaultValue();
// use cancelTimeout only if queryTimeout is set.
if (activeConnectionProperties.getProperty(sPropKey) != null && activeConnectionProperties.getProperty(sPropKey).length() > 0 && queryTimeoutSeconds > defaultQueryTimeout) {
try {
int n = new Integer(activeConnectionProperties.getProperty(sPropKey));
if (n >= defaultCancelTimeout) {
cancelQueryTimeoutSeconds = n;
}
else {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidCancelQueryTimeout"));
Object[] msgArgs = {activeConnectionProperties.getProperty(sPropKey)};
SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false);
}
}
catch (NumberFormatException e) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidCancelQueryTimeout"));
Object[] msgArgs = {activeConnectionProperties.getProperty(sPropKey)};
SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false);
}
}

sPropKey = SQLServerDriverIntProperty.SERVER_PREPARED_STATEMENT_DISCARD_THRESHOLD.toString();
if (activeConnectionProperties.getProperty(sPropKey) != null && activeConnectionProperties.getProperty(sPropKey).length() > 0) {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -690,6 +690,26 @@ public int getQueryTimeout() {
return getIntProperty(connectionProps, SQLServerDriverIntProperty.QUERY_TIMEOUT.toString(),
SQLServerDriverIntProperty.QUERY_TIMEOUT.getDefaultValue());
}

/**
* Setting the cancel timeout
*
* @param cancelQueryTimeout
* The number of seconds to wait before we wait for the query timeout to happen.
*/
public void setCancelQueryTimeout(int cancelQueryTimeout) {
setIntProperty(connectionProps, SQLServerDriverIntProperty.CANCEL_QUERY_TIMEOUT.toString(), cancelQueryTimeout);
}

/**
* Getting the cancel timeout
*
* @return the number of seconds to wait before we wait for the query timeout to happen.
*/
public int getCancelQueryTimeout() {
return getIntProperty(connectionProps, SQLServerDriverIntProperty.CANCEL_QUERY_TIMEOUT.toString(),
SQLServerDriverIntProperty.CANCEL_QUERY_TIMEOUT.getDefaultValue());
}

/**
* If this configuration is false the first execution of a prepared statement will call sp_executesql and not prepare
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ enum SQLServerDriverIntProperty {
SOCKET_TIMEOUT ("socketTimeout", 0),
SERVER_PREPARED_STATEMENT_DISCARD_THRESHOLD("serverPreparedStatementDiscardThreshold", SQLServerConnection.DEFAULT_SERVER_PREPARED_STATEMENT_DISCARD_THRESHOLD),
STATEMENT_POOLING_CACHE_SIZE ("statementPoolingCacheSize", SQLServerConnection.DEFAULT_STATEMENT_POOLING_CACHE_SIZE),
CANCEL_QUERY_TIMEOUT ("cancelQueryTimeout", -1),
;

private final String name;
Expand Down Expand Up @@ -428,6 +429,7 @@ public final class SQLServerDriver implements java.sql.Driver {
new SQLServerDriverPropertyInfo(SQLServerDriverIntProperty.STATEMENT_POOLING_CACHE_SIZE.toString(), Integer.toString(SQLServerDriverIntProperty.STATEMENT_POOLING_CACHE_SIZE.getDefaultValue()), false, null),
new SQLServerDriverPropertyInfo(SQLServerDriverStringProperty.JAAS_CONFIG_NAME.toString(), SQLServerDriverStringProperty.JAAS_CONFIG_NAME.getDefaultValue(), false, null),
new SQLServerDriverPropertyInfo(SQLServerDriverStringProperty.SSL_PROTOCOL.toString(), SQLServerDriverStringProperty.SSL_PROTOCOL.getDefaultValue(), false, new String[] {SSLProtocol.TLS.toString(), SSLProtocol.TLS_V10.toString(), SSLProtocol.TLS_V11.toString(), SSLProtocol.TLS_V12.toString()}),
new SQLServerDriverPropertyInfo(SQLServerDriverIntProperty.CANCEL_QUERY_TIMEOUT.toString(), Integer.toString(SQLServerDriverIntProperty.CANCEL_QUERY_TIMEOUT.getDefaultValue()), false, null),
};

// Properties that can only be set by using Properties.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -391,5 +391,7 @@ protected Object[][] getContents() {
{"R_invalidDataTypeSupportForSQLVariant", "Unexpected TDS type ' '{0}' ' in SQL_VARIANT."},
{"R_sslProtocolPropertyDescription", "SSL protocol label from TLS, TLSv1, TLSv1.1, and TLSv1.2. The default is TLS."},
{"R_invalidSSLProtocol", "SSL Protocol {0} label is not valid. Only TLS, TLSv1, TLSv1.1, and TLSv1.2 are supported."},
{"R_cancelQueryTimeoutPropertyDescription", "The number of seconds to wait to cancel sending a query timeout."},
{"R_invalidCancelQueryTimeout", "The cancel timeout value {0} is not valid."},
};
}