async Task <T> ISqlCommandExecutor.ExecuteQueryAsync <T>( IActivityMonitor monitor, SqlConnection connection, SqlTransaction transaction, SqlCommand cmd, Func <SqlCommand, CancellationToken, Task <T> > innerExecutor, CancellationToken cancellationToken) { Debug.Assert(connection != null && connection.State == System.Data.ConnectionState.Open); DateTime start = DateTime.UtcNow; int retryCount = 0; List <SqlDetailedException> previous = null; T result; for (; ;) { SqlDetailedException e = null; try { cmd.Connection = connection; cmd.Transaction = transaction; OnCommandExecuting(cmd, retryCount); result = await innerExecutor(cmd, cancellationToken).ConfigureAwait(false); break; } catch (IOException ex) { e = SqlDetailedException.Create(cmd, ex, retryCount++); } catch (SqlException ex) { e = SqlDetailedException.Create(cmd, ex, retryCount++); } catch (Exception ex) { Monitor.Fatal(ex); throw; } Debug.Assert(e != null); Monitor.Error(e); if (previous == null) { previous = new List <SqlDetailedException>(); } TimeSpan retry = OnCommandError(cmd, connection, e, previous, start); if (retry.Ticks < 0 || retry == TimeSpan.MaxValue || previous.Count > 1000) { throw e; } previous.Add(e); await Task.Delay(retry).ConfigureAwait(false); } OnCommandExecuted(cmd, retryCount, result); return(result); }
/// <summary> /// Extension point called after a command failed. /// At this level, this method does nothing and returns <see cref="TimeSpan.MaxValue"/>: no retry will be done. /// <para> /// Note that any negative TimeSpan as well as TimeSpan.MaxValue will result in /// the <see cref="SqlDetailedException"/> being thrown. /// </para> /// </summary> /// <param name="cmd">The executing command.</param> /// <param name="c">The connection.</param> /// <param name="ex">The exception caught and wrapped in a <see cref="SqlDetailedException"/>.</param> /// <param name="previous">Previous errors when retries have been made. Empty on the first error.</param> /// <param name="firstExecutionTimeUtc">The Utc time of the first try.</param> /// <returns>The time span to retry. A negative time span or <see cref="TimeSpan.MaxValue"/> to skip retry.</returns> protected virtual TimeSpan OnCommandError( SqlCommand cmd, SqlConnection c, SqlDetailedException ex, IReadOnlyList <SqlDetailedException> previous, DateTime firstExecutionTimeUtc) => TimeSpan.MaxValue;