Skip to content

Commit 2830a29

Browse files
committed
fix
1 parent 842f5dd commit 2830a29

File tree

9 files changed

+129
-62
lines changed

9 files changed

+129
-62
lines changed

Eocron.DependencyInjection.Interceptors/Caching/MemoryCacheAsyncInterceptor.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@
33
using System.Reflection;
44
using System.Threading.Tasks;
55
using Castle.DynamicProxy;
6-
using Eocron.DependencyInjection.Interceptors.Caching;
76
using Microsoft.Extensions.Caching.Memory;
87

9-
namespace Eocron.DependencyInjection.Interceptors
8+
namespace Eocron.DependencyInjection.Interceptors.Caching
109
{
1110
public sealed class MemoryCacheAsyncInterceptor : IAsyncInterceptor
1211
{

Eocron.DependencyInjection.Interceptors/DecoratorChainExtensions.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
using System;
22
using System.Reflection;
33
using Castle.DynamicProxy;
4+
using Eocron.DependencyInjection.Interceptors.Caching;
5+
using Eocron.DependencyInjection.Interceptors.Logging;
46
using Eocron.DependencyInjection.Interceptors.Retry;
7+
using Eocron.DependencyInjection.Interceptors.Timeout;
58
using Microsoft.Extensions.Caching.Memory;
69
using Microsoft.Extensions.DependencyInjection;
710
using Microsoft.Extensions.Logging;
@@ -24,6 +27,16 @@ public static DecoratorChain AddInterceptor(this DecoratorChain decoratorChain,
2427
return decoratorChain;
2528
}
2629

30+
public static DecoratorChain AddTracing(this DecoratorChain decoratorChain)
31+
{
32+
decoratorChain.AddInterceptor((sp) =>
33+
new LoggingAsyncInterceptor(
34+
sp.GetService<ILoggerFactory>().CreateLogger(decoratorChain.ServiceType.FullName),
35+
LogLevel.Trace,
36+
LogLevel.Error));
37+
return decoratorChain;
38+
}
39+
2740
public static DecoratorChain AddTimeout(this DecoratorChain decoratorChain, TimeSpan timeout)
2841
{
2942
if (timeout <= TimeSpan.Zero)
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
using System;
2+
using System.Diagnostics;
3+
using System.Threading.Tasks;
4+
using Castle.DynamicProxy;
5+
using Microsoft.Extensions.Logging;
6+
7+
namespace Eocron.DependencyInjection.Interceptors.Logging
8+
{
9+
public sealed class LoggingAsyncInterceptor : IAsyncInterceptor
10+
{
11+
private readonly ILogger _logger;
12+
private readonly LogLevel _onTrace;
13+
private readonly LogLevel _onError;
14+
15+
public LoggingAsyncInterceptor(
16+
ILogger logger,
17+
LogLevel onTrace,
18+
LogLevel onError)
19+
{
20+
_logger = logger;
21+
_onTrace = onTrace;
22+
_onError = onError;
23+
}
24+
25+
public void InterceptSynchronous(IInvocation invocation)
26+
{
27+
ExecuteSync(invocation);
28+
}
29+
30+
public void InterceptAsynchronous(IInvocation invocation)
31+
{
32+
invocation.ReturnValue = ExecuteAsync(invocation);
33+
}
34+
35+
public void InterceptAsynchronous<TResult>(IInvocation invocation)
36+
{
37+
invocation.ReturnValue = ExecuteAsync<TResult>(invocation);
38+
}
39+
40+
private void ExecuteSync(IInvocation invocation)
41+
{
42+
var sw = Stopwatch.StartNew();
43+
_logger?.Log(_onTrace, "Call {invocation}", invocation.Method.Name);
44+
try
45+
{
46+
invocation.Proceed();
47+
_logger?.Log(_onTrace, "Call {invocation} ended. Elapsed: {elapsed}", invocation.Method.Name, sw.Elapsed);
48+
return;
49+
}
50+
catch (Exception ex)
51+
{
52+
_logger?.Log(_onError, ex, "Failed to invoke {invocation}. Elapsed: {elapsed}", invocation.Method.Name, sw.Elapsed);
53+
throw;
54+
}
55+
}
56+
57+
private async Task ExecuteAsync(IInvocation invocation)
58+
{
59+
var sw = Stopwatch.StartNew();
60+
_logger?.Log(_onTrace, "Call {invocation}", invocation.Method.Name);
61+
try
62+
{
63+
invocation.Proceed();
64+
var task = (Task)invocation.ReturnValue;
65+
await task.ConfigureAwait(false);
66+
_logger?.Log(_onTrace, "Call {invocation} ended. Elapsed: {elapsed}", invocation.Method.Name, sw.Elapsed);
67+
}
68+
catch (Exception ex)
69+
{
70+
_logger?.Log(_onError, ex, "Failed to invoke {invocation}. Elapsed: {elapsed}", invocation.Method.Name, sw.Elapsed);
71+
throw;
72+
}
73+
}
74+
75+
private async Task<T> ExecuteAsync<T>(IInvocation invocation)
76+
{
77+
var sw = Stopwatch.StartNew();
78+
_logger?.Log(_onTrace, "Call {invocation}", invocation.Method.Name);
79+
try
80+
{
81+
invocation.Proceed();
82+
var task = (Task<T>)invocation.ReturnValue;
83+
var result = await task.ConfigureAwait(false);
84+
_logger?.Log(_onTrace, "Call {invocation} ended. Elapsed: {elapsed}", invocation.Method.Name, sw.Elapsed);
85+
return result;
86+
}
87+
catch (Exception ex)
88+
{
89+
_logger?.Log(_onError, ex, "Failed to invoke {invocation}. Elapsed: {elapsed}", invocation.Method.Name, sw.Elapsed);
90+
throw;
91+
}
92+
}
93+
}
94+
}

Eocron.DependencyInjection.Interceptors/Retry/CorrelatedExponentialBackoff.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,20 @@ public static class CorrelatedExponentialBackoff
77
public static TimeSpan Calculate(Random random, int attempt, TimeSpan propagationDuration,
88
TimeSpan maxPropagationDuration, bool jittered)
99
{
10-
if(attempt < 0)
10+
if(attempt < 1)
1111
throw new ArgumentOutOfRangeException(nameof(attempt));
1212
if(propagationDuration >= maxPropagationDuration)
1313
throw new ArgumentOutOfRangeException(nameof(propagationDuration), "Minimum propagation duration must be less than max propagation duration.");
1414
if (maxPropagationDuration <= TimeSpan.Zero)
1515
throw new ArgumentOutOfRangeException(nameof(maxPropagationDuration), "Maximum propagation duration must be greater than zero.");
16-
16+
17+
var power = attempt - 1;
1718
var minPropagationMs = Math.Max((int)propagationDuration.TotalMilliseconds, 5); //min time it takes to process single request
1819
var maxPropagationMs = Math.Max(minPropagationMs, (int)maxPropagationDuration.TotalMilliseconds); //max time it takes to process single request
19-
var maxAttemptExp = (int)Math.Floor(Math.Log2((maxPropagationMs - minPropagationMs) / minPropagationMs));
20-
if (maxAttemptExp >= attempt)
20+
var maxPower = (int)Math.Floor(Math.Log2((maxPropagationMs - minPropagationMs) / minPropagationMs));
21+
if (maxPower >= power)
2122
{
22-
var duration = minPropagationMs * (1 << attempt);
23+
var duration = minPropagationMs * (1 << power);
2324
return TimeSpan.FromMilliseconds(jittered ? random.Next(duration) : duration);
2425
}
2526
else

Eocron.DependencyInjection.Interceptors/Retry/RetryUntilConditionAsyncInterceptor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
using Castle.DynamicProxy;
55
using Microsoft.Extensions.Logging;
66

7-
namespace Eocron.DependencyInjection.Interceptors
7+
namespace Eocron.DependencyInjection.Interceptors.Retry
88
{
99
public sealed class RetryUntilConditionAsyncInterceptor : IAsyncInterceptor
1010
{

Eocron.DependencyInjection.Interceptors/Timeout/TimeoutAsyncInterceptor.cs

Lines changed: 8 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@
33
using System.Threading.Tasks;
44
using Castle.DynamicProxy;
55

6-
namespace Eocron.DependencyInjection.Interceptors
6+
namespace Eocron.DependencyInjection.Interceptors.Timeout
77
{
88
public sealed class TimeoutAsyncInterceptor : AsyncInterceptorBase
99
{
1010
private readonly TimeSpan _timeout;
1111

1212
public TimeoutAsyncInterceptor(TimeSpan timeout)
1313
{
14-
if (timeout <= TimeSpan.Zero || timeout == Timeout.InfiniteTimeSpan)
14+
if (timeout <= TimeSpan.Zero || timeout == System.Threading.Timeout.InfiniteTimeSpan)
1515
{
1616
throw new ArgumentOutOfRangeException($"Invalid timeout provided: {timeout}");
1717
}
@@ -37,57 +37,15 @@ protected override async Task<TResult> InterceptAsync<TResult>(IInvocation invoc
3737
using var cts = CancellationTokenSource.CreateLinkedTokenSource(rootCt);
3838
InterceptionHelper.TryReplaceCancellationToken(invocation, cts.Token);
3939

40-
var tcs = new TaskCompletionSource<TResult>();
41-
42-
async Task TimeoutJob()
43-
{
44-
await InterceptionHelper.SafeDelay(_timeout).ConfigureAwait(false);
45-
tcs.TrySetException(CreateTimeoutException(invocation));
46-
}
47-
48-
async Task ProceedJob()
49-
{
50-
await Task.Yield();
51-
cts.CancelAfter(_timeout);
52-
TResult result;
53-
try
54-
{
55-
result = await proceed(invocation, proceedInfo);
56-
if (IsTimedOut())
57-
{
58-
tcs.TrySetException(CreateTimeoutException(invocation));
59-
}
60-
else
61-
{
62-
tcs.TrySetResult(result);
63-
}
64-
}
65-
catch (Exception e) when (IsTimedOut())
66-
{
67-
tcs.TrySetException(CreateTimeoutException(invocation, e));
68-
}
69-
catch (Exception e)
70-
{
71-
tcs.TrySetException(e);
72-
}
73-
}
74-
75-
bool IsTimedOut()
40+
var task = Task.Run(() => proceed(invocation, proceedInfo));
41+
var completedTask = await Task.WhenAny(task, Task.Delay(_timeout, cts.Token));
42+
if (completedTask == task || rootCt.IsCancellationRequested)
7643
{
77-
return cts.IsCancellationRequested && !rootCt.IsCancellationRequested;
44+
await cts.CancelAsync().ConfigureAwait(false);
45+
return await task.ConfigureAwait(false); // Very important in order to propagate exceptions
7846
}
7947

80-
81-
#pragma warning disable CS4014
82-
TimeoutJob();
83-
ProceedJob();
84-
#pragma warning restore CS4014
85-
return await tcs.Task.ConfigureAwait(false);
86-
}
87-
88-
private Exception CreateTimeoutException(IInvocation invocation, Exception e = null)
89-
{
90-
return new TimeoutException($"Method {invocation} timed out after {_timeout}", e);
48+
throw new TimeoutException($"Method {invocation} timed out after {_timeout}");
9149
}
9250
}
9351
}

Eocron.DependencyInjection.Tests/MemoryCacheInterceptorTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Threading.Tasks;
44
using Castle.DynamicProxy;
55
using Eocron.DependencyInjection.Interceptors;
6+
using Eocron.DependencyInjection.Interceptors.Caching;
67
using FluentAssertions;
78
using Moq;
89
using NUnit.Framework;

Eocron.DependencyInjection.Tests/RetryUntilConditionInterceptorTests.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public void CorrelatedExponentialBackoff_Check()
2929
{
3030
var rnd = new Random(42);
3131
var expectedMs = new[] {5, 10, 20, 40, 80, 160, 320, 640, 1280, 2560, 5120, 10240, 20480, 40960, 60000, 60000, 60000, 60000, 60000, 60000};
32-
var actualMs = Enumerable.Range(0, 20).Select(x=> (int)CorrelatedExponentialBackoff.Calculate(rnd, x, TimeSpan.Zero, TimeSpan.FromSeconds(60), false).TotalMilliseconds).ToArray();
32+
var actualMs = Enumerable.Range(1, 20).Select(x=> (int)CorrelatedExponentialBackoff.Calculate(rnd, x, TimeSpan.Zero, TimeSpan.FromSeconds(60), false).TotalMilliseconds).ToArray();
3333
actualMs.Should().Equal(expectedMs);
3434
}
3535

@@ -38,7 +38,7 @@ public void CorrelatedExponentialBackoffJittered_Check()
3838
{
3939
var rnd = new Random(42);
4040
var expectedMs = new[] {3, 1, 2, 20, 13, 42, 231, 328, 222, 1948, 1201, 2634, 10354, 13116, 22858, 15614, 31047, 2119, 48848, 34631};
41-
var actualMs = Enumerable.Range(0, 20).Select(x=> (int)CorrelatedExponentialBackoff.Calculate(rnd, x, TimeSpan.Zero, TimeSpan.FromSeconds(60), true).TotalMilliseconds).ToArray();
41+
var actualMs = Enumerable.Range(1, 20).Select(x=> (int)CorrelatedExponentialBackoff.Calculate(rnd, x, TimeSpan.Zero, TimeSpan.FromSeconds(60), true).TotalMilliseconds).ToArray();
4242
actualMs.Should().Equal(expectedMs);
4343
}
4444

@@ -47,7 +47,7 @@ public void ConstantBackoff_Check()
4747
{
4848
var rnd = new Random(42);
4949
var expectedMs = new[] {60000, 60000, 60000, 60000, 60000, 60000, 60000, 60000, 60000, 60000, 60000, 60000, 60000, 60000, 60000, 60000, 60000, 60000, 60000, 60000};
50-
var actualMs = Enumerable.Range(0, 20).Select(x=> (int)ConstantBackoff.Calculate(rnd, TimeSpan.FromSeconds(60), false).TotalMilliseconds).ToArray();
50+
var actualMs = Enumerable.Range(1, 20).Select(x=> (int)ConstantBackoff.Calculate(rnd, TimeSpan.FromSeconds(60), false).TotalMilliseconds).ToArray();
5151
actualMs.Should().Equal(expectedMs);
5252
}
5353

@@ -56,7 +56,7 @@ public void ConstantBackoffJittered_Check()
5656
{
5757
var rnd = new Random(42);
5858
var expectedMs = new[] {40086, 8454, 7531, 31365, 10106, 15755, 43464, 30775, 10419, 45675, 14075, 15439, 30336, 19213, 22858, 15614, 31047, 2119, 48848, 34631};
59-
var actualMs = Enumerable.Range(0, 20).Select(x=> (int)ConstantBackoff.Calculate(rnd, TimeSpan.FromSeconds(60), true).TotalMilliseconds).ToArray();
59+
var actualMs = Enumerable.Range(1, 20).Select(x=> (int)ConstantBackoff.Calculate(rnd, TimeSpan.FromSeconds(60), true).TotalMilliseconds).ToArray();
6060
actualMs.Should().Equal(expectedMs);
6161
}
6262

Eocron.DependencyInjection.Tests/TimeoutInterceptorTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Threading.Tasks;
44
using Castle.DynamicProxy;
55
using Eocron.DependencyInjection.Interceptors;
6+
using Eocron.DependencyInjection.Interceptors.Timeout;
67
using FluentAssertions;
78
using Moq;
89
using NUnit.Framework;

0 commit comments

Comments
 (0)