Skip to content

Commit

Permalink
Migrate to resilience policies in Polly v8 with a new example (#79)
Browse files Browse the repository at this point in the history
  • Loading branch information
mburumaxwell authored Dec 12, 2023
1 parent 63fea89 commit 493f332
Show file tree
Hide file tree
Showing 12 changed files with 124 additions and 12 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ resource job 'Microsoft.App/jobs@2023-05-01' = {
- [Managing periodic tasks in AspNetCore](./samples/AspNetCoreSample)
- [Triggering periodic tasks using Tingle.EventBus](./samples/EventBusSample)
- [Save executions to a database using Entity Framework](./samples/EFCoreStoreSample)
- [Add retries using Polly's Resilience Pipelines](./samples/ResilienceSample/)

## Issues & Comments

Expand Down
7 changes: 7 additions & 0 deletions Tingle.PeriodicTasks.sln
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFCoreStoreSample", "sample
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EventBusSample", "samples\EventBusSample\EventBusSample.csproj", "{BB2ED193-FA14-4118-8D02-0D5E65FEE996}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ResilienceSample", "samples\ResilienceSample\ResilienceSample.csproj", "{5EAADE1F-4BA5-4E6F-85C0-94773BC3B132}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleSample", "samples\SimpleSample\SimpleSample.csproj", "{4EBCCAB0-E459-4771-8510-CE1A5839BC54}"
EndProject
Global
Expand Down Expand Up @@ -86,6 +88,10 @@ Global
{BB2ED193-FA14-4118-8D02-0D5E65FEE996}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BB2ED193-FA14-4118-8D02-0D5E65FEE996}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BB2ED193-FA14-4118-8D02-0D5E65FEE996}.Release|Any CPU.Build.0 = Release|Any CPU
{5EAADE1F-4BA5-4E6F-85C0-94773BC3B132}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5EAADE1F-4BA5-4E6F-85C0-94773BC3B132}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5EAADE1F-4BA5-4E6F-85C0-94773BC3B132}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5EAADE1F-4BA5-4E6F-85C0-94773BC3B132}.Release|Any CPU.Build.0 = Release|Any CPU
{4EBCCAB0-E459-4771-8510-CE1A5839BC54}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4EBCCAB0-E459-4771-8510-CE1A5839BC54}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4EBCCAB0-E459-4771-8510-CE1A5839BC54}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand All @@ -105,6 +111,7 @@ Global
{A44751D3-FACD-4EF4-928A-001075B9D992} = {0ADC51A4-CD37-4D45-8F91-AA493AE00197}
{956273BC-3375-4C2B-B1FF-43B05DA98DD0} = {0ADC51A4-CD37-4D45-8F91-AA493AE00197}
{BB2ED193-FA14-4118-8D02-0D5E65FEE996} = {0ADC51A4-CD37-4D45-8F91-AA493AE00197}
{5EAADE1F-4BA5-4E6F-85C0-94773BC3B132} = {0ADC51A4-CD37-4D45-8F91-AA493AE00197}
{4EBCCAB0-E459-4771-8510-CE1A5839BC54} = {0ADC51A4-CD37-4D45-8F91-AA493AE00197}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
Expand Down
58 changes: 58 additions & 0 deletions samples/ResilienceSample/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using Polly.Retry;
using Polly;
using Tingle.PeriodicTasks;

var host = Host.CreateDefaultBuilder(args)
.ConfigureServices((context, services) =>
{
var environment = context.HostingEnvironment;
var configuration = context.Configuration;

// register IDistributedLockProvider
var path = configuration.GetValue<string?>("DistributedLocking:FilePath")
?? Path.Combine(environment.ContentRootPath, "distributed-locks");
services.AddSingleton<Medallion.Threading.IDistributedLockProvider>(provider =>
{
return new Medallion.Threading.FileSystem.FileDistributedSynchronizationProvider(Directory.CreateDirectory(path));
});

// register periodic tasks
services.AddPeriodicTasks(builder =>
{
builder.AddTask<DatabaseCleanerTask>(o =>
{
o.Schedule = "*/1 * * * *";
o.ResiliencePipeline = new ResiliencePipelineBuilder()
.AddRetry(new RetryStrategyOptions
{
ShouldHandle = new PredicateBuilder().Handle<Exception>(),
Delay = TimeSpan.FromSeconds(1),
MaxRetryAttempts = 3,
BackoffType = DelayBackoffType.Constant,
OnRetry = args =>
{
Console.WriteLine($"Attempt {args.AttemptNumber} failed; retrying in {args.RetryDelay}");
return ValueTask.CompletedTask;
},
})
.Build();
});
});
})
.Build();

await host.RunAsync();

class DatabaseCleanerTask(ILogger<DatabaseCleanerTask> logger) : IPeriodicTask
{
public async Task ExecuteAsync(PeriodicTaskExecutionContext context, CancellationToken cancellationToken = default)
{
if (Random.Shared.Next(1, 5) > 2) // 60% of the time
{
throw new Exception("Failed to clean up old records from the database");
}

logger.LogInformation("Cleaned up old records from the database");
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
}
}
11 changes: 11 additions & 0 deletions samples/ResilienceSample/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"profiles": {
"ResilienceSample": {
"commandName": "Project",
"dotnetRunMessages": true,
"environmentVariables": {
"DOTNET_ENVIRONMENT": "Development"
}
}
}
}
12 changes: 12 additions & 0 deletions samples/ResilienceSample/ResilienceSample.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk.Worker">

<ItemGroup>
<PackageReference Include="DistributedLock.FileSystem" Version="1.0.1" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Tingle.PeriodicTasks\Tingle.PeriodicTasks.csproj" />
</ItemGroup>

</Project>
15 changes: 15 additions & 0 deletions samples/ResilienceSample/appsettings.Development.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"Microsoft.Hosting.Lifetime": "Information"
},
"Console": {
"FormatterName": "simple",
"FormatterOptions": {
"TimestampFormat": "[yyyy-MM-dd HH:mm:ss] ",
"SingleLine": true
}
}
}
}
8 changes: 8 additions & 0 deletions samples/ResilienceSample/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public void PostConfigure(string? name, PeriodicTaskOptions options)
options.LockTimeout ??= tasksHostOptions.DefaultLockTimeout;
options.Deadline ??= tasksHostOptions.DefaultDeadline;
options.ExecutionIdFormat ??= tasksHostOptions.DefaultExecutionIdFormat;
options.RetryPolicy ??= tasksHostOptions.DefaultRetryPolicy;
options.ResiliencePipeline ??= tasksHostOptions.DefaultResiliencePipeline;
}

/// <inheritdoc/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ public class PeriodicTasksHostOptions
public string? LockNamePrefix { get; set; }

/// <summary>
/// Optional default retry policy to use for periodic tasks where it is not specified.
/// To specify a value per periodic task, use the <see cref="PeriodicTaskOptions.RetryPolicy"/> option.
/// Optional default <see cref="ResiliencePipeline"/> to use for periodic tasks where it is not specified.
/// To specify a value per periodic task, use the <see cref="PeriodicTaskOptions.ResiliencePipeline"/> option.
/// Defaults to <see langword="null"/>.
/// </summary>
public AsyncPolicy? DefaultRetryPolicy { get; set; }
public ResiliencePipeline? DefaultResiliencePipeline { get; set; }

/// <summary>
/// Gets or sets the default <see cref="CronSchedule"/> to use for periodic tasks where it is not specified.
Expand Down
8 changes: 4 additions & 4 deletions src/Tingle.PeriodicTasks/Internal/PeriodicTaskRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,12 +128,12 @@ public async Task RunAsync(string name, CancellationToken cancellationToken = de

var context = new PeriodicTaskExecutionContext(name, executionId) { TaskType = typeof(TTask), };

// Invoke handler method, with retry if specified
var retryPolicy = options.RetryPolicy;
if (retryPolicy != null)
// Invoke handler method, with resilience pipeline if specified
var resiliencePipeline = options.ResiliencePipeline;
if (resiliencePipeline != null)
{
var contextData = new Dictionary<string, object> { ["context"] = context, };
await retryPolicy.ExecuteAsync((ctx, ct) => task.ExecuteAsync(context, cts.Token), contextData, cancellationToken).ConfigureAwait(false);
await resiliencePipeline.ExecuteAsync(async (ctx, ct) => await task.ExecuteAsync(context, cts.Token).ConfigureAwait(false), contextData, cancellationToken).ConfigureAwait(false);
}
else
{
Expand Down
6 changes: 3 additions & 3 deletions src/Tingle.PeriodicTasks/PeriodicTaskOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public class PeriodicTaskOptions
public string? LockName { get; set; }

/// <summary>
/// The retry policy to apply when executing the job.
/// The <see cref="Polly.ResiliencePipeline"/> to apply when executing the job.
/// This is an outer wrapper around the
/// <see cref="IPeriodicTask.ExecuteAsync(PeriodicTaskExecutionContext, CancellationToken)"/>
/// method.
Expand All @@ -98,7 +98,7 @@ public class PeriodicTaskOptions
/// </summary>
/// <remarks>
/// When a value is provided, the host may extend the duration of the distributed for the task
/// until the execution with retry policy completes successfully or not.
/// until the execution with this pipeline completes successfully or not.
/// </remarks>
public AsyncPolicy? RetryPolicy { get; set; }
public ResiliencePipeline? ResiliencePipeline { get; set; }
}
2 changes: 1 addition & 1 deletion src/Tingle.PeriodicTasks/Tingle.PeriodicTasks.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="8.0.0" />
<PackageReference Include="Polly" Version="8.2.0" />
<PackageReference Include="Polly.Core" Version="8.2.0" />
</ItemGroup>

</Project>

0 comments on commit 493f332

Please sign in to comment.