/// <summary> /// Executes the provided retry callback. In case the callback is successful, the returned value of type /// R will be returned. In case of failure (the retry callback throws an exception), the operation will be /// retried while the specified retry policy allows it. /// /// If some back-off policy is defined, the amount of time configured in the policy will be waited before /// retrying the callback execution. /// /// When the callback fails and the retry policy does not allow more retries for some exception, a /// RetryExhaustedException will be thrown (containing a reference to the exception that caused the /// failure in the retry callback). /// </summary> /// <param name="pendingRetry">Handle to an operation pending for retries as returned by SaveForRetry.</param> /// <param name="retryCallback">The function that will be invoked in each retry</param> /// <param name="recoveryCallback">An optional function that will be invoked when the retries over the operation have been exhausted according the specified RetryPolicy . If null, it be ignored.</param> /// <param name="cancellationToken">Cancellation token that allows to manually indicate that the retries should stop.</returns> public R DoExecute <T, R>(PendingRetry <T> pendingRetry, Func <T, R> retryCallback, Func <T, R> recoveryCallback, CancellationToken cancellationToken) { var collection = database.GetCollection <PendingRetry <T> >(RetryTemplate.PENDING_RETRIES_COLLECTION_NAME); Exception lastException = null; var retryPolicy = RetryPolicy; var backOffPolicy = BackOffPolicy; retryPolicy.StartContext(); backOffPolicy.StartContext(); while (retryPolicy.CanRetry(lastException) && !cancellationToken.IsCancellationRequested) { try { lastException = null; R result = retryCallback.Invoke(pendingRetry.Argument); if (!collection.Delete(pendingRetry.Id)) { throw new DeleteRetryException("The pending retry document cannot be found on database"); } return(result); } catch (DeleteRetryException) { throw; } catch (Exception e) { lastException = e; if (retryPolicy.CanRetry(lastException) && !cancellationToken.IsCancellationRequested) { retryPolicy.RegisterRetry(lastException); backOffPolicy.BackOff(); } } } if (cancellationToken.IsCancellationRequested) { throw new RetryInterruptedException("The execution of retries has been explicitly cancelled."); } else { collection.Delete(pendingRetry.Id); return(HandleRetryExhausted(recoveryCallback, pendingRetry.Argument, lastException)); } }
/// <summary>Saves a new operation as pending for retries.</summary> /// <param name="operationId">The operation identifier, that can be subsequently used to look for peding retry operations.</param> /// <param name="argument">The argument that must be provided to the operation when retried. It may be null.</param> /// <returns>A new instance of <see cref="PendingRetry"/> referring to the new created operation pending for retries.</returns> public PendingRetry <T> SaveForRetry <T>(string operationId, T argument) { var collection = database.GetCollection <PendingRetry <T> >(PENDING_RETRIES_COLLECTION_NAME); var pendingRetry = new PendingRetry <T> { Argument = argument, OperationId = operationId }; // Create index for the OperationId field collection.EnsureIndex(x => x.OperationId); lock (this) { collection.Insert(pendingRetry); BlockingCollection <PendingRetry <T> > blockingRetriesCollection = blockingOperationCollections.GetOrAdd(operationId, new BlockingCollection <PendingRetry <T> >()) as BlockingCollection <PendingRetry <T> >; blockingRetriesCollection.Add(pendingRetry); } return(pendingRetry); }
private void MarkAsCompleted <T>(PendingRetry <T> pendingRetry) { var collection = database.GetCollection <PendingRetry <T> >(PENDING_RETRIES_COLLECTION_NAME); collection.Delete(pendingRetry.Id); }