/// <summary>Clean up the cache if necessary and close the context provided (if the flag indicates that processing was successful).</summary> /// <param name="retryPolicy">The retry policy.</param> /// <param name="context">The context.</param> /// <param name="state">The state.</param> /// <param name="succeeded">The succeeded.</param> protected void Close(IRetryPolicy retryPolicy, IRetryContext context, IRetryState state, bool succeeded) { if (state != null) { if (succeeded) { this.retryContextCache.Remove(state.GetKey()); retryPolicy.Close(context); } } else { retryPolicy.Close(context); } }
/// <summary>The register exception.</summary> /// <param name="retryPolicy">The retry policy.</param> /// <param name="state">The state.</param> /// <param name="context">The context.</param> /// <param name="e">The e.</param> /// <exception cref="RetryException"></exception> protected void RegisterThrowable(IRetryPolicy retryPolicy, IRetryState state, IRetryContext context, Exception e) { if (state != null) { var key = state.GetKey(); if (context.RetryCount > 0 && !this.retryContextCache.ContainsKey(key)) { throw new RetryException( "Inconsistent state for failed item key: cache key has changed. " + "Consider whether equals() or hashCode() for the key might be inconsistent, " + "or if you need to supply a better key"); } this.retryContextCache.Put(key, context); } retryPolicy.RegisterException(context, e); }
/// <summary>Delegate to the <see cref="IRetryPolicy"/> having checked in the cache for an existing value if the state is not null.</summary> /// <param name="retryPolicy">The retry policy.</param> /// <param name="state">The state.</param> /// <returns>The Spring.Retry.Retry.IRetryContext, either a new one or the one used last time the same state was encountered.</returns> protected IRetryContext Open(IRetryPolicy retryPolicy, IRetryState state) { if (state == null) { return(this.DoOpenInternal(retryPolicy)); } var key = state.GetKey(); if (state.IsForceRefresh()) { return(this.DoOpenInternal(retryPolicy)); } // If there is no cache hit we can avoid the possible expense of the // cache re-hydration. if (!this.retryContextCache.ContainsKey(key)) { // The cache is only used if there is a failure. return(this.DoOpenInternal(retryPolicy)); } var context = this.retryContextCache.Get(key); if (context == null) { if (this.retryContextCache.ContainsKey(key)) { throw new RetryException( "Inconsistent state for failed item: no history found. " + "Consider whether equals() or hashCode() for the item might be inconsistent, " + "or if you need to supply a better ItemKeyGenerator"); } // The cache could have been expired in between calls to // containsKey(), so we have to live with this: return(this.DoOpenInternal(retryPolicy)); } return(context); }
/// <summary>Extension point for subclasses to decide on behaviour after catching an /// exception in a <see cref="IRetryCallback{T}"/>. Normal stateless behaviour is not /// to rethrow, and if there is state we rethrow.</summary> /// <param name="retryPolicy">The retry policy.</param> /// <param name="context">The context.</param> /// <param name="state">The state.</param> /// <returns>The System.Boolean.</returns> protected bool ShouldRethrow(IRetryPolicy retryPolicy, IRetryContext context, IRetryState state) { if (state == null) { return(false); } else { return(state.RollbackFor(context.LastException)); } }
/// <summary>Actions to take after final attempt has failed. If there is state clean /// up the cache. If there is a recovery callback, execute that and return /// its result. Otherwise throw an exception.</summary> /// <param name="recoveryCallback">The callback for recovery (might be null).</param> /// <param name="context">The current retry context.</param> /// <param name="state">The state.</param> /// <returns>The T.</returns> /// <typeparam name="T">Type T.</typeparam> protected T HandleRetryExhausted <T>(Func <IRetryContext, T> recoveryCallback, IRetryContext context, IRetryState state) { if (state != null) { this.retryContextCache.Remove(state.GetKey()); } if (recoveryCallback != null) { return(recoveryCallback.Invoke(context)); } if (state != null) { Logger.Debug(m => m("Retry exhausted after last attempt with no recovery path.")); throw new ExhaustedRetryException("Retry exhausted after last attempt with no recovery path", context.LastException); } throw WrapIfNecessary(context.LastException); }
protected T DoExecute <T>(object retryCallback, object recoveryCallback, IRetryState state) { if (retryCallback == null || (!(retryCallback is IRetryCallback <T>) & !(retryCallback is Func <IRetryContext, T>))) { throw new ArgumentException("retryCallback must be an IRetryCallback<T> or a Func<IRetryContext, T>"); } if (recoveryCallback != null && (!(recoveryCallback is IRetryCallback <T>) & !(recoveryCallback is Func <IRetryContext, T>))) { throw new ArgumentException("recoveryCallback must be an IRecoveryCallback<T> or a Func<IRetryContext, T>"); } var retryPolicy = this.retryPolicy; var backOffPolicy = this.backOffPolicy; // Allow the retry policy to initialise itself... var context = this.Open(retryPolicy, state); Logger.Trace(m => m("RetryContext retrieved: {0}", context)); // Make sure the context is available globally for clients who need // it... RetrySynchronizationManager.Register(context); Exception lastException = null; try { // Give clients a chance to enhance the context... var running = retryCallback is IRetryCallback <T>?this.DoOpenInterceptors((IRetryCallback <T>) retryCallback, context) : this.DoOpenInterceptors((Func <IRetryContext, T>)retryCallback, context); if (!running) { throw new TerminatedRetryException("Retry terminated abnormally by interceptor before first attempt"); } // Get or Start the backoff context... IBackOffContext backOffContext = null; IAttributeAccessor attributeAccessor = null; if (context is IAttributeAccessor) { attributeAccessor = context; var resource = attributeAccessor.GetAttribute("backOffContext"); if (resource is IBackOffContext) { backOffContext = (IBackOffContext)resource; } } if (backOffContext == null) { backOffContext = backOffPolicy.Start(context); if (attributeAccessor != null && backOffContext != null) { attributeAccessor.SetAttribute("backOffContext", backOffContext); } } // We allow the whole loop to be skipped if the policy or context // already forbid the first try. This is used in the case of // external retry to allow a recovery in handleRetryExhausted // without the callback processing (which would throw an exception). while (this.CanRetry(retryPolicy, context) && !context.ExhaustedOnly) { try { Logger.Debug(m => m("Retry: count={0}", context.RetryCount)); // Reset the last exception, so if we are successful // the close interceptors will not think we failed... lastException = null; return(retryCallback is IRetryCallback <T>?((IRetryCallback <T>)retryCallback).DoWithRetry(context) : ((Func <IRetryContext, T>)retryCallback).Invoke(context)); } catch (Exception e) { lastException = e; if (retryCallback is IRetryCallback <T> ) { this.DoOnErrorInterceptors((IRetryCallback <T>)retryCallback, context, e); } else { this.DoOnErrorInterceptors((Func <IRetryContext, T>)retryCallback, context, e); } try { this.RegisterThrowable(retryPolicy, state, context, e); } catch (Exception ex) { throw new TerminatedRetryException("Could not register exception", ex); } if (this.CanRetry(retryPolicy, context) && !context.ExhaustedOnly) { try { backOffPolicy.BackOff(backOffContext); } catch (BackOffInterruptedException ex) { lastException = e; // back off was prevented by another thread - fail // the retry Logger.Debug(m => m("Abort retry because interrupted: count={0}", context.RetryCount)); throw; } } Logger.Debug(m => m("Checking for rethrow: count={0}", context.RetryCount)); if (this.ShouldRethrow(retryPolicy, context, state)) { Logger.Debug(m => m("Rethrow in retry for policy: count={0}", context.RetryCount)); throw WrapIfNecessary(e); } } // A stateful attempt that can retry should have rethrown the // exception by now - i.e. we shouldn't get this far for a // stateful attempt if it can retry. } Logger.Debug(m => m("Retry failed last attempt: count={0}" + context.RetryCount)); if (context.ExhaustedOnly) { throw new ExhaustedRetryException("Retry exhausted after last attempt with no recovery path.", context.LastException); } if (recoveryCallback == null) { return(this.HandleRetryExhausted(default(IRecoveryCallback <T>), context, state)); } else if (recoveryCallback is IRecoveryCallback <T> ) { return(this.HandleRetryExhausted((IRecoveryCallback <T>)recoveryCallback, context, state)); } else { return(this.HandleRetryExhausted((Func <IRetryContext, T>)recoveryCallback, context, state)); } } finally { this.Close(retryPolicy, context, state, lastException == null); if (retryCallback is IRetryCallback <T> ) { this.DoCloseInterceptors((IRetryCallback <T>)retryCallback, context, lastException); } else { this.DoCloseInterceptors((Func <IRetryContext, T>)retryCallback, context, lastException); } RetrySynchronizationManager.Clear(); } }
/// <summary>The execute.</summary> /// <param name="retryCallback">The retry callback.</param> /// <param name="recoveryCallback">The recovery callback.</param> /// <param name="retryState">The retry state.</param> /// <returns>The T.</returns> /// <typeparam name="T">Type T.</typeparam> public T Execute <T>(Func <IRetryContext, T> retryCallback, Func <IRetryContext, T> recoveryCallback, IRetryState retryState) { return(this.DoExecute <T>(retryCallback, recoveryCallback, retryState)); }
/// <summary>The execute.</summary> /// <param name="retryCallback">The retry callback.</param> /// <param name="recoveryCallback">The recovery callback.</param> /// <param name="retryState">The retry state.</param> /// <returns>The T.</returns> /// <typeparam name="T">Type T.</typeparam> public T Execute <T>(IRetryCallback <T> retryCallback, IRecoveryCallback <T> recoveryCallback, IRetryState retryState) { return(this.DoExecute <T>(retryCallback, recoveryCallback, retryState)); }