Skip to content

Commit da377f6

Browse files
authored
Merge pull request #332 from thomhurst/fix/async-processor-correctness
Fix processor correctness: lazy re-enumeration, broken cancellation on dispose, hung awaiters
2 parents 445d406 + 0db5f1c commit da377f6

55 files changed

Lines changed: 1415 additions & 1216 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/dotnet.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,11 @@ jobs:
2828
- name: Setup .NET
2929
uses: actions/setup-dotnet@v4
3030
with:
31-
dotnet-version: 9.0.x
31+
# 10.0.x runs the pipeline; 8.0.x and 9.0.x provide runtimes for the multi-targeted test project
32+
dotnet-version: |
33+
8.0.x
34+
9.0.x
35+
10.0.x
3236
- name: Run Pipeline
3337
run: dotnet run -c Release
3438
working-directory: "EnumerableAsyncProcessor.Pipeline"

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,3 +351,5 @@ MigrationBackup/
351351

352352
# Claude local settings
353353
.claude/settings.local.json
354+
355+
.playwright-mcp/

EnumerableAsyncProcessor.Pipeline/EnumerableAsyncProcessor.Pipeline.csproj

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,20 @@
22

33
<PropertyGroup>
44
<OutputType>Exe</OutputType>
5-
<TargetFramework>net9.0</TargetFramework>
5+
<TargetFramework>net10.0</TargetFramework>
66
<ImplicitUsings>enable</ImplicitUsings>
77
<Nullable>enable</Nullable>
88
<SignAssembly>false</SignAssembly>
99
</PropertyGroup>
1010

1111
<ItemGroup>
12-
<PackageReference Include="ModularPipelines.DotNet" Version="3.1.90" />
13-
<PackageReference Include="ModularPipelines.Git" Version="3.1.90" />
12+
<PackageReference Include="ModularPipelines.DotNet" Version="3.2.8" />
13+
<PackageReference Include="ModularPipelines.Git" Version="3.2.8" />
14+
</ItemGroup>
15+
16+
<ItemGroup>
17+
<!-- Pipeline.CreateBuilder resolves configuration relative to the output directory -->
18+
<None Include="appsettings.json" CopyToOutputDirectory="PreserveNewest" />
1419
</ItemGroup>
1520

1621
</Project>
Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using ModularPipelines.Attributes;
1+
using ModularPipelines.Attributes;
2+
using ModularPipelines.Configuration;
23
using ModularPipelines.Context;
34
using ModularPipelines.DotNet.Extensions;
45
using ModularPipelines.DotNet.Options;
@@ -11,21 +12,24 @@ namespace EnumerableAsyncProcessor.Pipeline.Modules.LocalMachine;
1112
[DependsOn<CreateLocalNugetFolderModule>]
1213
public class AddLocalNugetSourceModule : Module<CommandResult>
1314
{
14-
protected override async Task<bool> ShouldIgnoreFailures(IPipelineContext context, Exception exception)
15+
protected override ModuleConfiguration Configure()
1516
{
16-
await Task.Yield();
17-
return exception is CommandException commandException &&
18-
commandException.StandardOutput.Contains("The name specified has already been added to the list of available package sources");
17+
return ModuleConfiguration.Create()
18+
.WithIgnoreFailuresWhen((_, exception) =>
19+
exception is CommandException commandException &&
20+
commandException.StandardOutput.Contains("The name specified has already been added to the list of available package sources"))
21+
.Build();
1922
}
2023

21-
protected override async Task<CommandResult?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
24+
protected override async Task<CommandResult?> ExecuteAsync(IModuleContext context, CancellationToken cancellationToken)
2225
{
23-
var localNugetPathResult = await GetModule<CreateLocalNugetFolderModule>();
26+
var localNugetPathResult = await context.GetModule<CreateLocalNugetFolderModule>();
2427

2528
return await context.DotNet().Nuget.Add
26-
.Source(new DotNetNugetAddSourceOptions(localNugetPathResult.Value!)
29+
.Source(new DotNetNugetAddSourceOptions
2730
{
31+
Packagesourcepath = localNugetPathResult.ValueOrDefault!.Path,
2832
Name = "ModularPipelinesLocalNuGet"
29-
}, cancellationToken);
33+
}, cancellationToken: cancellationToken);
3034
}
3135
}
Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using Microsoft.Extensions.Logging;
1+
using Microsoft.Extensions.Logging;
22
using ModularPipelines.Attributes;
33
using ModularPipelines.Context;
44
using ModularPipelines.FileSystem;
@@ -10,17 +10,16 @@ namespace EnumerableAsyncProcessor.Pipeline.Modules.LocalMachine;
1010
[DependsOn<PackagePathsParserModule>]
1111
public class CreateLocalNugetFolderModule : Module<Folder>
1212
{
13-
protected override async Task<Folder?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
13+
protected override Task<Folder?> ExecuteAsync(IModuleContext context, CancellationToken cancellationToken)
1414
{
15-
var localNugetRepositoryFolder = context.FileSystem.GetFolder(Environment.SpecialFolder.ApplicationData)
15+
var localNugetRepositoryFolder = context.Files
16+
.GetFolder(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData))
1617
.GetFolder("ModularPipelines")
1718
.GetFolder("LocalNuget")
1819
.Create();
19-
20-
await Task.Yield();
2120

2221
context.Logger.LogInformation("Local NuGet Repository Path: {Path}", localNugetRepositoryFolder.Path);
2322

24-
return localNugetRepositoryFolder;
23+
return Task.FromResult<Folder?>(localNugetRepositoryFolder);
2524
}
2625
}

EnumerableAsyncProcessor.Pipeline/Modules/LocalMachine/UploadPackagesToLocalNuGetModule.cs

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,26 +14,31 @@ namespace EnumerableAsyncProcessor.Pipeline.Modules.LocalMachine;
1414
[DependsOn<CreateLocalNugetFolderModule>]
1515
public class UploadPackagesToLocalNuGetModule : Module<CommandResult[]>
1616
{
17-
protected override async Task OnBeforeExecute(IPipelineContext context)
17+
protected override async Task OnBeforeExecuteAsync(IModuleContext context, CancellationToken cancellationToken)
1818
{
19-
var packagePaths = await GetModule<PackagePathsParserModule>();
20-
foreach (var packagePath in packagePaths.Value!)
19+
var packagePaths = await context.GetModule<PackagePathsParserModule>();
20+
21+
foreach (var packagePath in packagePaths.ValueOrDefault ?? [])
2122
{
2223
context.Logger.LogInformation("[Local Directory] Uploading {File}", packagePath);
2324
}
2425

25-
await base.OnBeforeExecute(context);
26+
await base.OnBeforeExecuteAsync(context, cancellationToken);
2627
}
2728

28-
protected override async Task<CommandResult[]?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
29+
protected override async Task<CommandResult[]?> ExecuteAsync(IModuleContext context, CancellationToken cancellationToken)
2930
{
30-
var localRepoLocation = await GetModule<CreateLocalNugetFolderModule>();
31-
var packagePaths = await GetModule<PackagePathsParserModule>();
32-
return await packagePaths.Value!.SelectAsync(async file => await context.DotNet()
33-
.Nuget
34-
.Push(new DotNetNugetPushOptions(file)
35-
{
36-
Source = localRepoLocation.Value!,
37-
}, cancellationToken), cancellationToken: cancellationToken).ProcessOneAtATime();
31+
var localRepoLocation = await context.GetModule<CreateLocalNugetFolderModule>();
32+
var packagePaths = await context.GetModule<PackagePathsParserModule>();
33+
34+
return await packagePaths.ValueOrDefault!
35+
.SelectAsync(file => context.DotNet()
36+
.Nuget
37+
.Push(new DotNetNugetPushOptions
38+
{
39+
Path = file.Path,
40+
Source = localRepoLocation.ValueOrDefault!.Path,
41+
}, cancellationToken: cancellationToken), cancellationToken: cancellationToken)
42+
.ProcessOneAtATime();
3843
}
39-
}
44+
}
Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,28 @@
1-
using Microsoft.Extensions.Logging;
1+
using Microsoft.Extensions.Logging;
22
using ModularPipelines.Context;
33
using ModularPipelines.Git.Extensions;
4+
using ModularPipelines.Models;
45
using ModularPipelines.Modules;
56

6-
// ReSharper disable HeuristicUnreachableCode
7-
#pragma warning disable CS0162
8-
97
namespace EnumerableAsyncProcessor.Pipeline.Modules;
108

119
public class NugetVersionGeneratorModule : Module<string>
1210
{
13-
protected override async Task<string?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
11+
protected override async Task<string?> ExecuteAsync(IModuleContext context, CancellationToken cancellationToken)
1412
{
1513
var gitVersionInformation = await context.Git().Versioning.GetGitVersioningInformation();
16-
14+
1715
if (gitVersionInformation.BranchName == "main")
1816
{
1917
return gitVersionInformation.SemVer!;
2018
}
21-
19+
2220
return $"{gitVersionInformation.Major}.{gitVersionInformation.Minor}.{gitVersionInformation.Patch}-{gitVersionInformation.PreReleaseLabel}-{gitVersionInformation.CommitsSinceVersionSource}";
2321
}
2422

25-
protected override async Task OnAfterExecute(IPipelineContext context)
23+
protected override Task<ModuleResult<string>?> OnAfterExecuteAsync(IModuleContext context, ModuleResult<string> result, CancellationToken cancellationToken)
2624
{
27-
var moduleResult = await this;
28-
context.Logger.LogInformation("NuGet Version to Package: {Version}", moduleResult.Value);
25+
context.Logger.LogInformation("NuGet Version to Package: {Version}", result.ValueOrDefault);
26+
return base.OnAfterExecuteAsync(context, result, cancellationToken);
2927
}
3028
}

EnumerableAsyncProcessor.Pipeline/Modules/PackProjectsModule.cs

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,29 +15,31 @@ namespace EnumerableAsyncProcessor.Pipeline.Modules;
1515
[DependsOn<RunUnitTestsModule>]
1616
public class PackProjectsModule : Module<List<CommandResult>>
1717
{
18-
protected override async Task<List<CommandResult>?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
18+
protected override async Task<List<CommandResult>?> ExecuteAsync(IModuleContext context, CancellationToken cancellationToken)
1919
{
2020
var results = new List<CommandResult>();
21-
var packageVersion = await GetModule<NugetVersionGeneratorModule>();
22-
var projectFiles = context.Git().RootDirectory!.GetFiles(f => GetProjectsPredicate(f, context));
21+
var packageVersion = await context.GetModule<NugetVersionGeneratorModule>();
22+
var projectFiles = context.Git().RootDirectory.GetFiles(f => GetProjectsPredicate(f, context));
23+
2324
foreach (var projectFile in projectFiles)
2425
{
25-
results.Add(await context.DotNet().Pack(new DotNetPackOptions {
26-
ProjectSolution = projectFile.Path,
27-
Configuration = Configuration.Release,
26+
results.Add(await context.DotNet().Pack(new DotNetPackOptions
27+
{
28+
ProjectSolution = projectFile.Path,
29+
Configuration = "Release",
2830
Properties =
2931
[
30-
("PackageVersion", packageVersion.Value)!,
31-
("Version", packageVersion.Value)!
32+
("PackageVersion", packageVersion.ValueOrDefault)!,
33+
("Version", packageVersion.ValueOrDefault)!
3234
],
3335
IncludeSource = true
34-
}, cancellationToken));
36+
}, cancellationToken: cancellationToken));
3537
}
3638

3739
return results;
3840
}
3941

40-
private bool GetProjectsPredicate(File file, IPipelineContext context)
42+
private bool GetProjectsPredicate(File file, IModuleContext context)
4143
{
4244
var path = file.Path;
4345
if (!path.EndsWith(".csproj", StringComparison.OrdinalIgnoreCase))
@@ -55,4 +57,4 @@ private bool GetProjectsPredicate(File file, IPipelineContext context)
5557
context.Logger.LogInformation("Found File: {File}", path);
5658
return true;
5759
}
58-
}
60+
}

EnumerableAsyncProcessor.Pipeline/Modules/PackageFilesRemovalModule.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@ namespace EnumerableAsyncProcessor.Pipeline.Modules;
66

77
public class PackageFilesRemovalModule : Module
88
{
9-
protected override async Task<IDictionary<string, object>?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
9+
protected override Task ExecuteModuleAsync(IModuleContext context, CancellationToken cancellationToken)
1010
{
11-
var packageFiles = context.Git().RootDirectory.GetFiles(path => path.Extension is ".nupkg");
11+
var packageFiles = context.Git().RootDirectory.GetFiles(file => file.Extension is ".nupkg");
1212

1313
foreach (var packageFile in packageFiles)
1414
{
1515
packageFile.Delete();
1616
}
1717

18-
return await NothingAsync();
18+
return Task.CompletedTask;
1919
}
2020
}

EnumerableAsyncProcessor.Pipeline/Modules/PackagePathsParserModule.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ namespace EnumerableAsyncProcessor.Pipeline.Modules;
88
[DependsOn<PackProjectsModule>]
99
public class PackagePathsParserModule : Module<List<File>>
1010
{
11-
protected override async Task<List<File>?> ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken)
11+
protected override async Task<List<File>?> ExecuteAsync(IModuleContext context, CancellationToken cancellationToken)
1212
{
13-
var packPackagesModuleResult = await GetModule<PackProjectsModule>();
13+
var packPackagesModuleResult = await context.GetModule<PackProjectsModule>();
1414

15-
return packPackagesModuleResult.Value!
15+
return packPackagesModuleResult.ValueOrDefault!
1616
.Select(x => x.StandardOutput)
1717
.Select(x => x.Split("Successfully created package '")[1])
1818
.Select(x => x.Split("'.")[0])

0 commit comments

Comments
 (0)