Skip to content
This repository has been archived by the owner on Jan 12, 2023. It is now read-only.

Refactor and add test case. #1

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 12 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,18 @@
# Polly.Contrib.BlankTemplate
# Polly.Contrib.RequestHedging

Polly.Contrib.BlankTemplate is blank template to use a starting point to develop contributions to Polly.Contrib.
Polly.Contrib.RequestHedginge allow the sequential and delayed execution of a set of tasks, until one of them completes successfully or until all of them are completed.
First task that is successfully completed triggers the cancellation and disposal of all other tasks and/or adjacent allocated resources.

_If you are looking to develop a custom policy, start instead from the repo_ [Polly.Contrib.CustomPolicyTemplates](https://github.com/Polly-Contrib/Polly.Contrib.CustomPolicyTemplates).
# Installing via NuGet

## How to use the template
Install-Package Polly.Contrib.RequestHedging

This repo is essentially just a blank template of a solution that:
# Usage

+ references [Polly](https://github.com/App-vNext/Polly)
+ contains multi-targeting for the current compilation targets which Polly supports
- builds against Net Standard 1.1 and Net Standard 2.0
- tests againt .Net Core 1.1, .Net core 2.0, .Net Framework 4.6.2, .Net Framework 4.7.2
+ contains a build script which builds to a nuget package styled `Polly.Contrib.X`
``` C#
PolicyBuilder[<TResult>] policyBuilder;

## Getting your contribution included in Polly.Contrib

Reach out to the Polly team at our [slack channel](http://pollytalk.slack.com) or the main [Polly project Github](https://github.com/App-vNext/Polly).

We can set up a repo in the Polly.Contrib organisation - you'll have full rights to this repo, to manage and deliver your awesomeness to the Polly community!

If you already have your contribution in a github repo, we can also just move the existing repo into the Polly.Contrib org - you still retain full rights over the repo and management of the content, but the contrib gets official recognition under the Polly.Contrib banner.
policyBuilder.HedgeAsync(maxAttemptCount: 2,
hedgingDelay: TimeSpan.FromMilliseconds(100),
onHedgeAsync: context => Task.CompletedTask);
```
102 changes: 102 additions & 0 deletions src/Polly.Contrib.RequestHedging.Specs/AsyncHedgingSpecs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Xunit;

namespace Polly.Contrib.RequestHedging.Specs
{
public class AsyncHedgingSpecs
{
[Fact]
public async Task Should_ExceptionPredicates_Invoked_But_Not_Match()
{
var hedgeCount = 0;

await Assert.ThrowsAsync<TimeoutException>(() => Policy.Handle<InvalidOperationException>().HedgeAsync(
1, TimeSpan.FromMilliseconds(100), _ =>
{
Interlocked.Increment(ref hedgeCount);

return Task.CompletedTask;
})
.ExecuteAsync(() => throw new TimeoutException()));

Assert.Equal(0, hedgeCount);
}

[Fact]
public async Task Should_ExceptionPredicates_Invoked_And_Matched()
{
var hedgeCount = 0;
var invoked = false;

await Policy.Handle<InvalidOperationException>().HedgeAsync(
1, TimeSpan.FromMilliseconds(100), _ =>
{
Interlocked.Increment(ref hedgeCount);

return Task.CompletedTask;
})
.ExecuteAsync(() =>
{
if (invoked) return Task.CompletedTask;

invoked = true;

throw new InvalidOperationException();
});

Assert.Equal(1, hedgeCount);
Assert.True(invoked);
}

[Fact]
public Task Always_Throw_Exception_And_Match() =>
Assert.ThrowsAsync<TimeoutException>(() => Policy.Handle<TimeoutException>()
.HedgeAsync(3, TimeSpan.FromMilliseconds(100))
.ExecuteAsync(() => throw new TimeoutException()));

[Fact]
public async Task OnHedgeAsync_Count_Should_Equal_AttemptCount()
{
const int maxAttemptCount = 10;
const int maxValue = 5;

var hedgeCount = 0;
var invokeCount = 0;

await Policy.Handle<InvalidOperationException>()
.HedgeAsync(maxAttemptCount, TimeSpan.FromMilliseconds(20), _ =>
{
Interlocked.Increment(ref hedgeCount);

return Task.CompletedTask;
}).ExecuteAsync(() =>
{
if (invokeCount++ < maxValue) throw new InvalidOperationException();

return Task.CompletedTask;
});

Assert.Equal(maxValue, hedgeCount);
}

[Fact]
public async Task OnHedgeAsync_Count_Should_Equal_MaxAttemptCount()
{
const int maxAttemptCount = 10;

var hedgeCount = 0;

await Assert.ThrowsAsync<TimeoutException>(() => Policy.Handle<TimeoutException>()
.HedgeAsync(maxAttemptCount, TimeSpan.FromMilliseconds(20), _ =>
{
Interlocked.Increment(ref hedgeCount);

return Task.CompletedTask;
}).ExecuteAsync(() => throw new TimeoutException()));

Assert.Equal(maxAttemptCount, hedgeCount);
}
}
}
233 changes: 233 additions & 0 deletions src/Polly.Contrib.RequestHedging.Specs/AsyncHedgingTResultSpecs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Xunit;

namespace Polly.Contrib.RequestHedging.Specs
{
public class AsyncHedgingTResultSpecs
{
[Fact]
public async Task Should_ExceptionPredicates_Invoked_But_Not_Match()
{
var hedgeCount = 0;

await Assert.ThrowsAsync<TimeoutException>(() => Policy<int>.Handle<InvalidOperationException>().HedgeAsync(
1, TimeSpan.FromMilliseconds(100), _ =>
{
Interlocked.Increment(ref hedgeCount);

return Task.CompletedTask;
})
.ExecuteAsync(() => throw new TimeoutException()));

Assert.Equal(0, hedgeCount);
}

[Fact]
public async Task Should_ExceptionPredicates_Invoked_And_Matched()
{
var hedgeCount = 0;
var invoked = false;

await Policy<int>.Handle<InvalidOperationException>().HedgeAsync(
1, TimeSpan.FromMilliseconds(100), _ =>
{
Interlocked.Increment(ref hedgeCount);

return Task.CompletedTask;
})
.ExecuteAsync(() =>
{
if (invoked) return Task.FromResult(1);

invoked = true;

throw new InvalidOperationException();
});

Assert.Equal(1, hedgeCount);
Assert.True(invoked);
}

[Fact]
public Task Always_Throw_Exception_And_Match() =>
Assert.ThrowsAsync<TimeoutException>(() => Policy<bool>.Handle<TimeoutException>()
.HedgeAsync(3, TimeSpan.FromMilliseconds(100))
.ExecuteAsync(() => throw new TimeoutException()));

[Fact]
public async Task Should_ResultPredicates_Invoked()
{
var invokeCount = 0;

await Policy<int>.HandleResult(x => x < 1).HedgeAsync(3, TimeSpan.FromMilliseconds(100))
.ExecuteAsync(() => Task.FromResult(invokeCount++));

Assert.Equal(2, invokeCount);
}

[Fact]
public async Task OnHedgeAsync_Count_Should_Equal_AttemptCount()
{
const int maxAttemptCount = 10;
const int maxValue = 5;

var hedgeCount = 0;
var invokeCount = 0;

await Policy<int>.HandleResult(x => x < maxValue)
.HedgeAsync(maxAttemptCount, TimeSpan.FromMilliseconds(20), _ =>
{
Interlocked.Increment(ref hedgeCount);

return Task.CompletedTask;
}).ExecuteAsync(() => Task.FromResult(invokeCount++));

Assert.Equal(maxValue, hedgeCount);
}

[Fact]
public async Task OnHedgeAsync_Count_Should_Equal_MaxAttemptCount()
{
const int maxAttemptCount = 10;

var hedgeCount = 0;

await Policy<int>.HandleResult(x => x < 10)
.HedgeAsync(maxAttemptCount, TimeSpan.FromMilliseconds(20), _ =>
{
Interlocked.Increment(ref hedgeCount);

return Task.CompletedTask;
}).ExecuteAsync(() => Task.FromResult(0));

Assert.Equal(maxAttemptCount, hedgeCount);
}

[Fact]
public async Task Should_Prefer_Result_InsteadOf_Exception()
{
var invoked = false;

Assert.False(await Policy<bool>.HandleResult(x => !x).Or<InvalidOperationException>()
.HedgeAsync(1, TimeSpan.FromMilliseconds(100))
.ExecuteAsync(() =>
{
if (invoked) throw new InvalidOperationException();

invoked = true;

return Task.FromResult(false);
}));
}

[Fact]
public async Task Should_Return_The_Fastest_Result()
{
var array = new[] { (500, 3), (250, 15), (200, 20) };
var count = 0;

Assert.Equal(15, await Policy<int>.HandleResult(x => x < 10).HedgeAsync(array.Length - 1, TimeSpan.FromMilliseconds(100))
.ExecuteAsync(async () =>
{
var item = array[count++];

await Task.Delay(item.Item1);

return item.Item2;
}));
}

[Fact]
public async Task Should_Return_The_Fastest_Result_Slowly()
{
var array = new[] { (500, 3), (350, 15), (200, 20) };
var count = 0;

Assert.Equal(20, await Policy<int>.HandleResult(x => x < 10).HedgeAsync(array.Length - 1, TimeSpan.FromMilliseconds(100))
.ExecuteAsync(async () =>
{
var item = array[count++];

await Task.Delay(item.Item1);

return item.Item2;
}));
}

[Fact]
public async Task Completed_Before_Any_Hedge_Request()
{
var invokeCount = 0;

Assert.Equal(0, await Policy<int>.Handle<Exception>().HedgeAsync(10, TimeSpan.FromMilliseconds(1000))
.ExecuteAsync(async () =>
{
await Task.Delay(50);

return invokeCount++;
}));
}

[Fact]
public async Task Return_Least_Matched_Result()
{
var invokeCount = 0;

Assert.Equal(3, await Policy<int>.HandleResult(x => x < 5).HedgeAsync(3, TimeSpan.FromMilliseconds(100))
.ExecuteAsync(async () =>
{
await Task.Delay(50);

return invokeCount++;
}));
}

[Fact]
public async Task When_Cancel()
{
using (var cts = new CancellationTokenSource(500))
{
await Assert.ThrowsAsync<TaskCanceledException>(() =>
Policy<int>.HandleResult(x => x < 10).HedgeAsync(10, TimeSpan.FromMilliseconds(100))
.ExecuteAsync(async token =>
{
await Task.Delay(10000, token);

return 1;
}, cts.Token));
}
}

[Fact]
public async Task First_Request_Completed_Successfully_Triggers_The_Cancellation_Of_All_Others()
{
var array = new[] { (5000, 3), (3500, 15), (100, 20) };
var count = 0;
var cancelled = 0;

Assert.Equal(20, await Policy<int>.HandleResult(x => x < 10).HedgeAsync(array.Length - 1, TimeSpan.FromMilliseconds(100))
.ExecuteAsync(async token =>
{
var item = array[count++];

try
{
await Task.Delay(item.Item1, token);
}
catch (OperationCanceledException) when (token.IsCancellationRequested)
{
Interlocked.Increment(ref cancelled);

throw;
}

return item.Item2;
}, CancellationToken.None));

Assert.Equal(2, cancelled);

}
}
}
13 changes: 0 additions & 13 deletions src/Polly.Contrib.RequestHedging.Specs/MyContribSpecs.cs

This file was deleted.

Loading