File: Dcp\DcpPipelineBuilder.cs
Web Access
Project: src\src\Aspire.Hosting\Aspire.Hosting.csproj (Aspire.Hosting)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Microsoft.Extensions.Logging;
using Polly;
using Polly.Retry;
using Polly.Timeout;
 
namespace Aspire.Hosting.Dcp;
 
internal static class DcpPipelineBuilder
{
    public static ResiliencePipeline BuildDeleteRetryPipeline(ILogger logger)
    {
        var ensureDeleteRetryStrategy = new RetryStrategyOptions()
        {
            BackoffType = DelayBackoffType.Exponential,
            Delay = TimeSpan.FromMilliseconds(200),
            UseJitter = true,
            MaxRetryAttempts = 10, // Cumulative time for all attempts amounts to about 15 seconds
            MaxDelay = TimeSpan.FromSeconds(3),
            ShouldHandle = new PredicateBuilder().Handle<Exception>(),
            OnRetry = (retry) =>
            {
                logger.LogDebug("Retrying check for deleted resource. Attempt: {Attempt}. Error message: {ErrorMessage}", retry.AttemptNumber, retry.Outcome.Exception?.Message);
                return ValueTask.CompletedTask;
            }
        };
 
        var execution = new ResiliencePipelineBuilder().AddRetry(ensureDeleteRetryStrategy).Build();
        return execution;
    }
 
    public static ResiliencePipeline BuildCreateServiceRetryPipeline(DcpOptions dcpOptions, ILogger logger)
    {
        var withTimeout = new TimeoutStrategyOptions()
        {
            Timeout = dcpOptions.ServiceStartupWatchTimeout
        };
 
        var tryTwice = new RetryStrategyOptions()
        {
            BackoffType = DelayBackoffType.Constant,
            MaxDelay = TimeSpan.FromSeconds(1),
            UseJitter = true,
            MaxRetryAttempts = 1,
            ShouldHandle = new PredicateBuilder().Handle<Exception>(),
            OnRetry = (retry) =>
            {
                logger.LogDebug(
                    retry.Outcome.Exception,
                    "Watching for service port allocation ended with an error after {WatchDurationMs} (iteration {Iteration})",
                    retry.Duration.TotalMilliseconds,
                    retry.AttemptNumber
                );
                return ValueTask.CompletedTask;
            }
        };
 
        var execution = new ResiliencePipelineBuilder().AddRetry(tryTwice).AddTimeout(withTimeout).Build();
        return execution;
    }
 
    public static ResiliencePipeline BuildWatchResourcePipeline(ILogger logger)
    {
        var retryUntilCancelled = new RetryStrategyOptions()
        {
            ShouldHandle = new PredicateBuilder().HandleInner<EndOfStreamException>(),
            BackoffType = DelayBackoffType.Exponential,
            MaxRetryAttempts = int.MaxValue,
            UseJitter = true,
            MaxDelay = TimeSpan.FromSeconds(30),
            OnRetry = (retry) =>
            {
                logger.LogDebug(
                    retry.Outcome.Exception,
                    "Long poll watch operation was ended by server after {LongPollDurationInMs} milliseconds (iteration {Iteration}).",
                    retry.Duration.TotalMilliseconds,
                    retry.AttemptNumber
                    );
                return ValueTask.CompletedTask;
            }
        };
 
        var pipeline = new ResiliencePipelineBuilder().AddRetry(retryUntilCancelled).Build();
        return pipeline;
    }
}