Skip to content

Commit 0ef7098

Browse files
authored
Fixed DataLoader DeadLock in tests. (#6962)
1 parent 633d0b8 commit 0ef7098

File tree

4 files changed

+43
-25
lines changed

4 files changed

+43
-25
lines changed

src/GreenDonut/src/Core/GreenDonut.csproj

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,13 @@
2828
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
2929
</ItemGroup>
3030

31+
<ItemGroup>
32+
<Compile Update="DataLoaderBase.Fetch.cs">
33+
<DependentUpon>DataLoaderBase.cs</DependentUpon>
34+
</Compile>
35+
<Compile Update="DataLoaderBase.IDataLoader.cs">
36+
<DependentUpon>DataLoaderBase.cs</DependentUpon>
37+
</Compile>
38+
</ItemGroup>
39+
3140
</Project>

src/GreenDonut/test/Core.Tests/DataLoaderTests.cs

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,31 @@
33
using System.Linq;
44
using System.Threading;
55
using System.Threading.Tasks;
6+
using Microsoft.Extensions.DependencyInjection;
67
using Snapshooter.Xunit;
78
using Xunit;
9+
using Xunit.Abstractions;
810
using static GreenDonut.TestHelpers;
911
// ReSharper disable CollectionNeverUpdated.Local
1012
// ReSharper disable InconsistentNaming
1113

1214
namespace GreenDonut;
1315

14-
public class DataLoaderTests
16+
public class DataLoaderTests(ITestOutputHelper output)
1517
{
1618
[Fact(DisplayName = "Clear: Should not throw any exception")]
1719
public void ClearNoException()
1820
{
1921
// arrange
2022
var fetch = CreateFetch<string, string>();
21-
var batchScheduler = new ManualBatchScheduler();
22-
var loader = new DataLoader<string, string>(fetch, batchScheduler);
23+
var services = new ServiceCollection()
24+
.AddScoped<IBatchScheduler, ManualBatchScheduler>()
25+
.AddDataLoader(sp => new DataLoader<string, string>(fetch, sp.GetRequiredService<IBatchScheduler>()));
26+
var scope = services.BuildServiceProvider().CreateScope();
27+
var dataLoader = scope.ServiceProvider.GetRequiredService<DataLoader<string, string>>();
2328

2429
// act
25-
void Verify() => loader.Clear();
30+
void Verify() => dataLoader.Clear();
2631

2732
// assert
2833
Assert.Null(Record.Exception(Verify));
@@ -411,11 +416,11 @@ ValueTask Fetch(
411416
[InlineData(5, 25, 25, 0, true, false)]
412417
[InlineData(5, 25, 25, 0, false, true)]
413418
[InlineData(5, 25, 25, 0, false, false)]
414-
// [InlineData(100, 1000, 25, 25, true, true)]
415-
// [InlineData(100, 1000, 25, 0, true, true)]
416-
// [InlineData(100, 1000, 25, 0, true, false)]
417-
// [InlineData(100, 1000, 25, 25, false, true)]
418-
// [InlineData(100, 1000, 25, 0, false, false)]
419+
[InlineData(100, 1000, 25, 25, true, true)]
420+
[InlineData(100, 1000, 25, 0, true, true)]
421+
[InlineData(100, 1000, 25, 0, true, false)]
422+
[InlineData(100, 1000, 25, 25, false, true)]
423+
[InlineData(100, 1000, 25, 0, false, false)]
419424
[Theory(DisplayName = "LoadAsync: Runs integration tests with different settings")]
420425
public async Task LoadTest(
421426
int uniqueKeys,
@@ -445,14 +450,16 @@ async ValueTask Wait()
445450
=> await Task.Delay(random.Next(maxDelay), cancellationToken);
446451
}
447452

453+
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
454+
var ct = cts.Token;
448455
using var cacheOwner = caching
449456
? new TaskCacheOwner()
450457
: null;
451-
458+
452459
var options = new DataLoaderOptions
453460
{
454461
Cache = cacheOwner?.Cache,
455-
CancellationToken = cacheOwner?.CancellationToken ?? default,
462+
CancellationToken = ct,
456463
MaxBatchSize = batching ? 1 : maxBatchSize,
457464
};
458465

@@ -468,26 +475,31 @@ async ValueTask Wait()
468475
var requests = new Task<int>[maxRequests];
469476

470477
// act
478+
output.WriteLine("LoadAsync");
471479
for (var i = 0; i < maxRequests; i++)
472480
{
473481
requests[i] = Task.Factory.StartNew(async () =>
474482
{
475483
var index = random.Next(uniqueKeys);
476484
var delay = random.Next(maxDelay);
477485

478-
await Task.Delay(delay);
486+
await Task.Delay(delay, ct);
479487

480-
return await loader.LoadAsync(keyArray[index]);
488+
return await loader.LoadAsync(keyArray[index], ct);
481489
}, TaskCreationOptions.RunContinuationsAsynchronously).Unwrap();
482490
}
483491

492+
output.WriteLine("Start Dispatch");
484493
while (requests.Any(task => !task.IsCompleted))
485494
{
486-
await Task.Delay(25);
495+
output.WriteLine("Wait");
496+
await Task.Delay(25, ct);
497+
output.WriteLine("Dispatch");
487498
batchScheduler.Dispatch();
488499
}
489500

490501
// assert
502+
output.WriteLine("Wait for results.");
491503
var responses = await Task.WhenAll(requests);
492504

493505
foreach (var response in responses)

src/GreenDonut/test/Core.Tests/GreenDonut.Tests.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,17 @@
1111

1212
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
1313
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="8.0.0" />
14+
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
1415
</ItemGroup>
1516

1617
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
1718
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="7.0.0" />
19+
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
1820
</ItemGroup>
1921

2022
<ItemGroup Condition="'$(TargetFramework)' == 'net6.0' OR '$(TargetFramework)' == 'netstandard2.1' OR '$(TargetFramework)' == 'netstandard2.0'">
2123
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="6.0.0" />
24+
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
2225
</ItemGroup>
2326

2427
</Project>
Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,24 @@
11
using System;
22
using System.Collections.Concurrent;
3+
using System.Diagnostics;
34
using System.Threading.Tasks;
45

56
namespace GreenDonut;
67

78
public class ManualBatchScheduler : IBatchScheduler
89
{
9-
private readonly object _sync = new();
1010
private readonly ConcurrentQueue<Func<ValueTask>> _queue = new();
1111

1212
public void Dispatch()
1313
{
14-
lock(_sync)
14+
while (_queue.TryDequeue(out var dispatch))
1515
{
16-
while (_queue.TryDequeue(out var dispatch))
17-
{
18-
dispatch();
19-
}
16+
Task.Run(async () => await dispatch());
2017
}
2118
}
2219

2320
public void Schedule(Func<ValueTask> dispatch)
2421
{
25-
lock (_sync)
26-
{
27-
_queue.Enqueue(dispatch);
28-
}
22+
_queue.Enqueue(dispatch);
2923
}
30-
}
24+
}

0 commit comments

Comments
 (0)