public void ExecuteReaderWithUpdates(Action<IDataRecord, DbOperation> processRecord)
{
lock (_stopLocker)
{
if (_disposing)
{
return;
}
_stopHandle.Reset();
}
var useNotifications = _dbBehavior.StartSqlDependencyListener();
var delays = _dbBehavior.UpdateLoopRetryDelays;
for (var i = 0; i < delays.Count; i++)
{
if (i == 0 && useNotifications)
{
// Reset the state to ProcessingUpdates if this is the start of the loop.
// This should be safe to do here without Interlocked because the state is protected
// in the other two cases using Interlocked, i.e. there should only be one instance of
// this running at any point in time.
_notificationState = NotificationState.ProcessingUpdates;
}
Tuple<int, int> retry = delays[i];
var retryDelay = retry.Item1;
var retryCount = retry.Item2;
for (var j = 0; j < retryCount; j++)
{
if (_disposing)
{
Stop(null);
return;
}
if (retryDelay > 0)
{
Trace.TraceVerbose("{0}Waiting {1}ms before checking for messages again", TracePrefix, retryDelay);
Thread.Sleep(retryDelay);
}
var recordCount = 0;
try
{
recordCount = ExecuteReader(processRecord);
Queried();
}
catch (Exception ex)
{
Trace.TraceError("{0}Error in SQL receive loop: {1}", TracePrefix, ex);
Faulted(ex);
}
if (recordCount > 0)
{
Trace.TraceVerbose("{0}{1} records received", TracePrefix, recordCount);
// We got records so start the retry loop again
i = -1;
break;
}
Trace.TraceVerbose("{0}No records received", TracePrefix);
var isLastRetry = i == delays.Count - 1 && j == retryCount - 1;
if (isLastRetry)
{
// Last retry loop iteration
if (!useNotifications)
{
// Last retry loop and we're not using notifications so just stay looping on the last retry delay
j = j - 1;
}
else
{
// No records after all retries, set up a SQL notification
try
{
Trace.TraceVerbose("{0}Setting up SQL notification", TracePrefix);
recordCount = ExecuteReader(processRecord, command =>
{
_dbBehavior.AddSqlDependency(command, e => SqlDependency_OnChange(e, processRecord));
});
Queried();
if (recordCount > 0)
{
Trace.TraceVerbose("{0}Records were returned by the command that sets up the SQL notification, restarting the receive loop", TracePrefix);
i = -1;
break; // break the inner for loop
}
else
{
var previousState = Interlocked.CompareExchange(ref _notificationState, NotificationState.AwaitingNotification,
NotificationState.ProcessingUpdates);
if (previousState == NotificationState.AwaitingNotification)
{
Trace.TraceError("{0}A SQL notification was already running. Overlapping receive loops detected, this should never happen. BUG!", TracePrefix);
return;
}
if (previousState == NotificationState.NotificationReceived)
{
// Failed to change _notificationState from ProcessingUpdates to AwaitingNotification, it was already NotificationReceived
Trace.TraceVerbose("{0}The SQL notification fired before the receive loop returned, restarting the receive loop", TracePrefix);
i = -1;
break; // break the inner for loop
}
}
Trace.TraceVerbose("{0}No records received while setting up SQL notification", TracePrefix);
// We're in a wait state for a notification now so check if we're disposing
lock (_stopLocker)
{
if (_disposing)
{
_stopHandle.Set();
}
}
}
catch (Exception ex)
{
Trace.TraceError("{0}Error in SQL receive loop: {1}", TracePrefix, ex);
Faulted(ex);
// Re-enter the loop on the last retry delay
j = j - 1;
if (retryDelay > 0)
{
Trace.TraceVerbose("{0}Waiting {1}ms before checking for messages again", TracePrefix, retryDelay);
Thread.Sleep(retryDelay);
}
}
}
}
}
}
Trace.TraceVerbose("{0}Receive loop exiting", TracePrefix);
}