in src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java [2726:3043]
private void login(String primary, String primaryInstanceName, int primaryPortNumber, String mirror,
FailoverInfo foActual, int timeout, long timerStart) throws SQLServerException {
// standardLogin would be false only for db mirroring scenarios. It would be true
// for all other cases, including multiSubnetFailover
final boolean isDBMirroring = null != mirror || null != foActual;
int sleepInterval = 100; // milliseconds to sleep (back off) between attempts.
long timeoutUnitInterval;
boolean useFailoverHost = false;
FailoverInfo tempFailover = null;
// This is the failover server info place holder
ServerPortPlaceHolder currentFOPlaceHolder = null;
// This is the primary server placeHolder
ServerPortPlaceHolder currentPrimaryPlaceHolder = null;
if (null != foActual) {
tempFailover = foActual;
useFailoverHost = foActual.getUseFailoverPartner();
} else {
if (isDBMirroring) {
// Create a temporary class with the mirror info from the user
tempFailover = new FailoverInfo(mirror, this, false);
}
}
// useParallel is set to true only for the first connection
// when multiSubnetFailover is set to true. In all other cases, it is set
// to false.
boolean useParallel = getMultiSubnetFailover();
boolean useTnir = getTransparentNetworkIPResolution();
long intervalExpire;
if (0 == timeout) {
timeout = SQLServerDriverIntProperty.LOGIN_TIMEOUT.getDefaultValue();
}
long timerTimeout = timeout * 1000L; // ConnectTimeout is in seconds, we need timer millis
timerExpire = timerStart + timerTimeout;
// For non-dbmirroring, non-tnir and non-multisubnetfailover scenarios, full time out would be used as time
// slice.
if (isDBMirroring || useParallel) {
timeoutUnitInterval = (long) (TIMEOUTSTEP * timerTimeout);
} else if (useTnir) {
timeoutUnitInterval = (long) (TIMEOUTSTEP_TNIR * timerTimeout);
} else {
timeoutUnitInterval = timerTimeout;
}
intervalExpire = timerStart + timeoutUnitInterval;
// This is needed when the host resolves to more than 64 IP addresses. In that case, TNIR is ignored
// and the original timeout is used instead of the timeout slice.
long intervalExpireFullTimeout = timerStart + timerTimeout;
if (connectionlogger.isLoggable(Level.FINER)) {
connectionlogger.finer(toString() + " Start time: " + timerStart + " Time out time: " + timerExpire
+ " Timeout Unit Interval: " + timeoutUnitInterval);
}
// Initialize loop variables
int attemptNumber = 0;
// indicates the no of times the connection was routed to a different server
int noOfRedirections = 0;
// Only three ways out of this loop:
// 1) Successfully connected
// 2) Parser threw exception while main timer was expired
// 3) Parser threw logon failure-related exception (LOGON_FAILED, PASSWORD_EXPIRED, etc)
//
// Of these methods, only #1 exits normally. This preserves the call stack on the exception
// back into the parser for the error cases.
while (true) {
clientConnectionId = null;
state = State.Initialized;
try {
if (isDBMirroring && useFailoverHost) {
if (null == currentFOPlaceHolder) {
// integrated security flag passed here to verify that the linked dll can be loaded
currentFOPlaceHolder = tempFailover.failoverPermissionCheck(this, integratedSecurity);
}
currentConnectPlaceHolder = currentFOPlaceHolder;
} else {
if (routingInfo != null) {
currentPrimaryPlaceHolder = routingInfo;
routingInfo = null;
} else if (null == currentPrimaryPlaceHolder) {
currentPrimaryPlaceHolder = primaryPermissionCheck(primary, primaryInstanceName,
primaryPortNumber);
}
currentConnectPlaceHolder = currentPrimaryPlaceHolder;
}
if (connectionlogger.isLoggable(Level.FINE)) {
connectionlogger
.fine(toString() + " This attempt server name: " + currentConnectPlaceHolder.getServerName()
+ " port: " + currentConnectPlaceHolder.getPortNumber() + " InstanceName: "
+ currentConnectPlaceHolder.getInstanceName() + " useParallel: " + useParallel);
connectionlogger.fine(toString() + " This attempt endtime: " + intervalExpire);
connectionlogger.fine(toString() + " This attempt No: " + attemptNumber);
}
// Attempt login. Use Place holder to make sure that the failoverdemand is done.
InetSocketAddress inetSocketAddress = connectHelper(currentConnectPlaceHolder,
timerRemaining(intervalExpire), timeout, useParallel, useTnir, (0 == attemptNumber), // TNIR
// first
// attempt
timerRemaining(intervalExpireFullTimeout)); // Only used when host resolves to >64 IPs
// Successful connection, cache the IP address and port if server supports DNS Cache.
if (serverSupportsDNSCaching) {
dnsCache.put(currentConnectPlaceHolder.getServerName(), inetSocketAddress);
}
if (isRoutedInCurrentAttempt) {
// we ignore the failoverpartner ENVCHANGE if we got routed so no error needs to be thrown
if (isDBMirroring) {
String msg = SQLServerException.getErrString("R_invalidRoutingInfo");
terminate(SQLServerException.DRIVER_ERROR_UNSUPPORTED_CONFIG, msg);
}
noOfRedirections++;
if (noOfRedirections > 1) {
String msg = SQLServerException.getErrString("R_multipleRedirections");
terminate(SQLServerException.DRIVER_ERROR_UNSUPPORTED_CONFIG, msg);
}
// close tds channel
if (tdsChannel != null)
tdsChannel.close();
initResettableValues();
// reset all params that could have been changed due to ENVCHANGE tokens
// to defaults, excluding those changed due to routing ENVCHANGE token
resetNonRoutingEnvchangeValues();
// increase the attempt number. This is not really necessary
// (in fact it does not matter whether we increase it or not) as
// we do not use any timeslicing for multisubnetfailover. However, this
// is done just to be consistent with the rest of the logic.
attemptNumber++;
// useParallel and useTnir should be set to false once we get routed
useParallel = false;
useTnir = false;
// When connection is routed for read only application, remaining timer duration is used as a one
// full interval
intervalExpire = timerExpire;
// if timeout expired, throw.
if (timerHasExpired(timerExpire)) {
MessageFormat form = new MessageFormat(
SQLServerException.getErrString("R_tcpipConnectionFailed"));
Object[] msgArgs = {getServerNameString(currentConnectPlaceHolder.getServerName()),
Integer.toString(currentConnectPlaceHolder.getPortNumber()),
SQLServerException.getErrString("R_timedOutBeforeRouting")};
String msg = form.format(msgArgs);
terminate(SQLServerException.DRIVER_ERROR_UNSUPPORTED_CONFIG, msg);
} else {
// set isRoutedInCurrentAttempt to false for the next attempt
isRoutedInCurrentAttempt = false;
continue;
}
} else
break; // leave the while loop -- we've successfully connected
} catch (SQLServerException sqlex) {
int errorCode = sqlex.getErrorCode();
int driverErrorCode = sqlex.getDriverErrorCode();
if (SQLServerException.LOGON_FAILED == errorCode // logon failed, ie bad password
|| SQLServerException.PASSWORD_EXPIRED == errorCode // password expired
|| SQLServerException.USER_ACCOUNT_LOCKED == errorCode // user account locked
|| SQLServerException.DRIVER_ERROR_INVALID_TDS == driverErrorCode // invalid TDS
|| SQLServerException.DRIVER_ERROR_SSL_FAILED == driverErrorCode // SSL failure
|| SQLServerException.DRIVER_ERROR_INTERMITTENT_TLS_FAILED == driverErrorCode // TLS1.2 failure
|| SQLServerException.DRIVER_ERROR_UNSUPPORTED_CONFIG == driverErrorCode // unsupported config
// (eg Sphinx, invalid
// packetsize, etc)
|| SQLServerException.ERROR_SOCKET_TIMEOUT == driverErrorCode // socket timeout
|| timerHasExpired(timerExpire)
// for non-dbmirroring cases, do not retry after tcp socket connection succeeds
) {
// close the connection and throw the error back
close();
throw sqlex;
} else {
// Close the TDS channel from the failed connection attempt so that we don't
// hold onto network resources any longer than necessary.
if (null != tdsChannel)
tdsChannel.close();
}
// For standard connections and MultiSubnetFailover connections, change the sleep interval after every
// attempt.
// For DB Mirroring, we only sleep after every other attempt.
if (!isDBMirroring || 1 == attemptNumber % 2) {
// Check sleep interval to make sure we won't exceed the timeout
// Do this in the catch block so we can re-throw the current exception
long remainingMilliseconds = timerRemaining(timerExpire);
if (remainingMilliseconds <= sleepInterval) {
throw sqlex;
}
}
}
// We only get here when we failed to connect, but are going to re-try
// After trying to connect to both servers fails, sleep for a bit to prevent clogging
// the network with requests, then update sleep interval for next iteration (max 1 second interval)
// We have to sleep for every attempt in case of non-dbMirroring scenarios (including multisubnetfailover),
// Whereas for dbMirroring, we sleep for every two attempts as each attempt is to a different server.
if (!isDBMirroring || (1 == attemptNumber % 2)) {
if (connectionlogger.isLoggable(Level.FINE)) {
connectionlogger.fine(toString() + " sleeping milisec: " + sleepInterval);
}
try {
Thread.sleep(sleepInterval);
} catch (InterruptedException e) {
// re-interrupt the current thread, in order to restore the thread's interrupt status.
Thread.currentThread().interrupt();
}
sleepInterval = (sleepInterval < 500) ? sleepInterval * 2 : 1000;
}
// Update timeout interval (but no more than the point where we're supposed to fail: timerExpire)
attemptNumber++;
if (useParallel) {
intervalExpire = System.currentTimeMillis() + (timeoutUnitInterval * (attemptNumber + 1));
} else if (isDBMirroring) {
intervalExpire = System.currentTimeMillis() + (timeoutUnitInterval * ((attemptNumber / 2) + 1));
} else if (useTnir) {
long timeSlice = timeoutUnitInterval * (1 << attemptNumber);
// In case the timeout for the first slice is less than 500 ms then bump it up to 500 ms
if ((1 == attemptNumber) && (500 > timeSlice)) {
timeSlice = 500;
}
intervalExpire = System.currentTimeMillis() + timeSlice;
} else
intervalExpire = timerExpire;
// Due to the below condition and the timerHasExpired check in catch block,
// the multiSubnetFailover case or any other standardLogin case where timeOutInterval is full timeout would
// also be handled correctly.
if (intervalExpire > timerExpire) {
intervalExpire = timerExpire;
}
// try again, this time swapping primary/secondary servers
if (isDBMirroring) {
useFailoverHost = !useFailoverHost;
}
}
// If we get here, connection/login succeeded! Just a few more checks & record-keeping
// if connected to failover host, but said host doesn't have DbMirroring set up, throw an error
if (useFailoverHost && null == failoverPartnerServerProvided) {
String curserverinfo = currentConnectPlaceHolder.getServerName();
if (null != currentFOPlaceHolder.getInstanceName()) {
curserverinfo = curserverinfo + "\\";
curserverinfo = curserverinfo + currentFOPlaceHolder.getInstanceName();
}
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidPartnerConfiguration"));
Object[] msgArgs = {
activeConnectionProperties.getProperty(SQLServerDriverStringProperty.DATABASE_NAME.toString()),
curserverinfo};
terminate(SQLServerException.DRIVER_ERROR_UNSUPPORTED_CONFIG, form.format(msgArgs));
}
if (null != failoverPartnerServerProvided) {
// if server returns failoverPartner when multiSubnetFailover keyword is used, fail
if (multiSubnetFailover) {
String msg = SQLServerException.getErrString("R_dbMirroringWithMultiSubnetFailover");
terminate(SQLServerException.DRIVER_ERROR_UNSUPPORTED_CONFIG, msg);
}
// if server returns failoverPartner and applicationIntent=ReadOnly, fail
if ((applicationIntent != null) && applicationIntent.equals(ApplicationIntent.READ_ONLY)) {
String msg = SQLServerException.getErrString("R_dbMirroringWithReadOnlyIntent");
terminate(SQLServerException.DRIVER_ERROR_UNSUPPORTED_CONFIG, msg);
}
if (null == tempFailover)
tempFailover = new FailoverInfo(failoverPartnerServerProvided, this, false);
// if the failover is not from the map already out this in the map, if it is from the map just make sure
// that we change the
if (null != foActual) {
// We must wait for CompleteLogin to finish for to have the
// env change from the server to know its designated failover
// partner; saved in failoverPartnerServerProvided
foActual.failoverAdd(this, useFailoverHost, failoverPartnerServerProvided);
} else {
String databaseNameProperty = SQLServerDriverStringProperty.DATABASE_NAME.toString();
String instanceNameProperty = SQLServerDriverStringProperty.INSTANCE_NAME.toString();
String serverNameProperty = SQLServerDriverStringProperty.SERVER_NAME.toString();
if (connectionlogger.isLoggable(Level.FINE)) {
connectionlogger.fine(toString() + " adding new failover info server: "
+ activeConnectionProperties.getProperty(serverNameProperty) + " instance: "
+ activeConnectionProperties.getProperty(instanceNameProperty) + " database: "
+ activeConnectionProperties.getProperty(databaseNameProperty)
+ " server provided failover: " + failoverPartnerServerProvided);
}
tempFailover.failoverAdd(this, useFailoverHost, failoverPartnerServerProvided);
FailoverMapSingleton.putFailoverInfo(this, primary,
activeConnectionProperties.getProperty(instanceNameProperty),
activeConnectionProperties.getProperty(databaseNameProperty), tempFailover, useFailoverHost,
failoverPartnerServerProvided);
}
}
}