// -----------------------------------------
        // Methods for Timer maintenance and firing.
        // -----------------------------------------

        internal void StartTimer(SqlDependency dep)
        {
            IntPtr hscp;

            Bid.NotificationsScopeEnter(out hscp, "<sc.SqlDependencyPerAppDomainDispatcher.StartTimer|DEP> %d#, SqlDependency: %d#", 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)
                    {
                        Bid.NotificationsTrace("<sc.SqlDependencyPerAppDomainDispatcher.StartTimer|DEP> Timer not yet started, starting.\n");

                        _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)
                    {
                        Bid.NotificationsTrace("<sc.SqlDependencyPerAppDomainDispatcher.StartTimer|DEP> Timer already started, resetting time.\n");

                        // Save this as the earlier timeout to come.
                        _nextTimeout = dep.ExpirationTime;
                    }
                }
            }
            finally
            {
                Bid.ScopeLeave(ref hscp);
            }
        }
        // This method is called by SqlCommand to enable ASP.NET scenarios - map from ID to Dependency.
        internal SqlDependency LookupDependencyEntry(string id)
        {
            IntPtr hscp;

            Bid.NotificationsScopeEnter(out hscp, "<sc.SqlDependencyPerAppDomainDispatcher.LookupDependencyEntry|DEP> %d#, Key: '%ls'", 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
                    {
                        Bid.NotificationsTrace("<sc.SqlDependencyPerAppDomainDispatcher.LookupDependencyEntry|DEP|ERR> ERROR - dependency ID mismatch - not throwing.\n");
                    }
                }

                return(entry);
            }
            finally
            {
                Bid.ScopeLeave(ref hscp);
            }
        }
示例#3
0
        // Remove from commandToDependenciesHash all references to the passed dependency.
        private void RemoveDependencyFromCommandToDependenciesHash(SqlDependency dependency)
        {
            lock (_instanceLock)
            {
                List <string> notificationIdsToRemove = new List <string>();
                List <string> commandHashesToRemove   = new List <string>();

                foreach (KeyValuePair <string, DependencyList> entry in _notificationIdToDependenciesHash)
                {
                    DependencyList dependencies = entry.Value;
                    if (dependencies.Remove(dependency))
                    {
                        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
                    _notificationIdToDependenciesHash.Remove(notificationIdsToRemove[i]);
                    // 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!");
            }
        }
示例#4
0
        // 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("<sc.SqlDependencyPerAppDomainDispatcher.LookupDependencyEntry|DEP> {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("<sc.SqlDependencyPerAppDomainDispatcher.LookupDependencyEntry|DEP|ERR> ERROR - dependency ID mismatch - not throwing.");
                    }
                }

                return(entry);
            }
            finally
            {
                SqlClientEventSource.Log.TryNotificationScopeLeaveEvent(scopeID);
            }
        }
示例#5
0
        private static void TimeoutTimerCallback(object state)
        {
            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._instanceLock)
            {
                if (0 == SingletonInstance._dependencyIdToDependencyHash.Count)
                {
                    // Nothing to check.
                    return;
                }
                if (SingletonInstance._nextTimeout > DateTime.UtcNow)
                {
                    // 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._instanceLock)
            {
                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.
                }
            }
        }
示例#6
0
        // 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("<sc.SqlDependencyPerAppDomainDispatcher.AddCommandEntry|DEP> {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("<sc.SqlDependencyPerAppDomainDispatcher.AddCommandEntry|DEP> 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("<sc.SqlDependencyPerAppDomainDispatcher.AddCommandEntry|DEP> Dependency not present for commandHash, adding.");
                                dependencyList.Add(dep);
                            }
                            else
                            {
                                SqlClientEventSource.Log.TryNotificationTraceEvent("<sc.SqlDependencyPerAppDomainDispatcher.AddCommandEntry|DEP> 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("<sc.SqlDependencyPerAppDomainDispatcher.AddCommandEntry|DEP> 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);
        }