public async Task Send(T context, IPipe <T> next) { using (IRepeatContext repeatContext = _repeatPolicy.GetRepeatContext()) { await Attempt(repeatContext, context, next); } }
public static async Task UntilCancelled(CancellationToken cancellationToken, Func <Task> callback) { if (cancellationToken.IsCancellationRequested) { return; } await Task.Yield(); IRepeatPolicy repeatPolicy = UntilCancelled(cancellationToken); using (IRepeatContext repeatContext = repeatPolicy.GetRepeatContext()) { TimeSpan delay = TimeSpan.Zero; do { if (cancellationToken.IsCancellationRequested) { break; } if (delay > TimeSpan.Zero) { await Task.Delay(delay, repeatContext.CancellationToken).ConfigureAwait(false); } if (cancellationToken.IsCancellationRequested) { break; } await callback().ConfigureAwait(false); }while (repeatContext.CanRepeat(out delay)); } }
/// <summary> /// Clear the current context at the end of a batch - should only be used by /// IRepeatOperations implementations. /// </summary> /// <returns> the old value if there was one.</returns> public static IRepeatContext Clear() { IRepeatContext context = GetContext(); ContextHolder.Value = null; return(context); }
/// <summary> /// Method for registering a context - should only be used by /// IRepeatOperations implementations to ensure that /// #GetContext() always returns the correct value. /// </summary> /// <param name="context"> a new context at the start of a batch.</param> /// <returns>the old value if there was one.</returns> public static IRepeatContext Register(IRepeatContext context) { IRepeatContext oldSession = GetContext(); ContextHolder.Value = context; return(oldSession); }
/// <summary> /// Increments the context so the counter is up to date. Do nothing else. /// </summary> /// <param name="context"></param> public void Update(IRepeatContext context) { var support = context as RepeatContextSupport; if (support != null) { support.Increment(); } }
/// <summary> /// Handling exceptions. /// </summary> /// <param name="exception"></param> /// <param name="context"></param> /// <param name="deferred"></param> private void DoHandle(System.Exception exception, IRepeatContext context, ICollection <System.Exception> deferred) { // An exception alone is not sufficient grounds for not continuing System.Exception unwrappedException = UnwrapIfRethrown(exception); try { for (int i = _listeners.Length; i-- > 0;) { IRepeatListener interceptor = _listeners[i]; // This is not an error - only log at debug level. Logger.Debug(unwrappedException, "Exception intercepted ({0} of {1})", (i + 1), _listeners.Length); interceptor.OnError(context, unwrappedException); } Logger.Debug("Handling exception: {0}, caused by: {1} : {2}", exception.GetType().Name, unwrappedException.GetType().Name, unwrappedException.Message ); _exceptionHandler.HandleException(context, unwrappedException); } catch (System.Exception handled) { deferred.Add(handled); } }
/// <summary> /// If exit status is not continuable returns <c>true</c>, otherwise /// delegates to #IsComplete(IRepeatContext). /// </summary> /// <param name="context"></param> /// <param name="result"></param> /// <returns></returns> public virtual bool IsComplete(IRepeatContext context, RepeatStatus result) { if (result != null && !result.IsContinuable()) { return(true); } return(IsComplete(context)); }
/// <summary> /// If exit status is not continuable returns <code>true</code>, otherwise /// delegates to #IsComplete(IRepeatContext). /// </summary> /// <param name="context"></param> /// <param name="result"></param> /// <returns></returns> public virtual bool IsComplete(IRepeatContext context, RepeatStatus result) { if (result != null && !result.IsContinuable()) { return true; } return IsComplete(context); }
/// <summary> /// Get the next completed result, possibly executing several callbacks until /// one finally finishes. Normally a subclass would have to override both /// this method and <see cref="CreateInternalState"/> because the /// implementation of this method would rely on the details of the internal /// state. /// </summary> /// <param name="context"></param> /// <param name="callback"></param> /// <param name="state"></param> /// <returns></returns> /// <exception cref="Exception"> </exception> protected virtual RepeatStatus GetNextResult(IRepeatContext context, RepeatCallback callback, IRepeatInternalState state) { Update(context); if (Logger.IsDebugEnabled) { Logger.Debug("Repeat operation about to start at count={0}", context.GetStartedCount()); } return(callback(context)); //"DoInInteration" }
/// <summary> /// Convenience method to set the current repeat operation to complete if it exists. /// </summary> public static void SetCompleteOnly() { IRepeatContext context = GetContext(); if (context != null) { context.SetCompleteOnly(); } }
/// <summary> /// Set current session and all ancestors (via parent) to complete., /// </summary> public static void SetAncestorsCompleteOnly() { IRepeatContext context = GetContext(); while (context != null) { context.SetCompleteOnly(); context = context.Parent; } }
/// <summary> /// Delegates to the Completion policy. /// </summary> /// <param name="context"></param> /// <param name="result"></param> /// <returns></returns> protected bool IsComplete(IRepeatContext context, RepeatStatus result) { bool complete = _completionPolicy.IsComplete(context, result); if (complete && Logger.IsDebugEnabled) { Logger.Debug("Repeat is complete according to policy and result value."); } return(complete); }
/// <summary> /// Delegates to the Completion policy. /// </summary> /// <param name="context"></param> /// <returns></returns> protected bool IsComplete(IRepeatContext context) { bool complete = _completionPolicy.IsComplete(context); if (complete && Logger.IsDebugEnabled) { Logger.Debug("Repeat is complete according to policy alone not including result."); } return(complete); }
/// <summary> /// Delegates the start to the Completion policy. /// </summary> /// <returns></returns> protected IRepeatContext Start() { IRepeatContext parent = RepeatSynchronizationManager.GetContext(); IRepeatContext context = _completionPolicy.Start(parent); RepeatSynchronizationManager.Register(context); if (Logger.IsDebugEnabled) { Logger.Debug("Starting repeat context."); } return(context); }
/// <summary> /// Convenience method to execute after interceptors on a callback result. /// </summary> /// <param name="context"></param> /// <param name="value"></param> protected void ExecuteAfterInterceptors(IRepeatContext context, RepeatStatus value) { // Don't re-throw exceptions here: let the exception handler deal with // that... if (value != null && value.IsContinuable()) { for (int i = _listeners.Length; i-- > 0;) { IRepeatListener interceptor = _listeners[i]; interceptor.After(context, value); } } }
static async Task Attempt(IRepeatContext repeatContext, T context, IPipe <T> next) { TimeSpan delay = TimeSpan.Zero; do { if (delay > TimeSpan.Zero) { await Task.Delay(delay, repeatContext.CancellationToken); } await next.Send(context); }while (repeatContext.CanRepeat(out delay)); }
/// <summary> /// Check that given context is marked as completed. /// </summary> /// <param name="context"></param> /// <returns></returns> private bool IsMarkedComplete(IRepeatContext context) { bool complete = context.IsCompleteOnly(); if (context.Parent != null) { complete = complete || IsMarkedComplete(context.Parent); } if (complete && Logger.IsDebugEnabled) { Logger.Debug("Repeat is complete according to context alone."); } return(complete); }
public static async Task UntilCancelled(CancellationToken cancellationToken, Func <Task> callback) { IRepeatPolicy repeatPolicy = UntilCancelled(cancellationToken); using (IRepeatContext repeatContext = repeatPolicy.GetRepeatContext()) { TimeSpan delay = TimeSpan.Zero; do { if (delay > TimeSpan.Zero) { await Task.Delay(delay, repeatContext.CancellationToken); } await callback(); }while (repeatContext.CanRepeat(out delay)); } }
/// <summary> /// Execute the batch callback until the completion policy decides that we /// are finished. Wait for the whole batch to finish before returning even if /// the task executor is asynchronous. /// </summary> /// <param name="callback"></param> /// <returns></returns> public RepeatStatus Iterate(RepeatCallback callback) { IRepeatContext outer = RepeatSynchronizationManager.GetContext(); RepeatStatus result; try { // This works with an asynchronous TaskExecutor: the // interceptors have to wait for the child processes. result = ExecuteInternal(callback); } finally { RepeatSynchronizationManager.Clear(); if (outer != null) { RepeatSynchronizationManager.Register(outer); } } return(result); }
/// <summary> /// Use the TaskExecutor to generate a result. The /// internal state in this case is a queue of unfinished result holders of /// IResultHolder. The holder with the return value should not be /// on the queue when this method exits. The queue is scoped in the calling /// method so there is no need to synchronize access. /// </summary> /// <param name="context"></param> /// <param name="callback"></param> /// <param name="state"></param> /// <returns></returns> protected override RepeatStatus GetNextResult(IRepeatContext context, RepeatCallback callback, IRepeatInternalState state) { ExecutingRunnable runnable; IResultQueue <IResultHolder> queue = ((ResultQueueInternalState)state).ResultQueue; do { // Wrap the callback in a runnable that will add its result to the // queue when it is ready. runnable = new ExecutingRunnable(callback, context, queue); // Tell the runnable that it can expect a result. This could have // been in-lined with the constructor, but it might block, so it's // better to do it here, since we have the option (it's a private // class). runnable.Expect(); //Start the task possibly concurrently / in the future. _taskExecutor.Execute(new Task(delegate { runnable.Run(); })); // Allow termination policy to update its state. This must happen // immediately before or after the call to the task executor. Update(context); // Keep going until we get a result that is finished, or early // termination... }while (queue.IsEmpty() && !IsComplete(context)); IResultHolder result = queue.Take(); if (result.Error != null) { throw result.Error; } return(result.Result); }
/// <summary> /// True if the result is null, or a RepeatStatus indicating completion. /// </summary> /// <param name="context"></param> /// <param name="result"></param> /// <returns></returns> public override bool IsComplete(IRepeatContext context, RepeatStatus result) { return (result == null || !result.IsContinuable()); }
/// <summary> /// Resets the counter. /// </summary> /// <param name="context"></param> /// <returns></returns> public override IRepeatContext Start(IRepeatContext context) { return new SimpleTerminationContext(context); }
/// <summary> /// Terminates if the chunk size has been reached, or the result is null. /// </summary> /// <param name="context"></param> /// <param name="result"></param> /// <returns></returns> public override bool IsComplete(IRepeatContext context, RepeatStatus result) { return base.IsComplete(context, result) || ((SimpleTerminationContext)context).IsComplete(this); }
/// <summary> /// Delegates to the completion policy. /// </summary> /// <param name="context"></param> protected void Update(IRepeatContext context) { _completionPolicy.Update(context); }
/// <summary> /// Always true. /// </summary> /// <param name="context"></param> /// <returns></returns> public virtual bool IsComplete(IRepeatContext context) { return(true); }
/// <summary> /// Re-throws the exception. /// </summary> /// <param name="context"></param> /// <param name="exception"></param> public void HandleException(IRepeatContext context, System.Exception exception) { throw new System.Exception("Exception encountered", exception); }
/// <summary> /// Constructor for RepeatContextSupport. The parent can be null, but /// should be set to the enclosing repeat context if there is one, e.g. if /// this context is an inner loop. /// </summary> /// <param name="parent"></param> public RepeatContextSupport(IRepeatContext parent) { _parent = parent; }
/// <summary> /// Delegates to the Completion policy. /// </summary> /// <param name="context"></param> /// <param name="result"></param> /// <returns></returns> protected bool IsComplete(IRepeatContext context, RepeatStatus result) { bool complete = _completionPolicy.IsComplete(context, result); if (complete && Logger.IsDebugEnabled) { Logger.Debug("Repeat is complete according to policy and result value."); } return complete; }
/// <summary> /// Create an internal state object that is used to store data needed /// internally in the scope of an iteration. Used by subclasses to manage the /// queueing and retrieval of asynchronous results. The default just provides /// an accumulation of exceptions instances for processing at the end of the /// batch. /// </summary> /// <param name="context"></param> /// <returns></returns> protected virtual IRepeatInternalState CreateInternalState(IRepeatContext context) { return(new RepeatInternalStateSupport()); }
/// <summary> /// Delegates to the Completion policy. /// </summary> /// <param name="context"></param> /// <returns></returns> protected bool IsComplete(IRepeatContext context) { bool complete = _completionPolicy.IsComplete(context); if (complete && Logger.IsDebugEnabled) { Logger.Debug("Repeat is complete according to policy alone not including result."); } return complete; }
/// <summary> /// Handling exceptions. /// </summary> /// <param name="exception"></param> /// <param name="context"></param> /// <param name="deferred"></param> private void DoHandle(System.Exception exception, IRepeatContext context, ICollection<System.Exception> deferred) { // An exception alone is not sufficient grounds for not continuing System.Exception unwrappedException = UnwrapIfRethrown(exception); try { for (int i = _listeners.Length; i-- > 0; ) { IRepeatListener interceptor = _listeners[i]; // This is not an error - only log at debug level. Logger.Debug(unwrappedException, "Exception intercepted ({0} of {1})", (i + 1), _listeners.Length); interceptor.OnError(context, unwrappedException); } Logger.Debug("Handling exception: {0}, caused by: {1} : {2}", exception.GetType().Name, unwrappedException.GetType().Name, unwrappedException.Message ); _exceptionHandler.HandleException(context, unwrappedException); } catch (System.Exception handled) { deferred.Add(handled); } }
/// <summary> /// Create an internal state object that is used to store data needed /// internally in the scope of an iteration. Used by subclasses to manage the /// queueing and retrieval of asynchronous results. The default just provides /// an accumulation of exceptions instances for processing at the end of the /// batch. /// </summary> /// <param name="context"></param> /// <returns></returns> protected virtual IRepeatInternalState CreateInternalState(IRepeatContext context) { return new RepeatInternalStateSupport(); }
/// <summary> /// Check that given context is marked as completed. /// </summary> /// <param name="context"></param> /// <returns></returns> private bool IsMarkedComplete(IRepeatContext context) { bool complete = context.IsCompleteOnly(); if (context.Parent != null) { complete = complete || IsMarkedComplete(context.Parent); } if (complete && Logger.IsDebugEnabled) { Logger.Debug("Repeat is complete according to context alone."); } return complete; }
/// <summary> /// Get the next completed result, possibly executing several callbacks until /// one finally finishes. Normally a subclass would have to override both /// this method and <see cref="CreateInternalState"/> because the /// implementation of this method would rely on the details of the internal /// state. /// </summary> /// <param name="context"></param> /// <param name="callback"></param> /// <param name="state"></param> /// <returns></returns> /// <exception cref="Exception"> </exception> protected virtual RepeatStatus GetNextResult(IRepeatContext context, RepeatCallback callback, IRepeatInternalState state) { Update(context); if (Logger.IsDebugEnabled) { Logger.Debug("Repeat operation about to start at count={0}", context.GetStartedCount()); } return callback(context); //"DoInInteration" }
/// <summary> /// Builds a new IRepeatContext and returns it. /// </summary> /// <param name="parent"></param> /// <returns></returns> public virtual IRepeatContext Start(IRepeatContext parent) { return new RepeatContextSupport(parent); }
/// <summary> /// Builds a new IRepeatContext and returns it. /// </summary> /// <param name="parent"></param> /// <returns></returns> public virtual IRepeatContext Start(IRepeatContext parent) { return(new RepeatContextSupport(parent)); }
public ExecutingRunnable(RepeatCallback callback, IRepeatContext context, IResultQueue<IResultHolder> queue) { _callback = callback; Context = context; _queue = queue; }
/// <summary> /// Terminates if the chunk size has been reached, or the result is null. /// </summary> /// <param name="context"></param> /// <param name="result"></param> /// <returns></returns> public override bool IsComplete(IRepeatContext context, RepeatStatus result) { return(base.IsComplete(context, result) || ((SimpleTerminationContext)context).IsComplete(this)); }
/// <summary> /// Convenience method to execute after interceptors on a callback result. /// </summary> /// <param name="context"></param> /// <param name="value"></param> protected void ExecuteAfterInterceptors(IRepeatContext context, RepeatStatus value) { // Don't re-throw exceptions here: let the exception handler deal with // that... if (value != null && value.IsContinuable()) { for (int i = _listeners.Length; i-- > 0; ) { IRepeatListener interceptor = _listeners[i]; interceptor.After(context, value); } } }
/// <summary> /// /// </summary> /// <param name="callback"></param> /// <returns></returns> private RepeatStatus ExecuteInternal(RepeatCallback callback) { // Reset the termination policy if there is one... IRepeatContext context = Start(); // Make sure if we are already marked complete before we start then no // processing takes place. bool running = !IsMarkedComplete(context); foreach (IRepeatListener interceptor in _listeners) { interceptor.Open(context); running = running && !IsMarkedComplete(context); if (!running) { break; } } // Return value, default is to allow continued processing. RepeatStatus result = RepeatStatus.Continuable; IRepeatInternalState state = CreateInternalState(context); // This is the list of exceptions thrown by all active callbacks ICollection <System.Exception> exceptions = state.GetExceptions(); // Keep a separate list of exceptions we handled that need to be // rethrown ICollection <System.Exception> deferred = new List <System.Exception>(); try { while (running) { #region WhileRunning /* * Run the before interceptors here, not in the task executor so * that they all happen in the same thread - it's easier for * tracking batch status, amongst other things. */ foreach (IRepeatListener interceptor in _listeners) { interceptor.Before(context); // Allow before interceptors to veto the batch by setting // flag. running = running && !IsMarkedComplete(context); } // Check that we are still running (should always be true) ... if (running) { #region Running try { result = GetNextResult(context, callback, state); ExecuteAfterInterceptors(context, result); } catch (System.Exception exception) { DoHandle(exception, context, deferred); } // N.B. the order may be important here: if (IsComplete(context, result) || IsMarkedComplete(context) || deferred.Any()) { running = false; } #endregion } #endregion } result = result.And(WaitForResults(state)); foreach (System.Exception exception in exceptions) { DoHandle(exception, context, deferred); } // Explicitly drop any references to internal state... // useless ? state = null; } /* * No need for explicit catch here - if the business processing threw an * exception it was already handled by the helper methods. An exception * here is necessarily fatal. */ finally { #region HandleFinally HandleFinally(deferred, _listeners, context); #endregion } return(result); }
/// <summary> /// Always true. /// </summary> /// <param name="context"></param> /// <returns></returns> public virtual bool IsComplete(IRepeatContext context) { return true; }
/// <summary> /// Handling the finally from ExecuteInternal. /// </summary> /// <param name="deferred"></param> /// <param name="listeners"></param> /// <param name="context"></param> private void HandleFinally(ICollection <System.Exception> deferred, IRepeatListener[] listeners, IRepeatContext context) { try { if (deferred.Any()) { System.Exception exception = deferred.First(); Logger.Debug("Handling fatal exception explicitly (rethrowing first of {0}): {1} : {2}", deferred.Count, exception.GetType().Name, exception.Message ); Rethrow(exception); } } finally { try { foreach (IRepeatListener interceptor in _listeners) { interceptor.Close(context); } } finally { context.Close(); } } }
/// <summary> /// Use the TaskExecutor to generate a result. The /// internal state in this case is a queue of unfinished result holders of /// IResultHolder. The holder with the return value should not be /// on the queue when this method exits. The queue is scoped in the calling /// method so there is no need to synchronize access. /// </summary> /// <param name="context"></param> /// <param name="callback"></param> /// <param name="state"></param> /// <returns></returns> protected override RepeatStatus GetNextResult(IRepeatContext context, RepeatCallback callback, IRepeatInternalState state) { ExecutingRunnable runnable; IResultQueue<IResultHolder> queue = ((ResultQueueInternalState)state).ResultQueue; do { // Wrap the callback in a runnable that will add its result to the // queue when it is ready. runnable = new ExecutingRunnable(callback, context, queue); // Tell the runnable that it can expect a result. This could have // been in-lined with the constructor, but it might block, so it's // better to do it here, since we have the option (it's a private // class). runnable.Expect(); //Start the task possibly concurrently / in the future. _taskExecutor.Execute(new Task(delegate { runnable.Run(); })); // Allow termination policy to update its state. This must happen // immediately before or after the call to the task executor. Update(context); // Keep going until we get a result that is finished, or early // termination... } while (queue.IsEmpty() && !IsComplete(context)); IResultHolder result = queue.Take(); if (result.Error != null) { throw result.Error; } return result.Result; }
/// <summary> /// see RepeatTemplate#CreateInternalState . /// </summary> /// <param name="context"></param> /// <returns></returns> protected override IRepeatInternalState CreateInternalState(IRepeatContext context) { // Queue of pending results: return new ResultQueueInternalState(_throttleLimit); }
/// <summary> /// True if the result is null, or a RepeatStatus indicating completion. /// </summary> /// <param name="context"></param> /// <param name="result"></param> /// <returns></returns> public override bool IsComplete(IRepeatContext context, RepeatStatus result) { return(result == null || !result.IsContinuable()); }
/// <summary> /// Terminates if the chunk size has been reached. /// </summary> /// <param name="context"></param> /// <returns></returns> public override bool IsComplete(IRepeatContext context) { return ((SimpleTerminationContext)context).IsComplete(this); }
/// <summary> /// Always false. /// </summary> /// <param name="context"></param> /// <returns></returns> public override bool IsComplete(IRepeatContext context) { return(false); }
/// <summary> /// Custom Constructor. /// </summary> /// <param name="context"></param> public SimpleTerminationContext(IRepeatContext context) : base(context) { }
/// <summary> /// Method for registering a context - should only be used by /// IRepeatOperations implementations to ensure that /// #GetContext() always returns the correct value. /// </summary> /// <param name="context"> a new context at the start of a batch.</param> /// <returns>the old value if there was one.</returns> public static IRepeatContext Register(IRepeatContext context) { IRepeatContext oldSession = GetContext(); ContextHolder.Value = context; return oldSession; }
/// <summary> /// Always false. /// </summary> /// <param name="context"></param> /// <returns></returns> public override bool IsComplete(IRepeatContext context) { return false; }
/// <summary> /// Handling the finally from ExecuteInternal. /// </summary> /// <param name="deferred"></param> /// <param name="listeners"></param> /// <param name="context"></param> private void HandleFinally(ICollection<System.Exception> deferred,IRepeatListener[] listeners,IRepeatContext context) { try { if (deferred.Any()) { System.Exception exception = deferred.First(); Logger.Debug("Handling fatal exception explicitly (rethrowing first of {0}): {1} : {2}", deferred.Count, exception.GetType().Name, exception.Message ); Rethrow(exception); } } finally { try { foreach (IRepeatListener interceptor in _listeners) { interceptor.Close(context); } } finally { context.Close(); } } }