Public/Retry/TransientErrorStrategies/New-TransientNetworkRetryStrategy.ps1
|
<#
.NOTES Dot-sourced by Infrastructure.Common.psm1. The public surface is New-TransientNetworkRetryStrategy; Test-TransientNetworkException is a file-private helper kept alongside the factory so the classification policy lives next to its sole consumer. #> # --------------------------------------------------------------------------- # Test-TransientNetworkException (private) # Walks the exception chain on an ErrorRecord and decides whether the # failure is a transient network condition (worth retrying) or a # permanent error (a 4xx client response, an argument bug, the mock # layer in tests throwing a plain string, etc.). # # Transient signals: # - System.Net.Http.HttpRequestException (DNS, connection refused, # socket errors, generic # HttpClient failures) # - System.Net.WebException (legacy WebClient stack) # - System.Net.Sockets.SocketException (raw socket errors - # "No such host is known") # - System.TimeoutException # - System.Threading.Tasks.TaskCanceledException (HttpClient timeout) # # HTTP status-code rule: # - 5xx server errors -> transient, retry. # - 4xx client errors -> permanent, fail fast. # # Anything else (e.g. ArgumentException, RuntimeException from a # string throw in tests) is treated as permanent so a bug or a test # mock does not incur retry delays. # # Kept module-internal (not in Export-ModuleMember) - exposing the # policy decision through the strategy factory is enough, and keeping # it private leaves room to evolve the classification without breaking # callers. # --------------------------------------------------------------------------- function Test-TransientNetworkException { [CmdletBinding()] param( [Parameter(Mandatory)] [System.Management.Automation.ErrorRecord] $ErrorRecord ) $transientTypeNames = @( 'System.Net.Http.HttpRequestException', 'System.Net.WebException', 'System.Net.Sockets.SocketException', 'System.TimeoutException', 'System.Threading.Tasks.TaskCanceledException' ) $ex = $ErrorRecord.Exception while ($null -ne $ex) { $typeName = $ex.GetType().FullName # PowerShell 7's Invoke-RestMethod / Invoke-WebRequest emit # HttpResponseException for non-success responses. Distinguish 4xx # (permanent) from 5xx (transient) by the Response.StatusCode value. if ($typeName -eq 'Microsoft.PowerShell.Commands.HttpResponseException') { $statusCode = [int] $ex.Response.StatusCode return ($statusCode -ge 500) } if ($transientTypeNames -contains $typeName) { return $true } $ex = $ex.InnerException } return $false } function New-TransientNetworkRetryStrategy { <# .SYNOPSIS Builds a retry strategy hashtable that matches transient network failures (DNS hiccups, dropped connections, 5xx responses, HttpClient timeouts). .DESCRIPTION Returned shape is the standard retry-strategy contract consumed by Invoke-WithRetry: @{ Name = 'TransientNetwork' ShouldRetry = { param($err) <bool> } } The ShouldRetry predicate delegates to the file-private Test-TransientNetworkException helper, which walks the exception chain. 4xx HttpResponseExceptions and non-network errors are classified as permanent so the caller fails fast instead of sleeping through retries that cannot succeed. .EXAMPLE Invoke-WithRetry ` -ScriptBlock { Invoke-RestMethod $url } ` -RetryStrategy (New-TransientNetworkRetryStrategy) #> [CmdletBinding()] param() return @{ Name = 'TransientNetwork' ShouldRetry = { param([System.Management.Automation.ErrorRecord] $ErrorRecord) Test-TransientNetworkException -ErrorRecord $ErrorRecord } } } |