diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs deleted file mode 100644 index 6f67764f9a..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs +++ /dev/null @@ -1,185 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using Microsoft.Data.Common; - -namespace Microsoft.Data.SqlClient -{ - internal sealed class TdsParserStaticMethods - { - // Obfuscate password to be sent to SQL Server - // Blurb from the TDS spec at https://msdn.microsoft.com/en-us/library/dd304523.aspx - // "Before submitting a password from the client to the server, for every byte in the password buffer - // starting with the position pointed to by IbPassword, the client SHOULD first swap the four high bits - // with the four low bits and then do a bit-XOR with 0xA5 (10100101). After reading a submitted password, - // for every byte in the password buffer starting with the position pointed to by IbPassword, the server SHOULD - // first do a bit-XOR with 0xA5 (10100101) and then swap the four high bits with the four low bits." - // The password exchange during Login phase happens over a secure channel i.e. SSL/TLS - // Note: The same logic is used in SNIPacketSetData (SniManagedWrapper) to encrypt passwords stored in SecureString - // If this logic changed, SNIPacketSetData needs to be changed as well - internal static byte[] ObfuscatePassword(string password) - { - byte[] bObfuscated = new byte[password.Length << 1]; - int s; - byte bLo; - byte bHi; - - for (int i = 0; i < password.Length; i++) - { - s = (int)password[i]; - bLo = (byte)(s & 0xff); - bHi = (byte)((s >> 8) & 0xff); - bObfuscated[i << 1] = (byte)((((bLo & 0x0f) << 4) | (bLo >> 4)) ^ 0xa5); - bObfuscated[(i << 1) + 1] = (byte)((((bHi & 0x0f) << 4) | (bHi >> 4)) ^ 0xa5); - } - return bObfuscated; - } - - internal static byte[] ObfuscatePassword(byte[] password) - { - byte bLo; - byte bHi; - - for (int i = 0; i < password.Length; i++) - { - bLo = (byte)(password[i] & 0x0f); - bHi = (byte)(password[i] & 0xf0); - password[i] = (byte)(((bHi >> 4) | (bLo << 4)) ^ 0xa5); - } - return password; - } - - private const int NoProcessId = -1; - private static int s_currentProcessId = NoProcessId; - internal static int GetCurrentProcessIdForTdsLoginOnly() - { - if (s_currentProcessId == NoProcessId) - { - // Pick up the process Id from the current process instead of randomly generating it. - // This would be helpful while tracing application related issues. - int processId; - using (System.Diagnostics.Process p = System.Diagnostics.Process.GetCurrentProcess()) - { - processId = p.Id; - } - System.Threading.Volatile.Write(ref s_currentProcessId, processId); - } - return s_currentProcessId; - } - - - internal static int GetCurrentThreadIdForTdsLoginOnly() - { - return Environment.CurrentManagedThreadId; - } - - - private static byte[] s_nicAddress = null; - internal static byte[] GetNetworkPhysicalAddressForTdsLoginOnly() - { - // For ProjectK\CoreCLR we don't want to take a dependency on the registry to try to read a value - // that isn't usually set, so we'll just use a random value each time instead - if (null == s_nicAddress) - { - byte[] newNicAddress = new byte[TdsEnums.MAX_NIC_SIZE]; - Random random = new Random(); - random.NextBytes(newNicAddress); - System.Threading.Interlocked.CompareExchange(ref s_nicAddress, newNicAddress, null); - } - - return s_nicAddress; - } - - // translates remaining time in stateObj (from user specified timeout) to timeout value for SNI - internal static int GetTimeoutMilliseconds(long timeoutTime) - { - // User provided timeout t | timeout value for SNI | meaning - // ------------------------+-----------------------+------------------------------ - // t == long.MaxValue | -1 | infinite timeout (no timeout) - // t>0 && tint.MaxValue | int.MaxValue | must not exceed int.MaxValue - - if (long.MaxValue == timeoutTime) - { - return -1; // infinite timeout - } - - long msecRemaining = ADP.TimerRemainingMilliseconds(timeoutTime); - - if (msecRemaining < 0) - { - return 0; - } - if (msecRemaining > (long)int.MaxValue) - { - return int.MaxValue; - } - return (int)msecRemaining; - } - - internal static long GetTimeout(long timeoutMilliseconds) - { - long result; - if (timeoutMilliseconds <= 0) - { - result = long.MaxValue; // no timeout... - } - else - { - try - { - result = checked(ADP.TimerCurrent() + ADP.TimerFromMilliseconds(timeoutMilliseconds)); - } - catch (OverflowException) - { - // In case of overflow, set to 'infinite' timeout - result = long.MaxValue; - } - } - return result; - } - - internal static bool TimeoutHasExpired(long timeoutTime) - { - bool result = false; - - if (0 != timeoutTime && long.MaxValue != timeoutTime) - { - result = ADP.TimerHasExpired(timeoutTime); - } - return result; - } - - internal static int NullAwareStringLength(string str) - { - if (str == null) - { - return 0; - } - else - { - return str.Length; - } - } - - internal static int GetRemainingTimeout(int timeout, long start) - { - if (timeout <= 0) - { - return timeout; - } - long remaining = ADP.TimerRemainingSeconds(start + ADP.TimerFromSeconds(timeout)); - if (remaining <= 0) - { - return 1; - } - else - { - return checked((int)remaining); - } - } - internal static long GetTimeoutSeconds(int timeout) => GetTimeout((long)timeout * 1000L); - } -} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj index 2a15e3245a..340b274034 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -718,7 +718,6 @@ - diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Sql/SqlGenericUtil.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Sql/SqlGenericUtil.cs deleted file mode 100644 index 3a61e3e311..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Sql/SqlGenericUtil.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -namespace Microsoft.Data.Sql -{ - using System; - using Microsoft.Data; - using Microsoft.Data.Common; - - sealed internal class SqlGenericUtil - { - - private SqlGenericUtil() { /* prevent utility class from being instantiated*/ } - - // - // Sql generic exceptions - // - - // - // Sql.Definition - // - - static internal Exception NullCommandText() - { - return ADP.Argument(StringsHelper.GetString(Strings.Sql_NullCommandText)); - } - static internal Exception MismatchedMetaDataDirectionArrayLengths() - { - return ADP.Argument(StringsHelper.GetString(Strings.Sql_MismatchedMetaDataDirectionArrayLengths)); - } - } - -}//namespace - diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlBulkCopyColumnMapping.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlBulkCopyColumnMapping.cs deleted file mode 100644 index 601fd144ba..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlBulkCopyColumnMapping.cs +++ /dev/null @@ -1,138 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Microsoft.Data.Common; - -namespace Microsoft.Data.SqlClient -{ - // ------------------------------------------------------------------------------------------------- - // this class helps allows the user to create association between source- and targetcolumns - // - // - /// - public sealed class SqlBulkCopyColumnMapping - { - internal string _destinationColumnName; - internal int _destinationColumnOrdinal; - internal string _sourceColumnName; - internal int _sourceColumnOrdinal; - - // devnote: we don't want the user to detect the columnordinal after WriteToServer call. - // _sourceColumnOrdinal(s) will be copied to _internalSourceColumnOrdinal when WriteToServer executes. - internal int _internalDestinationColumnOrdinal; - internal int _internalSourceColumnOrdinal; // -1 indicates an undetermined value - - /// - public string DestinationColumn - { - get - { - if (_destinationColumnName != null) - { - return _destinationColumnName; - } - return string.Empty; - } - set - { - _destinationColumnOrdinal = _internalDestinationColumnOrdinal = -1; - _destinationColumnName = value; - } - } - - /// - public int DestinationOrdinal - { - get - { - return _destinationColumnOrdinal; - } - set - { - if (value >= 0) - { - _destinationColumnName = null; - _destinationColumnOrdinal = _internalDestinationColumnOrdinal = value; - } - else - { - throw ADP.IndexOutOfRange(value); - } - } - } - - /// - public string SourceColumn - { - get - { - if (_sourceColumnName != null) - { - return _sourceColumnName; - } - return string.Empty; - } - set - { - _sourceColumnOrdinal = _internalSourceColumnOrdinal = -1; - _sourceColumnName = value; - } - } - - /// - public int SourceOrdinal - { - get - { - return _sourceColumnOrdinal; - } - set - { - if (value >= 0) - { - _sourceColumnName = null; - _sourceColumnOrdinal = _internalSourceColumnOrdinal = value; - } - else - { - throw ADP.IndexOutOfRange(value); - } - } - } - - /// - public SqlBulkCopyColumnMapping() - { - _internalSourceColumnOrdinal = -1; - } - - /// - public SqlBulkCopyColumnMapping(string sourceColumn, string destinationColumn) - { - SourceColumn = sourceColumn; - DestinationColumn = destinationColumn; - } - - /// - public SqlBulkCopyColumnMapping(int sourceColumnOrdinal, string destinationColumn) - { - SourceOrdinal = sourceColumnOrdinal; - DestinationColumn = destinationColumn; - } - - /// - public SqlBulkCopyColumnMapping(string sourceColumn, int destinationOrdinal) - { - SourceColumn = sourceColumn; - DestinationOrdinal = destinationOrdinal; - } - - /// - public SqlBulkCopyColumnMapping(int sourceColumnOrdinal, int destinationOrdinal) - { - SourceOrdinal = sourceColumnOrdinal; - DestinationOrdinal = destinationOrdinal; - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDependencyUtils.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDependencyUtils.cs deleted file mode 100644 index 6f91683a4d..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDependencyUtils.cs +++ /dev/null @@ -1,709 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Threading; -using Microsoft.Data.Common; - -namespace Microsoft.Data.SqlClient -{ - // This is a singleton instance per AppDomain that acts as the notification dispatcher for - // that AppDomain. It receives calls from the SqlDependencyProcessDispatcher with an ID or a server name - // to invalidate matching dependencies in the given AppDomain. - - internal class SqlDependencyPerAppDomainDispatcher : MarshalByRefObject - { // MBR, since ref'ed by ProcessDispatcher. - - // ---------------- - // Instance members - // ---------------- - - internal static readonly SqlDependencyPerAppDomainDispatcher - SingletonInstance = new SqlDependencyPerAppDomainDispatcher(); // singleton object - - // Dependency ID -> Dependency hashtable. 1 -> 1 mapping. - // 1) Used for ASP.NET to map from ID to dependency. - // 2) Used to enumerate dependencies to invalidate based on server. - private Dictionary _dependencyIdToDependencyHash; - - // holds dependencies list per notification and the command hash from which this notification was generated - // command hash is needed to remove its entry from _commandHashToNotificationId when the notification is removed - sealed class DependencyList : List - { - public readonly string CommandHash; - - internal DependencyList(string commandHash) - { - this.CommandHash = commandHash; - } - } - - // notificationId -> Dependencies hashtable: 1 -> N mapping. notificationId == appDomainKey + commandHash. - // More than one dependency can be using the same command hash values resulting in a hash to the same value. - // We use this to cache mapping between command to dependencies such that we may reduce the notification - // resource effect on SQL Server. The Guid identifier is sent to the server during notification enlistment, - // and returned during the notification event. Dependencies look up existing Guids, if one exists, to ensure - // they are re-using notification ids. - private Dictionary _notificationIdToDependenciesHash; - - // CommandHash value -> notificationId associated with it: 1->1 mapping. This map is used to quickly find if we need to create - // new notification or hookup into existing one. - // CommandHash is built from connection string, command text and parameters - private Dictionary _commandHashToNotificationId; - - // TIMEOUT LOGIC DESCRIPTION - // - // Every time we add a dependency we compute the next, earlier timeout. - // - // We setup a timer to get a callback every 15 seconds. In the call back: - // - If there are no active dependencies, we just return. - // - If there are dependencies but none of them timed-out (compared to the "next timeout"), - // we just return. - // - Otherwise we Invalidate() those that timed-out. - // - // So the client-generated timeouts have a granularity of 15 seconds. This allows - // for a simple and low-resource-consumption implementation. - // - // LOCKS: don't update _nextTimeout outside of the _dependencyHash.SyncRoot lock. - - private bool _SqlDependencyTimeOutTimerStarted = false; - // Next timeout for any of the dependencies in the dependency table. - private DateTime _nextTimeout; - // Timer to periodically check the dependencies in the table and see if anyone needs - // a timeout. We'll enable this only on demand. - private Timer _timeoutTimer; - - // ----------- - // BID members - // ----------- - - private readonly int _objectID = System.Threading.Interlocked.Increment(ref _objectTypeCount); - private static int _objectTypeCount; // EventSource Counter - internal int ObjectID - { - get - { - return _objectID; - } - } - - private SqlDependencyPerAppDomainDispatcher() - { - long scopeID = SqlClientEventSource.Log.TryNotificationScopeEnterEvent(" {0}", ObjectID); - try - { - _dependencyIdToDependencyHash = new Dictionary(); - _notificationIdToDependenciesHash = new Dictionary(); - _commandHashToNotificationId = new Dictionary(); - - _timeoutTimer = new Timer(new TimerCallback(TimeoutTimerCallback), null, Timeout.Infinite, Timeout.Infinite); - - // If rude abort - we'll leak. This is acceptable for now. - AppDomain.CurrentDomain.DomainUnload += new EventHandler(this.UnloadEventHandler); - } - finally - { - SqlClientEventSource.Log.TryNotificationScopeLeaveEvent(scopeID); - } - } - - // SQL Hotfix 236 - // When remoted across appdomains, MarshalByRefObject links by default time out if there is no activity - // within a few minutes. Add this override to prevent marshaled links from timing out. - public override object InitializeLifetimeService() - { - return null; - } - - // ------ - // Events - // ------ - - private void UnloadEventHandler(object sender, EventArgs e) - { - long scopeID = SqlClientEventSource.Log.TryNotificationScopeEnterEvent(" {0}", ObjectID); - try - { - // Make non-blocking call to ProcessDispatcher to ThreadPool.QueueUserWorkItem to complete - // stopping of all start calls in this AppDomain. For containers shared among various AppDomains, - // this will just be a ref-count subtract. For non-shared containers, we will close the container - // and clean-up. - SqlDependencyProcessDispatcher dispatcher = SqlDependency.ProcessDispatcher; - if (null != dispatcher) - { - dispatcher.QueueAppDomainUnloading(SqlDependency.AppDomainKey); - } - } - finally - { - SqlClientEventSource.Log.TryNotificationScopeLeaveEvent(scopeID); - } - } - - // ---------------------------------------------------- - // Methods for dependency hash manipulation and firing. - // ---------------------------------------------------- - - // This method is called upon SqlDependency constructor. - internal void AddDependencyEntry(SqlDependency dep) - { - long scopeID = SqlClientEventSource.Log.TryNotificationScopeEnterEvent(" {0}, SqlDependency: {1}", ObjectID, dep.ObjectID); - try - { - lock (this) - { - _dependencyIdToDependencyHash.Add(dep.Id, dep); - } - } - finally - { - SqlClientEventSource.Log.TryNotificationScopeLeaveEvent(scopeID); - } - } - - // This method is called upon Execute of a command associated with a SqlDependency object. - internal string AddCommandEntry(string commandHash, SqlDependency dep) - { - string notificationId = string.Empty; - long scopeID = SqlClientEventSource.Log.TryNotificationScopeEnterEvent(" {0}, commandHash: '{1}', SqlDependency: {2}", ObjectID, commandHash, dep.ObjectID); - try - { - lock (this) - { - if (!_dependencyIdToDependencyHash.ContainsKey(dep.Id)) - { - // Determine if depId->dep hashtable contains dependency. If not, it's been invalidated. - SqlClientEventSource.Log.TryNotificationTraceEvent(" Dependency not present in depId->dep hash, must have been invalidated."); - } - else - { - // check if we already have notification associated with given command hash - if (_commandHashToNotificationId.TryGetValue(commandHash, out notificationId)) - { - // we have one or more SqlDependency instances with same command hash - - DependencyList dependencyList = null; - if (!_notificationIdToDependenciesHash.TryGetValue(notificationId, out dependencyList)) - { - // this should not happen since _commandHashToNotificationId and _notificationIdToDependenciesHash are always - // updated together - Debug.Assert(false, "_commandHashToNotificationId has entries that were removed from _notificationIdToDependenciesHash. Remember to keep them in sync"); - throw ADP.InternalError(ADP.InternalErrorCode.SqlDependencyCommandHashIsNotAssociatedWithNotification); - } - - // join the new dependency to the list - if (!dependencyList.Contains(dep)) - { - SqlClientEventSource.Log.TryNotificationTraceEvent(" Dependency not present for commandHash, adding."); - dependencyList.Add(dep); - } - else - { - SqlClientEventSource.Log.TryNotificationTraceEvent(" Dependency already present for commandHash."); - } - } - else - { - // we did not find notification ID with the same app domain and command hash, create a new one - // use unique guid to avoid duplicate IDs - // prepend app domain ID to the key - SqlConnectionContainer::ProcessNotificationResults (SqlDependencyListener.cs) - // uses this app domain ID to route the message back to the app domain in which this SqlDependency was created - notificationId = string.Format(System.Globalization.CultureInfo.InvariantCulture, - "{0};{1}", - SqlDependency.AppDomainKey, // must be first - Guid.NewGuid().ToString("D", System.Globalization.CultureInfo.InvariantCulture) - ); - SqlClientEventSource.Log.TryNotificationTraceEvent(" Creating new Dependencies list for commandHash."); - DependencyList dependencyList = new DependencyList(commandHash); - dependencyList.Add(dep); - - // map command hash to notification we just created to reuse it for the next client - // do it inside finally block to avoid ThreadAbort exception interrupt this operation - try - { } - finally - { - _commandHashToNotificationId.Add(commandHash, notificationId); - _notificationIdToDependenciesHash.Add(notificationId, dependencyList); - } - } - - - Debug.Assert(_notificationIdToDependenciesHash.Count == _commandHashToNotificationId.Count, "always keep these maps in sync!"); - } - } - } - finally - { - SqlClientEventSource.Log.TryNotificationScopeLeaveEvent(scopeID); - } - - return notificationId; - } - - // This method is called by the ProcessDispatcher upon a notification for this AppDomain. - internal void InvalidateCommandID(SqlNotification sqlNotification) - { - long scopeID = SqlClientEventSource.Log.TryNotificationScopeEnterEvent(" {0}, commandHash: '{1}'", ObjectID, sqlNotification.Key); - try - { - List dependencyList = null; - - lock (this) - { - dependencyList = LookupCommandEntryWithRemove(sqlNotification.Key); - - if (null != dependencyList) - { - SqlClientEventSource.Log.TryNotificationTraceEvent(" commandHash found in hashtable."); - foreach (SqlDependency dependency in dependencyList) - { - // Ensure we remove from process static app domain hash for dependency initiated invalidates. - LookupDependencyEntryWithRemove(dependency.Id); - - // Completely remove Dependency from commandToDependenciesHash. - RemoveDependencyFromCommandToDependenciesHash(dependency); - } - } - else - { - SqlClientEventSource.Log.TryNotificationTraceEvent(" commandHash NOT found in hashtable."); - } - } - - if (null != dependencyList) - { - // After removal from hashtables, invalidate. - foreach (SqlDependency dependency in dependencyList) - { - SqlClientEventSource.Log.TryNotificationTraceEvent(" Dependency found in commandHash dependency ArrayList - calling invalidate."); - try - { - dependency.Invalidate(sqlNotification.Type, sqlNotification.Info, sqlNotification.Source); - } - catch (Exception e) - { - // Since we are looping over dependencies, do not allow one Invalidate - // that results in a throw prevent us from invalidating all dependencies - // related to this server. - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } - ADP.TraceExceptionWithoutRethrow(e); - } - } - } - } - finally - { - SqlClientEventSource.Log.TryNotificationScopeLeaveEvent(scopeID); - } - } - - // This method is called when a connection goes down or other unknown error occurs in the ProcessDispatcher. - internal void InvalidateServer(string server, SqlNotification sqlNotification) - { - long scopeID = SqlClientEventSource.Log.TryNotificationScopeEnterEvent(" {0}, server: '{1}'", ObjectID, server); - try - { - List dependencies = new List(); - - lock (this) - { // Copy inside of lock, but invalidate outside of lock. - foreach (KeyValuePair entry in _dependencyIdToDependencyHash) - { - SqlDependency dependency = entry.Value; - if (dependency.ContainsServer(server)) - { - dependencies.Add(dependency); - } - } - - foreach (SqlDependency dependency in dependencies) - { // Iterate over resulting list removing from our hashes. - // Ensure we remove from process static app domain hash for dependency initiated invalidates. - LookupDependencyEntryWithRemove(dependency.Id); - - // Completely remove Dependency from commandToDependenciesHash. - RemoveDependencyFromCommandToDependenciesHash(dependency); - } - } - - foreach (SqlDependency dependency in dependencies) - { // Iterate and invalidate. - try - { - dependency.Invalidate(sqlNotification.Type, sqlNotification.Info, sqlNotification.Source); - } - catch (Exception e) - { - // Since we are looping over dependencies, do not allow one Invalidate - // that results in a throw prevent us from invalidating all dependencies - // related to this server. - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } - ADP.TraceExceptionWithoutRethrow(e); - } - } - } - finally - { - SqlClientEventSource.Log.TryNotificationScopeLeaveEvent(scopeID); - } - } - - // This method is called by SqlCommand to enable ASP.NET scenarios - map from ID to Dependency. - internal SqlDependency LookupDependencyEntry(string id) - { - long scopeID = SqlClientEventSource.Log.TryNotificationScopeEnterEvent(" {0}, Key: '{1}'", ObjectID, id); - try - { - if (null == id) - { - throw ADP.ArgumentNull("id"); - } - if (ADP.IsEmpty(id)) - { - throw SQL.SqlDependencyIdMismatch(); - } - - SqlDependency entry = null; - - lock (this) - { - if (_dependencyIdToDependencyHash.ContainsKey(id)) - { - entry = _dependencyIdToDependencyHash[id]; - } - else - { - SqlClientEventSource.Log.TryNotificationTraceEvent(" ERROR - dependency ID mismatch - not throwing."); - } - } - - return entry; - } - finally - { - SqlClientEventSource.Log.TryNotificationScopeLeaveEvent(scopeID); - } - } - - // Remove the dependency from the hashtable with the passed id. - private void LookupDependencyEntryWithRemove(string id) - { - long scopeID = SqlClientEventSource.Log.TryNotificationScopeEnterEvent(" {0}, id: '{1}'", ObjectID, id); - try - { - lock (this) - { - if (_dependencyIdToDependencyHash.ContainsKey(id)) - { - SqlClientEventSource.Log.TryNotificationTraceEvent(" Entry found in hashtable - removing."); - _dependencyIdToDependencyHash.Remove(id); - - // if there are no more dependencies then we can dispose the timer. - if (0 == _dependencyIdToDependencyHash.Count) - { - _timeoutTimer.Change(Timeout.Infinite, Timeout.Infinite); - _SqlDependencyTimeOutTimerStarted = false; - } - } - else - { - SqlClientEventSource.Log.TryNotificationTraceEvent(" Entry NOT found in hashtable."); - } - } - } - finally - { - SqlClientEventSource.Log.TryNotificationScopeLeaveEvent(scopeID); - } - } - - // Find and return arraylist, and remove passed hash value. - private List LookupCommandEntryWithRemove(string notificationId) - { - long scopeID = SqlClientEventSource.Log.TryNotificationScopeEnterEvent(" {0}, commandHash: '{1}'", ObjectID, notificationId); - try - { - DependencyList entry = null; - - lock (this) - { - if (_notificationIdToDependenciesHash.TryGetValue(notificationId, out entry)) - { - SqlClientEventSource.Log.TryNotificationTraceEvent(" Entries found in hashtable - removing."); - - // update the tables - do it inside finally block to avoid ThreadAbort exception interrupt this operation - try - { } - finally - { - _notificationIdToDependenciesHash.Remove(notificationId); - // VSTS 216991: cleanup the map between the command hash and associated notification ID - _commandHashToNotificationId.Remove(entry.CommandHash); - } - } - else - { - SqlClientEventSource.Log.TryNotificationTraceEvent(" Entries NOT found in hashtable."); - } - - Debug.Assert(_notificationIdToDependenciesHash.Count == _commandHashToNotificationId.Count, "always keep these maps in sync!"); - } - - return entry; // DependencyList inherits from List - } - finally - { - SqlClientEventSource.Log.TryNotificationScopeLeaveEvent(scopeID); - } - } - - // Remove from commandToDependenciesHash all references to the passed dependency. - private void RemoveDependencyFromCommandToDependenciesHash(SqlDependency dependency) - { - long scopeID = SqlClientEventSource.Log.TryNotificationScopeEnterEvent(" {0}, SqlDependency: {1}", ObjectID, dependency.ObjectID); - try - { - lock (this) - { - List notificationIdsToRemove = new List(); - List commandHashesToRemove = new List(); - - foreach (KeyValuePair entry in _notificationIdToDependenciesHash) - { - DependencyList dependencies = entry.Value; - if (dependencies.Remove(dependency)) - { - SqlClientEventSource.Log.TryNotificationTraceEvent(" Removed SqlDependency: {0}, with ID: '{1}'.", dependency.ObjectID, dependency.Id); - if (dependencies.Count == 0) - { - // this dependency was the last associated with this notification ID, remove the entry - // note: cannot do it inside foreach over dictionary - notificationIdsToRemove.Add(entry.Key); - commandHashesToRemove.Add(entry.Value.CommandHash); - } - } - - // same SqlDependency can be associated with more than one command, so we have to continue till the end... - } - - Debug.Assert(commandHashesToRemove.Count == notificationIdsToRemove.Count, "maps should be kept in sync"); - for (int i = 0; i < notificationIdsToRemove.Count; i++) - { - // cleanup the entry outside of foreach - // do it inside finally block to avoid ThreadAbort exception interrupt this operation - try - { } - finally - { - _notificationIdToDependenciesHash.Remove(notificationIdsToRemove[i]); - // VSTS 216991: cleanup the map between the command hash and associated notification ID - _commandHashToNotificationId.Remove(commandHashesToRemove[i]); - } - } - - Debug.Assert(_notificationIdToDependenciesHash.Count == _commandHashToNotificationId.Count, "always keep these maps in sync!"); - } - } - finally - { - SqlClientEventSource.Log.TryNotificationScopeLeaveEvent(scopeID); - } - } - - // ----------------------------------------- - // Methods for Timer maintenance and firing. - // ----------------------------------------- - - internal void StartTimer(SqlDependency dep) - { - long scopeID = SqlClientEventSource.Log.TryNotificationScopeEnterEvent(" {0}, SqlDependency: {1}", ObjectID, dep.ObjectID); - try - { - // If this dependency expires sooner than the current next timeout, change - // the timeout and enable timer callback as needed. Note that we change _nextTimeout - // only inside the hashtable syncroot. - lock (this) - { - // Enable the timer if needed (disable when empty, enable on the first addition). - if (!_SqlDependencyTimeOutTimerStarted) - { - SqlClientEventSource.Log.TryNotificationTraceEvent(" Timer not yet started, starting."); - _timeoutTimer.Change(15000 /* 15 secs */, 15000 /* 15 secs */); - - // Save this as the earlier timeout to come. - _nextTimeout = dep.ExpirationTime; - _SqlDependencyTimeOutTimerStarted = true; - } - else if (_nextTimeout > dep.ExpirationTime) - { - SqlClientEventSource.Log.TryNotificationTraceEvent(" Timer already started, resetting time."); - - // Save this as the earlier timeout to come. - _nextTimeout = dep.ExpirationTime; - } - } - } - finally - { - SqlClientEventSource.Log.TryNotificationScopeLeaveEvent(scopeID); - } - } - - private static void TimeoutTimerCallback(object state) - { - long scopeID = SqlClientEventSource.Log.TryNotificationScopeEnterEvent(" AppDomainKey: '{0}'", SqlDependency.AppDomainKey); - try - { - SqlDependency[] dependencies; - - // Only take the lock for checking whether there is work to do - // if we do have work, we'll copy the hashtable and scan it after releasing - // the lock. - lock (SingletonInstance) - { - if (0 == SingletonInstance._dependencyIdToDependencyHash.Count) - { - // Nothing to check. - SqlClientEventSource.Log.TryNotificationTraceEvent(" No dependencies, exiting."); - return; - } - if (SingletonInstance._nextTimeout > DateTime.UtcNow) - { - SqlClientEventSource.Log.TryNotificationTraceEvent(" No timeouts expired, exiting."); - - // No dependency timed-out yet. - return; - } - - // If at least one dependency timed-out do a scan of the table. - // NOTE: we could keep a shadow table sorted by expiration time, but - // given the number of typical simultaneously alive dependencies it's - // probably not worth the optimization. - dependencies = new SqlDependency[SingletonInstance._dependencyIdToDependencyHash.Count]; - SingletonInstance._dependencyIdToDependencyHash.Values.CopyTo(dependencies, 0); - } - - // Scan the active dependencies if needed. - DateTime now = DateTime.UtcNow; - DateTime newNextTimeout = DateTime.MaxValue; - - for (int i = 0; i < dependencies.Length; i++) - { - // If expired fire the change notification. - if (dependencies[i].ExpirationTime <= now) - { - try - { - // This invokes user-code which may throw exceptions. - // NOTE: this is intentionally outside of the lock, we don't want - // to invoke user-code while holding an internal lock. - dependencies[i].Invalidate(SqlNotificationType.Change, SqlNotificationInfo.Error, SqlNotificationSource.Timeout); - } - catch (Exception e) - { - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } - - // This is an exception in user code, and we're in a thread-pool thread - // without user's code up in the stack, no much we can do other than - // eating the exception. - ADP.TraceExceptionWithoutRethrow(e); - } - } - else - { - if (dependencies[i].ExpirationTime < newNextTimeout) - { - newNextTimeout = dependencies[i].ExpirationTime; // Track the next earlier timeout. - } - dependencies[i] = null; // Null means "don't remove it from the hashtable" in the loop below. - } - } - - // Remove timed-out dependencies from the hashtable. - lock (SingletonInstance) - { - for (int i = 0; i < dependencies.Length; i++) - { - if (null != dependencies[i]) - { - SingletonInstance._dependencyIdToDependencyHash.Remove(dependencies[i].Id); - } - } - if (newNextTimeout < SingletonInstance._nextTimeout) - { - SingletonInstance._nextTimeout = newNextTimeout; // We're inside the lock so ok to update. - } - } - } - finally - { - SqlClientEventSource.Log.TryNotificationScopeLeaveEvent(scopeID); - } - } - } - - // Simple class used to encapsulate all data in a notification. - internal class SqlNotification : MarshalByRefObject - { - // This class could be Serializable rather than MBR... - - private readonly SqlNotificationInfo _info; - private readonly SqlNotificationSource _source; - private readonly SqlNotificationType _type; - private readonly string _key; - - internal SqlNotification(SqlNotificationInfo info, SqlNotificationSource source, SqlNotificationType type, string key) - { - _info = info; - _source = source; - _type = type; - _key = key; - } - - internal SqlNotificationInfo Info - { - get - { - return _info; - } - } - - internal string Key - { - get - { - return _key; - } - } - - internal SqlNotificationSource Source - { - get - { - return _source; - } - } - - internal SqlNotificationType Type - { - get - { - return _type; - } - } - } -} -