Main/RetryPolicies/New-ConstantRetryPolicy.ps1
<#
.SYNOPSIS Creates a Constant Retry Policy .DESCRIPTION Creates a new object of type ConstantRetryPolicy to be used with Invoke-ScriptBlockWithRetry. The algorithm takes a constant delay, processing this policy will take baseDelay milliseconds to complete. Constructing a linear retry policy requires also a maximum number of retries mandatory. Once processing the maximum number of retries is reached succeeding retries will throw an error of type MaxRetryLimitReached. Error references can be passed in order to provide filtering on the types of errors to retry or not as well as a comparison type operator which will determine the comparison type to perform. Filtering is done through a method named [bool] shouldProcess() which takes a sample error and compares it against the reference. This method is used to determine whether to process the policy or not. shouldProcess method also considers the number of retries already performed and compare it agains the retry limit. The type of comparison determine the aspects of the sample error to compare agains the references provided. This comparison is performed in the base object and the possible behaviors are: - NoComparison: Performs no comparison and always returns true - TypeCompare: Compares the type of errors to ensure they're the same - ActivityCompare: Compares the CategoryInfo.Activity property to ensure are the same - CategoryCompare: Compares the CategoryInfo.Category property to ensure are the same - IdCompare: Compares the FullyQualifiedErrorId property to ensure sample error contains thew reference's id - AnyCompare: Dismiss the comparison and returns true .EXAMPLE PS> $retry = New-ConstantRetryPolicy -DelayBase 1000 -NumberOfRetries 3 Creates a new retry policy object that starts with a delay of 1 second and will attempt 3 times before throwing an error .EXAMPLE PS> $retry.getPolicyName() Returns the name of the policy instance .EXAMPLE PS> $retry.getPolicyVersion() Returns the version of the policy instance .EXAMPLE PS> $retry.clone() Returns a new instance of the policy instance using the data of the current object .EXAMPLE PS> $retry.shouldProcess($null) Determines whether the policy should be processed or not based on the sample error provided in comparison to the error reference objects passed when the object was built .EXAMPLE PS> $ex = New-Object ArgumentNullException -ArgumentList "Invalid Argument" PS> $er = New-Object System.Management.Automation.ErrorRecord -ArgumentList $ex, 'ErrID', 'InvalidArgument', $null PS> $retry.shouldProcess($er) Creates a sample Exception and a sample Error Record based of that, then the sample error record is used against the retry policy to validate whether to process the policy or not .EXAMPLE PS> $retry = New-ConstantRetryPolicy -DelayBase 500 -NumberOfRetries 3 PS> Measure-Command { $retry.processPolicy() } Days : 0 Hours : 0 Minutes : 0 Seconds : 0 Milliseconds : 509 Ticks : 5095698 TotalDays : 5.89779861111111E-06 TotalHours : 0.000141547166666667 TotalMinutes : 0.00849283 TotalSeconds : 0.5095698 TotalMilliseconds : 509.5698 Creates a sample linear retry policy with constant delay of half second .EXAMPLE PS> Measure-Command { $retry.processPolicy() } Days : 0 Hours : 0 Minutes : 0 Seconds : 0 Milliseconds : 501 Ticks : 5011027 TotalDays : 5.79979976851852E-06 TotalHours : 0.000139195194444444 TotalMinutes : 0.00835171166666667 TotalSeconds : 0.5011027 TotalMilliseconds : 501.1027 PS> Measure-Command { $retry.processPolicy() } Days : 0 Hours : 0 Minutes : 0 Seconds : 0 Milliseconds : 500 Ticks : 5003949 TotalDays : 5.79160763888889E-06 TotalHours : 0.000138998583333333 TotalMinutes : 0.008339915 TotalSeconds : 0.5003949 TotalMilliseconds : 500.3949 Based on the previous example each processing of the policy keeps the delay period of half second .EXAMPLE PS> Measure-Command { $retry.processPolicy() } [ConstantRetryPolicy.invokePolicy:MaxRetryLimitReached] Max number of retries reached: 3/3 At D:\repos\GitHub\PsxUtility\Main\RetryPolicies\New-ConstantRetryPolicy.ps1:375 char:13 + throw [xUtilityException]::New( + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : OperationStopped: (:) [], xUtilityException + FullyQualifiedErrorId : [ConstantRetryPolicy.invokePolicy:MaxRetryLimitReached] Max number of retries reached: 3 /3 Based on previous two examples the existing policy object has already reatched the maximum of attempts, hence any additional retry should fail .EXAMPLE PS> $el = $null PS> try{ 1 / 0 } catch { $el = $_ } PS> $retry = New-ConstantRetryPolicy -DelayBase 100 -NumberOfRetries 3 -ErrorReferences @($el) -ErrorRecordComparisonType ActivityCompare PS> $retry.shouldProcess($null) False PS> $es = $null PS> try{ 5/0 }catch{$es = $_} PS> $retry.shouldProcess($es) True Creates a constant retry policy that is triggered by the comparison on the Activity of a reference error. A null error provided did not return a positive match. However a similar error, with the same activity does trigger the match. .EXAMPLE PS> $el = $null PS> try{ 1 / 0 } catch { $el = $_ } PS> $retry = New-ConstantRetryPolicy -DelayBase 100 -NumberOfRetries 3 -ErrorReferences @($el) -ErrorRecordComparisonType AnyCompare PS> $retry.shouldProcess($null) True PS> $es = $null PS> try{ 5/0 }catch{$es = $_} PS> $retry.shouldProcess($es) True Creates a constant retry policy that is triggered with any sample error given the AnyCompare enum value. Both null and a sample error trigger the match .EXAMPLE PS> $el = $null PS> try{ 1 / 0 } catch { $el = $_ } PS> $retry = New-ConstantRetryPolicy -DelayBase 100 -NumberOfRetries 3 -ErrorReferences @($el) -ErrorRecordComparisonType CategoryCompare PS> $retry.shouldProcess($null) False PS> $es = $null PS> try{ 5/0 }catch{$es = $_} PS> $retry.shouldProcess($es) True Creates a constant retry policy that is triggered with error category match. Null values do not trigger the error while similar errors do match .EXAMPLE PS> $el = $null PS> try{ 1 / 0 } catch { $el = $_ } PS> $retry = New-ConstantRetryPolicy -DelayBase 100 -NumberOfRetries 3 -ErrorReferences @($el) -ErrorRecordComparisonType IdCompare PS> $retry.shouldProcess($null) False PS> Write-Error -Message 'Some Error' -ErrorId 'RuntimeException' PS> $es = $Error[0] PS> $retry.shouldProcess($es) True PS> Write-Error -Message 'Some Error' -ErrorId 'SomeId' PS> $es = $Error[0] PS> $retry.shouldProcess($es) False Creates a constant retry policy that is triggered based on the Fully Qualified Id property. Null value does not trigger the comparison match, while an error with the same ErrorId does trigger the match .EXAMPLE PS> $el = $null PS> try{ 1 / 0 } catch { $el = $_ } PS> $retry = New-ConstantRetryPolicy -DelayBase 100 -DelayIncrease 150 -NumberOfRetries 3 -ErrorReferences @($el) -ErrorRecordComparisonType NoComparison PS> $retry.shouldProcess($null) False PS> Write-Error -Message 'Some Error' -ErrorId 'RuntimeException' PS> $es = $Error[0] PS> $retry.shouldProcess($es) True PS> Write-Error -Message 'Some Error' -ErrorId 'SomeId' PS> $es = $Error[0] PS> $retry.shouldProcess($es) True Creates a constant retry policy that is triggered regardles of the object passed since no comparison is made. .EXAMPLE PS> $el = $null PS> try{ 1 / 0 } catch { $el = $_ } PS> $retry = New-ConstantRetryPolicy -DelayBase 100 -DelayIncrease 150 -NumberOfRetries 3 -ErrorReferences @($el) -ErrorRecordComparisonType NoComparison PS> $retry.shouldProcess($null) False PS> $es = $null PS> try{ 5/0 }catch{$es = $_} Creates a constant retry policy that is triggered when the type of the inner exception matches. It returns false otherwise #> function New-ConstantRetryPolicy { [CmdletBinding()] param( # Initial delay in milliseconds to start with [Parameter(Mandatory)] [ValidateScript({$_ -ge 0})] [int] $DelayBase = 0, # Number of times to retry [Parameter(Mandatory)] [ValidateScript({$_ -gt 0})] [int] $NumberOfRetries = 0, # Errors to compare against [Parameter()] [ValidateNotNullOrEmpty()] [System.Management.Automation.ErrorRecord[]] $ErrorReferences = @(), # Specify the type of comparison to perform [Parameter()] [ErrorRecordComparisonType] $ErrorRecordComparisonType = [ErrorRecordComparisonType]::AnyCompare ) $ErrorActionPreference = 'Stop' return [ConstantRetryPolicy]::New( $DelayBase, $NumberOfRetries, $ErrorReferences, $ErrorRecordComparisonType) } # Implements a constant retry policy class ConstantRetryPolicy : BaseRetryPolicy { hidden [int] $RetryCount hidden [int] $MaxRetries hidden [int] $DelayBaseMs hidden [System.Management.Automation.ErrorRecord[]] $ErrorMatches hidden [ErrorRecordComparisonType] $ErrorComparisonType ConstantRetryPolicy( # Delay base in milliseconds [int] $delayBase, # Number of retries to attempt [int] $numberOfRetries, # Errors that will trigger a retry [System.Management.Automation.ErrorRecord[]] $errorDetection, # Types to compare from the errors found [ErrorRecordComparisonType] $comparisonType ) { if ($delayBase -lt 0) { throw [xUtilityException]::New( "ConstantRetryPolicy:BaseRetryPolicy.Constructor", [xUtilityErrorCategory]::InvalidParameter, "Delay Base (milliseconds) has to be greater or equal to zero" ) } if ($numberOfRetries -lt 0) { throw [xUtilityException]::New( "ConstantRetryPolicy:BaseRetryPolicy.Constructor", [xUtilityErrorCategory]::InvalidParameter, "Number of Retries has to be greater or equal to zero" ) } $this.RetryCount = 0 $this.MaxRetries = $numberOfRetries $this.DelayBaseMs = $delayBase $this.ErrorMatches = $errorDetection if ($comparisonType -ne $null) { $this.ErrorComparisonType = $comparisonType } else { $this.ErrorComparisonType = [ErrorRecordComparisonType]::AnyCompare } } # Gets the policy name [string] getPolicyName() { return 'ConstantRetryPolicy' } # Get version of the current policy [System.Version] getPolicyVersion() { return [System.Version] '0.1.0.0' } # Determines whether to keep processing the policy or exit [bool] shouldProcess([System.Management.Automation.ErrorRecord] $operationError) { [int] $currentRetryCount = $this.RetryCount + 1 if ($currentRetryCount -ge $this.MaxRetries) { return $false } return [BaseRetryPolicy]::errorMatches($operationError, $this.ErrorMatches, $this.ErrorComparisonType) } # Creates a new instance of the implemented policy [BaseRetryPolicy] clone() { return [ConstantRetryPolicy]::New( $this.DelayBaseMs, $this.MaxRetries, $this.ErrorMatches, $this.ErrorComparisonType) } # Process the policy [void] processPolicy() { if ($this.RetryCount -ge $this.MaxRetries) { throw [xUtilityException]::New( ("{0}.invokePolicy" -f $this.getPolicyName()), [xUtilityErrorCategory]::MaxRetryLimitReached, ("Max number of retries reached: {0}/{1}" -f $this.RetryCount, $this.MaxRetries) ) } Write-Verbose ("[{0}] Retry {1}/{2} about to sleep {3} milliseconds" -f $this.getPolicyName(), $this.RetryCount, $this.MaxRetries, $this.DelayBaseMs) Start-Sleep -Milliseconds $this.DelayBaseMs $this.RetryCount++ } } |