2016-08-03 10:59:49 +00:00
//------------------------------------------------------------------------------
// <copyright file="SqlDependencyUtils.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
2017-08-21 15:34:15 +00:00
// <owner current="true" primary="true">Microsoft</owner>
// <owner current="true" primary="true">Microsoft</owner>
// <owner current="false" primary="false">Microsoft</owner>
2016-08-03 10:59:49 +00:00
//------------------------------------------------------------------------------
namespace System.Data.SqlClient {
using System ;
using System.Collections ;
using System.Collections.Generic ;
using System.Data.Common ;
using System.Diagnostics ;
using System.Security.Principal ;
using System.Security.AccessControl ;
using System.Text ;
using System.Threading ;
// 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 < string , SqlDependency > _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 < SqlDependency > {
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 < string , DependencyList > _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 < string , string > _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 ; // Bid counter
internal int ObjectID {
get {
return _objectID ;
}
}
private SqlDependencyPerAppDomainDispatcher ( ) {
IntPtr hscp ;
Bid . NotificationsScopeEnter ( out hscp , "<sc.SqlDependencyPerAppDomainDispatcher|DEP> %d#" , ObjectID ) ;
try {
_dependencyIdToDependencyHash = new Dictionary < string , SqlDependency > ( ) ;
_notificationIdToDependenciesHash = new Dictionary < string , DependencyList > ( ) ;
_commandHashToNotificationId = new Dictionary < string , string > ( ) ;
_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 {
Bid . ScopeLeave ( ref hscp ) ;
}
}
// 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 ) {
IntPtr hscp ;
Bid . NotificationsScopeEnter ( out hscp , "<sc.SqlDependencyPerAppDomainDispatcher.UnloadEventHandler|DEP> %d#" , 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 {
Bid . ScopeLeave ( ref hscp ) ;
}
}
// ----------------------------------------------------
// Methods for dependency hash manipulation and firing.
// ----------------------------------------------------
// This method is called upon SqlDependency constructor.
internal void AddDependencyEntry ( SqlDependency dep ) {
IntPtr hscp ;
Bid . NotificationsScopeEnter ( out hscp , "<sc.SqlDependencyPerAppDomainDispatcher.AddDependencyEntry|DEP> %d#, SqlDependency: %d#" , ObjectID , dep . ObjectID ) ;
try {
lock ( this ) {
_dependencyIdToDependencyHash . Add ( dep . Id , dep ) ;
}
}
finally {
Bid . ScopeLeave ( ref hscp ) ;
}
}
// This method is called upon Execute of a command associated with a SqlDependency object.
internal string AddCommandEntry ( string commandHash , SqlDependency dep ) {
IntPtr hscp ;
string notificationId = string . Empty ;
Bid . NotificationsScopeEnter ( out hscp , "<sc.SqlDependencyPerAppDomainDispatcher.AddCommandEntry|DEP> %d#, commandHash: '%ls', SqlDependency: %d#" , 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.
Bid . NotificationsTrace ( "<sc.SqlDependencyPerAppDomainDispatcher.AddCommandEntry|DEP> Dependency not present in depId->dep hash, must have been invalidated.\n" ) ;
}
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
2017-08-21 15:34:15 +00:00
Debug . Assert ( false , "_commandHashToNotificationId has entries that were removed from _notificationIdToDependenciesHash. Remember to keep them in sync" ) ;
2016-08-03 10:59:49 +00:00
throw ADP . InternalError ( ADP . InternalErrorCode . SqlDependencyCommandHashIsNotAssociatedWithNotification ) ;
}
// join the new dependency to the list
if ( ! dependencyList . Contains ( dep ) ) {
Bid . NotificationsTrace ( "<sc.SqlDependencyPerAppDomainDispatcher.AddCommandEntry|DEP> Dependency not present for commandHash, adding.\n" ) ;
dependencyList . Add ( dep ) ;
}
else {
Bid . NotificationsTrace ( "<sc.SqlDependencyPerAppDomainDispatcher.AddCommandEntry|DEP> Dependency already present for commandHash.\n" ) ;
}
}
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 )
) ;
Bid . NotificationsTrace ( "<sc.SqlDependencyPerAppDomainDispatcher.AddCommandEntry|DEP> Creating new Dependencies list for commandHash.\n" ) ;
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 ) ;
}
}
2017-08-21 15:34:15 +00:00
Debug . Assert ( _notificationIdToDependenciesHash . Count = = _commandHashToNotificationId . Count , "always keep these maps in sync!" ) ;
2016-08-03 10:59:49 +00:00
}
}
}
finally {
Bid . ScopeLeave ( ref hscp ) ;
}
return notificationId ;
}
// This method is called by the ProcessDispatcher upon a notification for this AppDomain.
internal void InvalidateCommandID ( SqlNotification sqlNotification ) {
IntPtr hscp ;
Bid . NotificationsScopeEnter ( out hscp , "<sc.SqlDependencyPerAppDomainDispatcher.InvalidateCommandID|DEP> %d#, commandHash: '%ls'" , ObjectID , sqlNotification . Key ) ;
try {
List < SqlDependency > dependencyList = null ;
lock ( this ) {
dependencyList = LookupCommandEntryWithRemove ( sqlNotification . Key ) ;
if ( null ! = dependencyList ) {
Bid . NotificationsTrace ( "<sc.SqlDependencyPerAppDomainDispatcher.InvalidateCommandID|DEP> commandHash found in hashtable.\n" ) ;
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 {
Bid . NotificationsTrace ( "<sc.SqlDependencyPerAppDomainDispatcher.InvalidateCommandID|DEP> commandHash NOT found in hashtable.\n" ) ;
}
}
if ( null ! = dependencyList ) {
// After removal from hashtables, invalidate.
foreach ( SqlDependency dependency in dependencyList ) {
Bid . NotificationsTrace ( "<sc.SqlDependencyPerAppDomainDispatcher.InvalidateCommandID|DEP> Dependency found in commandHash dependency ArrayList - calling invalidate.\n" ) ;
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 {
Bid . ScopeLeave ( ref hscp ) ;
}
}
// This method is called when a connection goes down or other unknown error occurs in the ProcessDispatcher.
internal void InvalidateServer ( string server , SqlNotification sqlNotification ) {
IntPtr hscp ;
Bid . NotificationsScopeEnter ( out hscp , "<sc.SqlDependencyPerAppDomainDispatcher.Invalidate|DEP> %d#, server: '%ls'" , ObjectID , server ) ;
try {
List < SqlDependency > dependencies = new List < SqlDependency > ( ) ;
lock ( this ) { // Copy inside of lock, but invalidate outside of lock.
foreach ( KeyValuePair < string , SqlDependency > 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 {
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 ) ;
}
}
// Remove the dependency from the hashtable with the passed id.
private void LookupDependencyEntryWithRemove ( string id ) {
IntPtr hscp ;
Bid . NotificationsScopeEnter ( out hscp , "<sc.SqlDependencyPerAppDomainDispatcher.LookupDependencyEntryWithRemove|DEP> %d#, id: '%ls'" , ObjectID , id ) ;
try {
lock ( this ) {
if ( _dependencyIdToDependencyHash . ContainsKey ( id ) ) {
Bid . NotificationsTrace ( "<sc.SqlDependencyPerAppDomainDispatcher.LookupDependencyEntryWithRemove|DEP> Entry found in hashtable - removing.\n" ) ;
_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 {
Bid . NotificationsTrace ( "<sc.SqlDependencyPerAppDomainDispatcher.LookupDependencyEntryWithRemove|DEP> Entry NOT found in hashtable.\n" ) ;
}
}
}
finally {
Bid . ScopeLeave ( ref hscp ) ;
}
}
// Find and return arraylist, and remove passed hash value.
private List < SqlDependency > LookupCommandEntryWithRemove ( string notificationId ) {
IntPtr hscp ;
Bid . NotificationsScopeEnter ( out hscp , "<sc.SqlDependencyPerAppDomainDispatcher.LookupCommandEntryWithRemove|DEP> %d#, commandHash: '%ls'" , ObjectID , notificationId ) ;
try {
DependencyList entry = null ;
lock ( this ) {
if ( _notificationIdToDependenciesHash . TryGetValue ( notificationId , out entry ) ) {
Bid . NotificationsTrace ( "<sc.SqlDependencyPerAppDomainDispatcher.LookupDependencyEntriesWithRemove|DEP> Entries found in hashtable - removing.\n" ) ;
// 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 {
Bid . NotificationsTrace ( "<sc.SqlDependencyPerAppDomainDispatcher.LookupDependencyEntriesWithRemove|DEP> Entries NOT found in hashtable.\n" ) ;
}
2017-08-21 15:34:15 +00:00
Debug . Assert ( _notificationIdToDependenciesHash . Count = = _commandHashToNotificationId . Count , "always keep these maps in sync!" ) ;
2016-08-03 10:59:49 +00:00
}
return entry ; // DependencyList inherits from List<SqlDependency>
}
finally {
Bid . ScopeLeave ( ref hscp ) ;
}
}
// Remove from commandToDependenciesHash all references to the passed dependency.
private void RemoveDependencyFromCommandToDependenciesHash ( SqlDependency dependency ) {
IntPtr hscp ;
Bid . NotificationsScopeEnter ( out hscp , "<sc.SqlDependencyPerAppDomainDispatcher.RemoveDependencyFromCommandToDependenciesHash|DEP> %d#, SqlDependency: %d#" , ObjectID , dependency . ObjectID ) ;
try {
lock ( this ) {
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 ) ) {
Bid . NotificationsTrace ( "<sc.SqlDependencyPerAppDomainDispatcher.RemoveDependencyFromCommandToDependenciesHash|DEP> Removed SqlDependency: %d#, with ID: '%ls'.\n" , 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...
}
2017-08-21 15:34:15 +00:00
Debug . Assert ( commandHashesToRemove . Count = = notificationIdsToRemove . Count , "maps should be kept in sync" ) ;
2016-08-03 10:59:49 +00:00
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 ] ) ;
}
}
2017-08-21 15:34:15 +00:00
Debug . Assert ( _notificationIdToDependenciesHash . Count = = _commandHashToNotificationId . Count , "always keep these maps in sync!" ) ;
2016-08-03 10:59:49 +00:00
}
}
finally {
Bid . ScopeLeave ( ref hscp ) ;
}
}
// -----------------------------------------
// 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 ) ;
}
}
private static void TimeoutTimerCallback ( object state ) {
IntPtr hscp ;
Bid . NotificationsScopeEnter ( out hscp , "<sc.SqlDependencyPerAppDomainDispatcher.TimeoutTimerCallback|DEP> AppDomainKey: '%ls'" , 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.
Bid . NotificationsTrace ( "<sc.SqlDependencyPerAppDomainDispatcher.TimeoutTimerCallback|DEP> No dependencies, exiting.\n" ) ;
return ;
}
if ( SingletonInstance . _nextTimeout > DateTime . UtcNow ) {
Bid . NotificationsTrace ( "<sc.SqlDependencyPerAppDomainDispatcher.TimeoutTimerCallback|DEP> No timeouts expired, exiting.\n" ) ;
// 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 {
Bid . ScopeLeave ( ref hscp ) ;
}
}
}
// 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 ;
}
}
}
}