Skip to content

Commit 2881d67

Browse files
[Release/2.0] Fix pooled connection re-use on access token expiry (#639)
1 parent 8b8196a commit 2881d67

File tree

9 files changed

+122
-62
lines changed

9 files changed

+122
-62
lines changed

src/Microsoft.Data.SqlClient/netcore/src/Common/src/Microsoft/Data/Common/AdapterUtil.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@ internal static partial class ADP
2929
internal static Task<bool> FalseTask => _falseTask ?? (_falseTask = Task.FromResult(false));
3030

3131
internal const CompareOptions DefaultCompareOptions = CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase;
32+
3233
internal const int DefaultConnectionTimeout = DbConnectionStringDefaults.ConnectTimeout;
34+
internal const int InfiniteConnectionTimeout = 0; // infinite connection timeout identifier in seconds
35+
internal const int MaxBufferAccessTokenExpiry = 600; // max duration for buffer in seconds
3336

3437
static private void TraceException(string trace, Exception e)
3538
{

src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,8 @@ virtual protected bool ReadyToPrepareTransaction
203203
}
204204
}
205205

206+
internal virtual bool IsAccessTokenExpired => false;
207+
206208
abstract protected void Activate(Transaction transaction);
207209

208210
internal void ActivateConnection(Transaction transaction)

src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/ProviderBase/DbConnectionPool.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1285,6 +1285,13 @@ private bool TryGetConnection(DbConnection owningObject, uint waitForMultipleObj
12851285
_waitHandles.CreationSemaphore.Release(1);
12861286
}
12871287
}
1288+
1289+
// Do not use this pooled connection if access token is about to expire soon before we can connect.
1290+
if(null != obj && obj.IsAccessTokenExpired)
1291+
{
1292+
DestroyObject(obj);
1293+
obj = null;
1294+
}
12881295
} while (null == obj);
12891296
}
12901297

src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ override protected DbConnectionInternal CreateConnection(DbConnectionOptions opt
3737
{
3838
SqlConnectionString opt = (SqlConnectionString)options;
3939
SqlConnectionPoolKey key = (SqlConnectionPoolKey)poolKey;
40-
SqlInternalConnection result = null;
4140
SessionData recoverySessionData = null;
4241

4342
SqlConnection sqlOwningConnection = (SqlConnection)owningConnection;
@@ -131,8 +130,7 @@ override protected DbConnectionInternal CreateConnection(DbConnectionOptions opt
131130
opt = new SqlConnectionString(opt, instanceName, userInstance: false, setEnlistValue: null);
132131
poolGroupProviderInfo = null; // null so we do not pass to constructor below...
133132
}
134-
result = new SqlInternalConnectionTds(identity, opt, key.Credential, poolGroupProviderInfo, "", null, redirectedUserInstance, userOpt, recoverySessionData, applyTransientFaultHandling: applyTransientFaultHandling, key.AccessToken, pool);
135-
return result;
133+
return new SqlInternalConnectionTds(identity, opt, key.Credential, poolGroupProviderInfo, "", null, redirectedUserInstance, userOpt, recoverySessionData, applyTransientFaultHandling: applyTransientFaultHandling, key.AccessToken, pool);
136134
}
137135

138136
protected override DbConnectionOptions CreateConnectionOptions(string connectionString, DbConnectionOptions previous)

src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs

Lines changed: 58 additions & 37 deletions
Large diffs are not rendered by default.

src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Common/AdapterUtil.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2180,6 +2180,8 @@ static internal Exception InvalidArgumentValue(string methodName)
21802180
internal const int DecimalMaxPrecision28 = 28; // there are some cases in Odbc where we need that ...
21812181
internal const int DefaultCommandTimeout = 30;
21822182
internal const int DefaultConnectionTimeout = DbConnectionStringDefaults.ConnectTimeout;
2183+
internal const int InfiniteConnectionTimeout = 0; // infinite connection timeout identifier in seconds
2184+
internal const int MaxBufferAccessTokenExpiry = 600; // max duration for buffer in seconds
21832185
internal const float FailoverTimeoutStep = 0.08F; // fraction of timeout to use for fast failover connections
21842186
internal const float FailoverTimeoutStepForTnir = 0.125F; // Fraction of timeout to use in case of Transparent Network IP resolution.
21852187
internal const int MinimumTimeoutForTnirMs = 500; // The first login attempt in Transparent network IP Resolution

src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,8 @@ public ConnectionState State
382382
}
383383
}
384384

385+
internal virtual bool IsAccessTokenExpired => false;
386+
385387
abstract protected void Activate(SysTx.Transaction transaction);
386388

387389
internal void ActivateConnection(SysTx.Transaction transaction)

src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/ProviderBase/DbConnectionPool.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1528,6 +1528,13 @@ private bool TryGetConnection(DbConnection owningObject, uint waitForMultipleObj
15281528
{
15291529
Marshal.ThrowExceptionForHR(releaseSemaphoreResult); // will only throw if (hresult < 0)
15301530
}
1531+
1532+
// Do not use this pooled connection if access token is about to expire soon before we can connect.
1533+
if (null != obj && obj.IsAccessTokenExpired)
1534+
{
1535+
DestroyObject(obj);
1536+
obj = null;
1537+
}
15311538
} while (null == obj);
15321539
}
15331540

src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ sealed internal class SqlInternalConnectionTds : SqlInternalConnection, IDisposa
119119
// Connection Resiliency
120120
private bool _sessionRecoveryRequested;
121121
internal bool _sessionRecoveryAcknowledged;
122-
internal SessionData _currentSessionData; // internal for use from TdsParser only, otehr should use CurrentSessionData property that will fix database and language
122+
internal SessionData _currentSessionData; // internal for use from TdsParser only, other should use CurrentSessionData property that will fix database and language
123123
private SessionData _recoverySessionData;
124124

125125
// Federated Authentication
@@ -131,13 +131,14 @@ sealed internal class SqlInternalConnectionTds : SqlInternalConnection, IDisposa
131131
internal bool _federatedAuthenticationInfoRequested; // Keep this distinct from _federatedAuthenticationRequested, since some fedauth library types may not need more info
132132
internal bool _federatedAuthenticationInfoReceived;
133133

134+
// The Federated Authentication returned by TryGetFedAuthTokenLocked or GetFedAuthToken.
135+
SqlFedAuthToken _fedAuthToken = null;
134136
internal byte[] _accessTokenInBytes;
135137

136138
private readonly ActiveDirectoryAuthenticationTimeoutRetryHelper _activeDirectoryAuthTimeoutRetryHelper;
137139
private readonly SqlAuthenticationProviderManager _sqlAuthenticationProviderManager;
138140

139141
// Certificate auth calbacks.
140-
//
141142
ServerCertificateValidationCallback _serverCallback;
142143
ClientCertificateRetrievalCallback _clientCallback;
143144
SqlClientOriginalNetworkAddressInfo _originalNetworkAddressInfo;
@@ -146,6 +147,18 @@ sealed internal class SqlInternalConnectionTds : SqlInternalConnection, IDisposa
146147

147148
private bool _serverSupportsDNSCaching = false;
148149

150+
/// <summary>
151+
/// Returns buffer time allowed before access token expiry to continue using the access token.
152+
/// </summary>
153+
private int accessTokenExpirationBufferTime
154+
{
155+
get
156+
{
157+
return (ConnectionOptions.ConnectTimeout == ADP.InfiniteConnectionTimeout || ConnectionOptions.ConnectTimeout >= ADP.MaxBufferAccessTokenExpiry)
158+
? ADP.MaxBufferAccessTokenExpiry : ConnectionOptions.ConnectTimeout;
159+
}
160+
}
161+
149162
/// <summary>
150163
/// Get or set if SQLDNSCaching FeatureExtAck is supported by the server.
151164
/// </summary>
@@ -808,6 +821,10 @@ protected override bool UnbindOnTransactionCompletion
808821
}
809822
}
810823

824+
/// <summary>
825+
/// Validates if federated authentication is used, Access Token used by this connection is active for the value of 'accessTokenExpirationBufferTime'.
826+
/// </summary>
827+
internal override bool IsAccessTokenExpired => _federatedAuthenticationInfoRequested && DateTime.FromFileTimeUtc(_fedAuthToken.expirationFileTime) < DateTime.UtcNow.AddSeconds(accessTokenExpirationBufferTime);
811828

812829
////////////////////////////////////////////////////////////////////////////////////////
813830
// GENERAL METHODS
@@ -1321,10 +1338,10 @@ internal void ExecuteTransactionYukon(
13211338
ThreadHasParserLockForClose = false;
13221339
_parserLock.Release();
13231340
releaseConnectionLock = false;
1324-
}, 0);
1341+
}, ADP.InfiniteConnectionTimeout);
13251342
if (reconnectTask != null)
13261343
{
1327-
AsyncHelper.WaitForCompletion(reconnectTask, 0); // there is no specific timeout for BeginTransaction, uses ConnectTimeout
1344+
AsyncHelper.WaitForCompletion(reconnectTask, ADP.InfiniteConnectionTimeout); // there is no specific timeout for BeginTransaction, uses ConnectTimeout
13281345
internalTransaction.ConnectionHasBeenRestored = true;
13291346
return;
13301347
}
@@ -2538,9 +2555,6 @@ internal void OnFedAuthInfo(SqlFedAuthInfo fedAuthInfo)
25382555
// We want to refresh the token, if taking the lock on the authentication context is successful.
25392556
bool attemptRefreshTokenLocked = false;
25402557

2541-
// The Federated Authentication returned by TryGetFedAuthTokenLocked or GetFedAuthToken.
2542-
SqlFedAuthToken fedAuthToken = null;
2543-
25442558
if (_dbConnectionPool != null)
25452559
{
25462560
Debug.Assert(_dbConnectionPool.AuthenticationContexts != null);
@@ -2575,7 +2589,7 @@ internal void OnFedAuthInfo(SqlFedAuthInfo fedAuthInfo)
25752589
}
25762590
else if (_forceExpiryLocked)
25772591
{
2578-
attemptRefreshTokenLocked = TryGetFedAuthTokenLocked(fedAuthInfo, dbConnectionPoolAuthenticationContext, out fedAuthToken);
2592+
attemptRefreshTokenLocked = TryGetFedAuthTokenLocked(fedAuthInfo, dbConnectionPoolAuthenticationContext, out _fedAuthToken);
25792593
}
25802594
#endif
25812595

@@ -2589,11 +2603,11 @@ internal void OnFedAuthInfo(SqlFedAuthInfo fedAuthInfo)
25892603

25902604
// Call the function which tries to acquire a lock over the authentication context before trying to update.
25912605
// If the lock could not be obtained, it will return false, without attempting to fetch a new token.
2592-
attemptRefreshTokenLocked = TryGetFedAuthTokenLocked(fedAuthInfo, dbConnectionPoolAuthenticationContext, out fedAuthToken);
2606+
attemptRefreshTokenLocked = TryGetFedAuthTokenLocked(fedAuthInfo, dbConnectionPoolAuthenticationContext, out _fedAuthToken);
25932607

2594-
// If TryGetFedAuthTokenLocked returns true, it means lock was obtained and fedAuthToken should not be null.
2608+
// If TryGetFedAuthTokenLocked returns true, it means lock was obtained and _fedAuthToken should not be null.
25952609
// If there was an exception in retrieving the new token, TryGetFedAuthTokenLocked should have thrown, so we won't be here.
2596-
Debug.Assert(!attemptRefreshTokenLocked || fedAuthToken != null, "Either Lock should not have been obtained or fedAuthToken should not be null.");
2610+
Debug.Assert(!attemptRefreshTokenLocked || _fedAuthToken != null, "Either Lock should not have been obtained or _fedAuthToken should not be null.");
25972611
Debug.Assert(!attemptRefreshTokenLocked || _newDbConnectionPoolAuthenticationContext != null, "Either Lock should not have been obtained or _newDbConnectionPoolAuthenticationContext should not be null.");
25982612

25992613
// Indicate in Bid Trace that we are successful with the update.
@@ -2610,8 +2624,8 @@ internal void OnFedAuthInfo(SqlFedAuthInfo fedAuthInfo)
26102624
if (dbConnectionPoolAuthenticationContext == null || attemptRefreshTokenUnLocked)
26112625
{
26122626
// Get the Federated Authentication Token.
2613-
fedAuthToken = GetFedAuthToken(fedAuthInfo);
2614-
Debug.Assert(fedAuthToken != null, "fedAuthToken should not be null.");
2627+
_fedAuthToken = GetFedAuthToken(fedAuthInfo);
2628+
Debug.Assert(_fedAuthToken != null, "_fedAuthToken should not be null.");
26152629

26162630
if (_dbConnectionPool != null)
26172631
{
@@ -2622,18 +2636,19 @@ internal void OnFedAuthInfo(SqlFedAuthInfo fedAuthInfo)
26222636
else if (!attemptRefreshTokenLocked)
26232637
{
26242638
Debug.Assert(dbConnectionPoolAuthenticationContext != null, "dbConnectionPoolAuthenticationContext should not be null.");
2625-
Debug.Assert(fedAuthToken == null, "fedAuthToken should be null in this case.");
2639+
Debug.Assert(_fedAuthToken == null, "_fedAuthToken should be null in this case.");
26262640
Debug.Assert(_newDbConnectionPoolAuthenticationContext == null, "_newDbConnectionPoolAuthenticationContext should be null.");
26272641

2628-
fedAuthToken = new SqlFedAuthToken();
2642+
_fedAuthToken = new SqlFedAuthToken();
26292643

26302644
// If the code flow is here, then we are re-using the context from the cache for this connection attempt and not
26312645
// generating a new access token on this thread.
2632-
fedAuthToken.accessToken = dbConnectionPoolAuthenticationContext.AccessToken;
2646+
_fedAuthToken.accessToken = dbConnectionPoolAuthenticationContext.AccessToken;
2647+
_fedAuthToken.expirationFileTime = dbConnectionPoolAuthenticationContext.ExpirationTime.ToFileTime();
26332648
}
26342649

2635-
Debug.Assert(fedAuthToken != null && fedAuthToken.accessToken != null, "fedAuthToken and fedAuthToken.accessToken cannot be null.");
2636-
_parser.SendFedAuthToken(fedAuthToken);
2650+
Debug.Assert(_fedAuthToken != null && _fedAuthToken.accessToken != null, "_fedAuthToken and _fedAuthToken.accessToken cannot be null.");
2651+
_parser.SendFedAuthToken(_fedAuthToken);
26372652
}
26382653

26392654
/// <summary>
@@ -2873,7 +2888,8 @@ internal void OnFeatureExtAck(int featureId, byte[] data)
28732888
{
28742889
if (_routingInfo != null)
28752890
{
2876-
if (TdsEnums.FEATUREEXT_SQLDNSCACHING != featureId) {
2891+
if (TdsEnums.FEATUREEXT_SQLDNSCACHING != featureId)
2892+
{
28772893
return;
28782894
}
28792895
}
@@ -3101,16 +3117,18 @@ internal void OnFeatureExtAck(int featureId, byte[] data)
31013117
throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream);
31023118
}
31033119

3104-
if (1 == data[0]) {
3120+
if (1 == data[0])
3121+
{
31053122
IsSQLDNSCachingSupported = true;
31063123
_cleanSQLDNSCaching = false;
3107-
3124+
31083125
if (_routingInfo != null)
31093126
{
31103127
IsDNSCachingBeforeRedirectSupported = true;
31113128
}
31123129
}
3113-
else {
3130+
else
3131+
{
31143132
// we receive the IsSupported whose value is 0
31153133
IsSQLDNSCachingSupported = false;
31163134
_cleanSQLDNSCaching = true;

0 commit comments

Comments
 (0)