Skip to content

Commit

Permalink
Bridge between Polly V7 and Polly V8 (#1255)
Browse files Browse the repository at this point in the history
  • Loading branch information
martintmk authored Jun 6, 2023
1 parent 442895d commit b0a6a38
Show file tree
Hide file tree
Showing 9 changed files with 439 additions and 2 deletions.
1 change: 1 addition & 0 deletions src/Polly.Specs/Polly.Specs.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Polly.TestUtils\Polly.TestUtils.csproj" />
<ProjectReference Include="..\Polly\Polly.csproj" />
</ItemGroup>
</Project>
188 changes: 188 additions & 0 deletions src/Polly.Specs/ResilienceStrategyConversionExtensionsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
using Polly.Strategy;
using Polly.TestUtils;

namespace Polly.Specs;

public class ResilienceStrategyConversionExtensionsTests
{
private static readonly ResiliencePropertyKey<string> Incoming = new ResiliencePropertyKey<string>("incoming-key");

private static readonly ResiliencePropertyKey<string> Executing = new ResiliencePropertyKey<string>("executing-key");

private static readonly ResiliencePropertyKey<string> Outgoing = new ResiliencePropertyKey<string>("outgoing-key");

private readonly TestResilienceStrategy _strategy;
private readonly ResilienceStrategy<string> _genericStrategy;
private bool _isSynchronous;
private bool _isVoid;
private Type? _resultType;

public ResilienceStrategyConversionExtensionsTests()
{
_strategy = new TestResilienceStrategy();
_strategy.Before = (context, ct) =>
{
context.IsVoid.Should().Be(_isVoid);
context.IsSynchronous.Should().Be(_isSynchronous);
context.Properties.Set(Outgoing, "outgoing-value");
context.Properties.GetValue(Incoming, string.Empty).Should().Be("incoming-value");

if (_resultType != null)
{
context.ResultType.Should().Be(_resultType);
}
};

_genericStrategy = new ResilienceStrategyBuilder<string>()
.AddStrategy(_strategy)
.Build();
}

[Fact]
public void AsSyncPolicy_Ok()
{
_isVoid = true;
_isSynchronous = true;
var context = new Context
{
[Incoming.Key] = "incoming-value"
};

_strategy.AsSyncPolicy().Execute(c =>
{
context[Executing.Key] = "executing-value";
},
context);

AssertContext(context);
}

[Fact]
public void AsSyncPolicy_Generic_Ok()
{
_isVoid = false;
_isSynchronous = true;
_resultType = typeof(string);
var context = new Context
{
[Incoming.Key] = "incoming-value"
};

var result = _genericStrategy.AsSyncPolicy().Execute(c => { context[Executing.Key] = "executing-value"; return "dummy"; }, context);
AssertContext(context);
result.Should().Be("dummy");
}

[Fact]
public void AsSyncPolicy_Result_Ok()
{
_isVoid = false;
_isSynchronous = true;
_resultType = typeof(string);
var context = new Context
{
[Incoming.Key] = "incoming-value"
};

var result = _strategy.AsSyncPolicy().Execute(c => { context[Executing.Key] = "executing-value"; return "dummy"; }, context);

AssertContext(context);
result.Should().Be("dummy");
}

[Fact]
public async Task AsAsyncPolicy_Ok()
{
_isVoid = true;
_isSynchronous = false;
var context = new Context
{
[Incoming.Key] = "incoming-value"
};

await _strategy.AsAsyncPolicy().ExecuteAsync(c =>
{
context[Executing.Key] = "executing-value";
return Task.CompletedTask;
},
context);

AssertContext(context);
}

[Fact]
public async Task AsAsyncPolicy_Generic_Ok()
{
_isVoid = false;
_isSynchronous = false;
_resultType = typeof(string);
var context = new Context
{
[Incoming.Key] = "incoming-value"
};

var result = await _genericStrategy.AsAsyncPolicy().ExecuteAsync(c =>
{
context[Executing.Key] = "executing-value";
return Task.FromResult("dummy");
},
context);
AssertContext(context);
result.Should().Be("dummy");
}

[Fact]
public async Task AsAsyncPolicy_Result_Ok()
{
_isVoid = false;
_isSynchronous = false;
_resultType = typeof(string);
var context = new Context
{
[Incoming.Key] = "incoming-value"
};

var result = await _strategy.AsAsyncPolicy().ExecuteAsync(c =>
{
context[Executing.Key] = "executing-value";
return Task.FromResult("dummy");
},
context);

AssertContext(context);
result.Should().Be("dummy");
}

[Fact]
public void RetryStrategy_AsSyncPolicy_Ok()
{
var policy = new ResilienceStrategyBuilder<string>()
.AddRetry(new RetryStrategyOptions<string>
{
ShouldRetry = _ => PredicateResult.True,
BackoffType = RetryBackoffType.Constant,
RetryCount = 5,
BaseDelay = TimeSpan.FromMilliseconds(1)
})
.Build()
.AsSyncPolicy();

var tries = 0;
policy.Execute(
() =>
{
tries++;
return "dummy";
})
.Should()
.Be("dummy");

tries.Should().Be(6);
}

private static void AssertContext(Context context)
{
context[Outgoing.Key].Should().Be("outgoing-value");
context[Executing.Key].Should().Be("executing-value");
}
}
4 changes: 2 additions & 2 deletions src/Polly/Context.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Polly;
namespace Polly;

/// <summary>
/// Context that carries with a single execution through a Policy. Commonly-used properties are directly on the class. Backed by a dictionary of string key / object value pairs, to which user-defined values may be added.
Expand Down Expand Up @@ -37,7 +37,7 @@ public Context()
/// <summary>
/// A key unique to the call site of the current execution.
/// <remarks>Policy instances are commonly reused across multiple call sites. Set an OperationKey so that logging and metrics can distinguish usages of policy instances at different call sites.</remarks>
/// <remarks>The value is set by using the <see cref="Context(String)"/> constructor taking an operationKey parameter.</remarks>
/// <remarks>The value is set by using the <see cref="Context(string)"/> constructor taking an operationKey parameter.</remarks>
/// </summary>
public string OperationKey { get; }

Expand Down
46 changes: 46 additions & 0 deletions src/Polly/ResilienceStrategyConversionExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using Polly.Utilities.Wrappers;
using Polly.Utils;

namespace Polly;

/// <summary>
/// Extensions for conversion of resilience strategies to policies.
/// </summary>
public static class ResilienceStrategyConversionExtensions
{
/// <summary>
/// Converts a <see cref="ResilienceStrategy"/> to an <see cref="IAsyncPolicy"/>.
/// </summary>
/// <param name="strategy">The strategy instance.</param>
/// <returns>An instance of <see cref="IAsyncPolicy"/>.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="strategy"/> is <see langword="null"/>.</exception>
public static IAsyncPolicy AsAsyncPolicy(this ResilienceStrategy strategy)
=> new ResilienceStrategyAsyncPolicy(strategy ?? throw new ArgumentNullException(nameof(strategy)));

/// <summary>
/// Converts a <see cref="ResilienceStrategy{TResult}"/> to an <see cref="IAsyncPolicy{TResult}"/>.
/// </summary>
/// <param name="strategy">The strategy instance.</param>
/// <returns>An instance of <see cref="IAsyncPolicy{TResult}"/>.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="strategy"/> is <see langword="null"/>.</exception>
public static IAsyncPolicy<TResult> AsAsyncPolicy<TResult>(this ResilienceStrategy<TResult> strategy)
=> new ResilienceStrategyAsyncPolicy<TResult>(strategy ?? throw new ArgumentNullException(nameof(strategy)));

/// <summary>
/// Converts a <see cref="ResilienceStrategy"/> to an <see cref="ISyncPolicy"/>.
/// </summary>
/// <param name="strategy">The strategy instance.</param>
/// <returns>An instance of <see cref="ISyncPolicy"/>.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="strategy"/> is <see langword="null"/>.</exception>
public static ISyncPolicy AsSyncPolicy(this ResilienceStrategy strategy)
=> new ResilienceStrategySyncPolicy(strategy ?? throw new ArgumentNullException(nameof(strategy)));

/// <summary>
/// Converts a <see cref="ResilienceStrategy{TResult}"/> to an <see cref="ISyncPolicy{TResult}"/>.
/// </summary>
/// <param name="strategy">The strategy instance.</param>
/// <returns>An instance of <see cref="ISyncPolicy{TResult}"/>.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="strategy"/> is <see langword="null"/>.</exception>
public static ISyncPolicy<TResult> AsSyncPolicy<TResult>(this ResilienceStrategy<TResult> strategy)
=> new ResilienceStrategySyncPolicy<TResult>(strategy ?? throw new ArgumentNullException(nameof(strategy)));
}
42 changes: 42 additions & 0 deletions src/Polly/Utilities/Wrappers/ResilienceContextFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
namespace Polly.Utilities.Wrappers;

internal static class ResilienceContextFactory
{
public static readonly ResiliencePropertyKey<Context> ContextKey = new("Polly.Legacy.Context");

public static ResilienceContext Create(Context context, CancellationToken cancellationToken, bool continueOnCapturedContext)
{
var resilienceContext = ResilienceContext.Get();
resilienceContext.CancellationToken = cancellationToken;
resilienceContext.ContinueOnCapturedContext = continueOnCapturedContext;

foreach (var pair in context)
{
var props = (IDictionary<string, object>)resilienceContext.Properties;
props.Add(pair.Key, pair.Value);
}

resilienceContext.Properties.Set(ContextKey, context);

return resilienceContext;
}

public static void Restore(ResilienceContext context)
{
var originalContext = context.GetContext();

foreach (var pair in context.Properties)
{
if (pair.Key == ContextKey.Key)
{
continue;
}

originalContext[pair.Key] = pair.Value;
}

ResilienceContext.Return(context);
}

public static Context GetContext(this ResilienceContext resilienceContext) => resilienceContext.Properties.GetValue(ContextKey, null!);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
namespace Polly.Utilities.Wrappers;

internal sealed class ResilienceStrategyAsyncPolicy<TResult> : AsyncPolicy<TResult>
{
private readonly ResilienceStrategy<TResult> _strategy;

public ResilienceStrategyAsyncPolicy(ResilienceStrategy<TResult> strategy) => _strategy = strategy;

protected sealed override async Task<TResult> ImplementationAsync(Func<Context, CancellationToken, Task<TResult>> action, Context context, CancellationToken cancellationToken, bool continueOnCapturedContext)
{
var resilienceContext = ResilienceContextFactory.Create(context, cancellationToken, continueOnCapturedContext);

try
{
return await _strategy.ExecuteAsync(
static async (context, state) =>
{
return await state(context.GetContext(), context.CancellationToken).ConfigureAwait(context.ContinueOnCapturedContext);
},
resilienceContext,
action).ConfigureAwait(continueOnCapturedContext);
}
finally
{
ResilienceContextFactory.Restore(resilienceContext);
}
}
}
56 changes: 56 additions & 0 deletions src/Polly/Utilities/Wrappers/ResilienceStrategyAsyncPolicy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
namespace Polly.Utilities.Wrappers;

internal sealed class ResilienceStrategyAsyncPolicy : AsyncPolicy
{
private readonly ResilienceStrategy _strategy;

public ResilienceStrategyAsyncPolicy(ResilienceStrategy strategy) => _strategy = strategy;

protected sealed override async Task ImplementationAsync(
Func<Context, CancellationToken, Task> action,
Context context,
CancellationToken cancellationToken,
bool continueOnCapturedContext)
{
var resilienceContext = ResilienceContextFactory.Create(context, cancellationToken, continueOnCapturedContext);

try
{
await _strategy.ExecuteAsync(
static async (context, state) =>
{
await state(context.GetContext(), context.CancellationToken).ConfigureAwait(context.ContinueOnCapturedContext);
},
resilienceContext,
action).ConfigureAwait(continueOnCapturedContext);
}
finally
{
ResilienceContextFactory.Restore(resilienceContext);
}
}

protected override async Task<TResult> ImplementationAsync<TResult>(
Func<Context, CancellationToken, Task<TResult>> action,
Context context,
CancellationToken cancellationToken,
bool continueOnCapturedContext)
{
var resilienceContext = ResilienceContextFactory.Create(context, cancellationToken, continueOnCapturedContext);

try
{
return await _strategy.ExecuteAsync(
static async (context, state) =>
{
return await state(context.GetContext(), context.CancellationToken).ConfigureAwait(context.ContinueOnCapturedContext);
},
resilienceContext,
action).ConfigureAwait(continueOnCapturedContext);
}
finally
{
ResilienceContextFactory.Restore(resilienceContext);
}
}
}
Loading

0 comments on commit b0a6a38

Please sign in to comment.