diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.cs index 70c6de60aa..880ab081f0 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.cs @@ -60,6 +60,7 @@ internal class AzureAttestationEnclaveProvider : EnclaveProviderBase private const string AttestationUrlSuffix = @"/.well-known/openid-configuration"; private static readonly MemoryCache OpenIdConnectConfigurationCache = new MemoryCache(new MemoryCacheOptions()); + private static readonly TimeSpan s_openIdConnectConfigurationCacheTimeout = TimeSpan.FromDays(1); #endregion #region Internal methods @@ -349,11 +350,7 @@ private OpenIdConnectConfiguration GetOpenIdConfigForSigningKeys(string url, boo throw SQL.AttestationFailed(string.Format(Strings.GetAttestationTokenSigningKeysFailed, GetInnerMostExceptionMessage(exception)), exception); } - MemoryCacheEntryOptions options = new MemoryCacheEntryOptions - { - AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(1) - }; - OpenIdConnectConfigurationCache.Set(url, openIdConnectConfig, options); + OpenIdConnectConfigurationCache.Set(url, openIdConnectConfig, absoluteExpirationRelativeToNow: s_openIdConnectConfigurationCacheTimeout); } return openIdConnectConfig; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveProviderBase.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveProviderBase.cs index b666819dde..c81f04471c 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveProviderBase.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveProviderBase.cs @@ -68,7 +68,6 @@ internal abstract class EnclaveProviderBase : SqlColumnEncryptionEnclaveProvider { #region Constants private const int NonceSize = 256; - private const int ThreadRetryCacheTimeoutInMinutes = 10; private const int LockTimeoutMaxInMilliseconds = 15 * 1000; // 15 seconds #endregion @@ -85,6 +84,7 @@ internal abstract class EnclaveProviderBase : SqlColumnEncryptionEnclaveProvider // It is used to save the attestation url and nonce value across API calls protected static readonly MemoryCache ThreadRetryCache = new MemoryCache(new MemoryCacheOptions()); + private static readonly TimeSpan s_threadRetryCacheTimeout = TimeSpan.FromMinutes(10); #endregion #region protected methods @@ -167,11 +167,8 @@ protected void GetEnclaveSessionHelper(EnclaveSessionParameters enclaveSessionPa retryThreadID = Thread.CurrentThread.ManagedThreadId.ToString(); } - MemoryCacheEntryOptions options = new MemoryCacheEntryOptions - { - AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(ThreadRetryCacheTimeoutInMinutes) - }; - ThreadRetryCache.Set(Thread.CurrentThread.ManagedThreadId.ToString(), retryThreadID, options); + ThreadRetryCache.Set(Thread.CurrentThread.ManagedThreadId.ToString(), retryThreadID, + absoluteExpirationRelativeToNow: s_threadRetryCacheTimeout); } } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveSessionCache.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveSessionCache.cs index 5673395e11..c3845244f3 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveSessionCache.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveSessionCache.cs @@ -11,6 +11,9 @@ namespace Microsoft.Data.SqlClient // Maintains a cache of SqlEnclaveSession instances internal class EnclaveSessionCache { + // Cache timeout of 8 hours to be consistent with JWT validity. + private static readonly TimeSpan s_enclaveCacheTimeout = TimeSpan.FromHours(8); + private readonly MemoryCache enclaveMemoryCache = new MemoryCache(new MemoryCacheOptions()); private readonly object enclaveCacheLock = new object(); @@ -18,9 +21,6 @@ internal class EnclaveSessionCache // given that for Always Encrypted scenarios, the server is considered an "untrusted" man-in-the-middle. private long _counter; - // Cache timeout of 8 hours to be consistent with jwt validity. - private static int enclaveCacheTimeOutInHours = 8; - // Retrieves a SqlEnclaveSession from the cache internal SqlEnclaveSession GetEnclaveSession(EnclaveSessionParameters enclaveSessionParameters, out long counter) { @@ -62,11 +62,8 @@ internal SqlEnclaveSession CreateSession(EnclaveSessionParameters enclaveSession lock (enclaveCacheLock) { enclaveSession = new SqlEnclaveSession(sharedSecret, sessionId); - MemoryCacheEntryOptions options = new MemoryCacheEntryOptions - { - AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(enclaveCacheTimeOutInHours) - }; - enclaveMemoryCache.Set(cacheKey, enclaveSession, options); + enclaveMemoryCache.Set(cacheKey, enclaveSession, + absoluteExpirationRelativeToNow: s_enclaveCacheTimeout); counter = Interlocked.Increment(ref _counter); } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/ValueUtilsSmi.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/ValueUtilsSmi.cs index 878efe58bc..0b75a6178d 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/ValueUtilsSmi.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/ValueUtilsSmi.cs @@ -35,7 +35,20 @@ internal static class ValueUtilsSmi // Constants private const int DefaultBinaryBufferSize = 4096; // Size of the buffer used to read input parameter of type Stream - private const int DefaultTextBufferSize = 4096; // Size of the buffer (in chars) user to read input parameter of type TextReader + private const int DefaultTextBufferSize = 4096; // Size of the buffer (in chars) user to read input parameter of type TextReader + + // @TODO: Replace field with the `field` keyword when LangVersion >= 14. + private static XmlWriterSettings s_xmlWriterSettings; + + private static XmlWriterSettings XmlWriterSettings => + s_xmlWriterSettings ??= new XmlWriterSettings() + { + // Don't close the memory stream + CloseOutput = false, + ConformanceLevel = ConformanceLevel.Fragment, + Encoding = System.Text.Encoding.Unicode, + OmitXmlDeclaration = true + }; // // User-visible semantics-laden Getter/Setter support methods @@ -3457,16 +3470,8 @@ private static void SetSqlXml_Unchecked(ITypedSettersV3 setters, int ordinal, Sq private static void SetXmlReader_Unchecked(ITypedSettersV3 setters, int ordinal, XmlReader xmlReader) { // set up writer - XmlWriterSettings WriterSettings = new XmlWriterSettings - { - CloseOutput = false, // don't close the memory stream - ConformanceLevel = ConformanceLevel.Fragment, - Encoding = System.Text.Encoding.Unicode, - OmitXmlDeclaration = true - }; - using (Stream target = new SmiSettersStream(setters, ordinal, SmiMetaData.DefaultXml)) - using (XmlWriter xmlWriter = XmlWriter.Create(target, WriterSettings)) + using (XmlWriter xmlWriter = XmlWriter.Create(target, XmlWriterSettings)) { // now spool the data into the writer (WriteNode will call Read()) xmlReader.Read(); diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SignatureVerificationCache.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SignatureVerificationCache.cs index 40d4ed9b79..8d1ce98df1 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SignatureVerificationCache.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SignatureVerificationCache.cs @@ -14,8 +14,8 @@ namespace Microsoft.Data.SqlClient /// internal class ColumnMasterKeyMetadataSignatureVerificationCache { - private const int _cacheSize = 2000; // Cache size in number of entries. - private const int _cacheTrimThreshold = 300; // Threshold above the cache size when we start trimming. + private const int CacheSize = 2000; // Cache size in number of entries. + private const int CacheTrimThreshold = 300; // Threshold above the cache size when we start trimming. private const string _className = "ColumnMasterKeyMetadataSignatureVerificationCache"; private const string _getSignatureVerificationResultMethodName = "GetSignatureVerificationResult"; @@ -26,6 +26,7 @@ internal class ColumnMasterKeyMetadataSignatureVerificationCache private const string _cacheLookupKeySeparator = ":"; private static readonly ColumnMasterKeyMetadataSignatureVerificationCache _signatureVerificationCache = new ColumnMasterKeyMetadataSignatureVerificationCache(); + private static readonly TimeSpan s_verificationCacheTimeout = TimeSpan.FromDays(10); //singleton instance internal static ColumnMasterKeyMetadataSignatureVerificationCache Instance { get { return _signatureVerificationCache; } } @@ -76,11 +77,7 @@ internal void AddSignatureVerificationResult(string keyStoreName, string masterK TrimCacheIfNeeded(); // By default evict after 10 days. - MemoryCacheEntryOptions options = new MemoryCacheEntryOptions - { - AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(10) - }; - _cache.Set(cacheLookupKey, result, options); + _cache.Set(cacheLookupKey, result, absoluteExpirationRelativeToNow: s_verificationCacheTimeout); } private void ValidateSignatureNotNullOrEmpty(byte[] signature, string methodName) @@ -117,12 +114,12 @@ private void TrimCacheIfNeeded() { // If the size of the cache exceeds the threshold, set that we are in trimming and trim the cache accordingly. long currentCacheSize = _cache.Count; - if ((currentCacheSize > _cacheSize + _cacheTrimThreshold) && (0 == Interlocked.CompareExchange(ref _inTrim, 1, 0))) + if ((currentCacheSize > CacheSize + CacheTrimThreshold) && (0 == Interlocked.CompareExchange(ref _inTrim, 1, 0))) { try { // Example: 2301 - 2000 = 301; 301 / 2301 = 0.1308 * 100 = 13% compacting - _cache.Compact((((double)(currentCacheSize - _cacheSize) / (double)currentCacheSize) * 100)); + _cache.Compact((((double)(currentCacheSize - CacheSize) / (double)currentCacheSize) * 100)); } finally { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlQueryMetadataCache.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlQueryMetadataCache.cs index 0af5ea44e6..a4a35840ac 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlQueryMetadataCache.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlQueryMetadataCache.cs @@ -19,11 +19,12 @@ namespace Microsoft.Data.SqlClient /// sealed internal class SqlQueryMetadataCache { - const int CacheSize = 2000; // Cache size in number of entries. - const int CacheTrimThreshold = 300; // Threshold above the cache size when we start trimming. + private const int CacheSize = 2000; // Cache size in number of entries. + private const int CacheTrimThreshold = 300; // Threshold above the cache size when we start trimming. private readonly MemoryCache _cache; private static readonly SqlQueryMetadataCache s_singletonInstance = new(); + private static readonly TimeSpan s_metadataCacheTimeout = TimeSpan.FromHours(10); private int _inTrim = 0; private long _cacheHits = 0; private long _cacheMisses = 0; @@ -235,16 +236,11 @@ internal void AddQueryMetadata(SqlCommand sqlCommand, bool ignoreQueriesWithRetu } } - // By default evict after 10 hours. - MemoryCacheEntryOptions options = new MemoryCacheEntryOptions - { - AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(10) - }; - _cache.Set>(cacheLookupKey, cipherMetadataDictionary, options); + _cache.Set>(cacheLookupKey, cipherMetadataDictionary, absoluteExpirationRelativeToNow: s_metadataCacheTimeout); if (sqlCommand.requiresEnclaveComputations) { ConcurrentDictionary keysToBeCached = CreateCopyOfEnclaveKeys(sqlCommand.keysToBeSentToEnclave); - _cache.Set>(enclaveLookupKey, keysToBeCached, options); + _cache.Set>(enclaveLookupKey, keysToBeCached, absoluteExpirationRelativeToNow: s_metadataCacheTimeout); } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlStream.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlStream.cs index ca0e90bab8..653faf7213 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlStream.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlStream.cs @@ -477,6 +477,8 @@ private long TotalLength sealed internal class SqlStreamingXml { + private static readonly XmlWriterSettings s_writerSettings = new() { CloseOutput = true, ConformanceLevel = ConformanceLevel.Fragment }; + private readonly int _columnOrdinal; private SqlDataReader _reader; private XmlReader _xmlReader; @@ -509,10 +511,7 @@ public long GetChars(long dataIndex, char[] buffer, int bufferIndex, int length) SqlStream sqlStream = new(_columnOrdinal, _reader, addByteOrderMark: true, processAllRows:false, advanceReader:false); _xmlReader = sqlStream.ToXmlReader(); _strWriter = new StringWriter((System.IFormatProvider)null); - XmlWriterSettings writerSettings = new(); - writerSettings.CloseOutput = true; // close the memory stream when done - writerSettings.ConformanceLevel = ConformanceLevel.Fragment; - _xmlWriter = XmlWriter.Create(_strWriter, writerSettings); + _xmlWriter = XmlWriter.Create(_strWriter, s_writerSettings); } int charsToSkip = 0; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSymmetricKeyCache.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSymmetricKeyCache.cs index 961746fc2d..d88833abed 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSymmetricKeyCache.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSymmetricKeyCache.cs @@ -98,11 +98,7 @@ internal SqlClientSymmetricKey GetKey(SqlEncryptionKeyInfo keyInfo, SqlConnectio { // In case multiple threads reach here at the same time, the first one wins. // The allocated memory will be reclaimed by Garbage Collector. - MemoryCacheEntryOptions options = new() - { - AbsoluteExpirationRelativeToNow = SqlConnection.ColumnEncryptionKeyCacheTtl - }; - _cache.Set(cacheLookupKey, encryptionKey, options); + _cache.Set(cacheLookupKey, encryptionKey, absoluteExpirationRelativeToNow: SqlConnection.ColumnEncryptionKeyCacheTtl); } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs index 115c62f6c5..35610c7844 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -41,6 +41,10 @@ namespace Microsoft.Data.SqlClient internal sealed partial class TdsParser { private static int _objectTypeCount; // EventSource counter + // @TODO: Replace both fields with the `field` keyword when LangVersion >= 14. + private static XmlWriterSettings s_asyncXmlWriterSettings; + private static XmlWriterSettings s_syncXmlWriterSettings; + private readonly SqlClientLogger _logger = new SqlClientLogger(); private SspiContextProvider _authenticationProvider; @@ -154,6 +158,12 @@ internal sealed partial class TdsParser // NOTE: You must take the internal connection's _parserLock before modifying this internal bool _asyncWrite = false; + private static XmlWriterSettings AsyncXmlWriterSettings => + s_asyncXmlWriterSettings ??= new() { CloseOutput = false, ConformanceLevel = ConformanceLevel.Fragment, Async = true }; + + private static XmlWriterSettings SyncXmlWriterSettings => + s_syncXmlWriterSettings ??= new() { CloseOutput = false, ConformanceLevel = ConformanceLevel.Fragment }; + /// /// Get or set if column encryption is supported by the server. /// @@ -12768,13 +12778,7 @@ private async Task WriteXmlFeed(XmlDataFeed feed, TdsParserStateObject stateObj, preambleToSkip = encoding.GetPreamble(); } - XmlWriterSettings writerSettings = new XmlWriterSettings(); - writerSettings.CloseOutput = false; // don't close the memory stream - writerSettings.ConformanceLevel = ConformanceLevel.Fragment; - if (_asyncWrite) - { - writerSettings.Async = true; - } + XmlWriterSettings writerSettings = _asyncWrite ? AsyncXmlWriterSettings : SyncXmlWriterSettings; using ConstrainedTextWriter writer = new ConstrainedTextWriter(new StreamWriter(new TdsOutputStream(this, stateObj, preambleToSkip), encoding), size); using XmlWriter ww = XmlWriter.Create(writer, writerSettings); diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.cs index 198ac9f10f..a3b4545d03 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.cs @@ -17,6 +17,7 @@ internal abstract class VirtualizationBasedSecurityEnclaveProviderBase : Enclave #region Members private static readonly MemoryCache rootSigningCertificateCache = new MemoryCache(new MemoryCacheOptions()); + private static readonly TimeSpan s_rootSigningCertificateCacheTimeout = TimeSpan.FromDays(1); #endregion @@ -211,11 +212,8 @@ private X509Certificate2Collection GetSigningCertificate(string attestationUrl, throw SQL.AttestationFailed(string.Format(Strings.GetAttestationSigningCertificateFailedInvalidCertificate, attestationUrl), exception); } - MemoryCacheEntryOptions options = new MemoryCacheEntryOptions - { - AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(1) - }; - rootSigningCertificateCache.Set(attestationUrl, certificateCollection, options); + rootSigningCertificateCache.Set(attestationUrl, certificateCollection, + absoluteExpirationRelativeToNow: s_rootSigningCertificateCacheTimeout); } return rootSigningCertificateCache.Get(attestationUrl);