Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
e930ff7
Async refactor
timcassell Dec 24, 2025
2244617
Async validation
timcassell Jan 5, 2026
c2bde2d
Merge branch 'master' into async-refactor
timcassell Jan 25, 2026
6f71ff4
Fix `CS0169` and `Could not load file or assembly Microsoft.Bcl.Async…
timcassell Jan 25, 2026
4094e33
Add thread-safety and wait-blocking to BenchmarkSynchronizationContext.
timcassell Jan 25, 2026
5524feb
Merge branch 'master' into async-refactor
timcassell Feb 1, 2026
3709747
IL emit match behavior of FieldsContainer conditional emit.
timcassell Feb 1, 2026
9242a9d
Reduce lock contention.
timcassell Feb 1, 2026
7e7e26b
Simplify argField names and remove unused constants.
timcassell Feb 1, 2026
62d575f
Fix race condition.
timcassell Feb 1, 2026
1e7b41c
Use async entry-point in wasm.
timcassell Feb 8, 2026
db34ae2
Made IExecutor.ExecuteAsync implementations truly async.
timcassell Feb 9, 2026
d87b530
Allow RoslynToolchain in old Mono.
timcassell Feb 9, 2026
6db0cde
Fix tests.
timcassell Feb 9, 2026
5423ea1
Catch proper exception on Unix.
timcassell Feb 9, 2026
c3d4529
Shorten pipe name.
timcassell Feb 9, 2026
0edf78a
Attempt to fix Windows hangs. Added pipe connection timeout.
timcassell Feb 9, 2026
53bdc54
Merge branch 'master' into async-refactor
timcassell Feb 10, 2026
5a465a8
Remove redundant check.
timcassell Feb 10, 2026
70a6b14
ReadLineAsync
timcassell Feb 10, 2026
48a307d
Use 2 pipes to fix deadlock.
timcassell Feb 10, 2026
3c082cc
Pluralize NamedPipesHost name.
timcassell Feb 10, 2026
d3fdd8e
Try read/write synchronously in the host.
timcassell Feb 12, 2026
fa7ecbb
Read/write synchronously on both ends.
timcassell Feb 12, 2026
00dc6d2
Change named pipes to TCP loopback.
timcassell Feb 13, 2026
4358883
Remove extra connect call. Catch known socket errors for early child …
timcassell Feb 13, 2026
4dfa2ce
Use synchronous read/write in child, synchronous write in parent.
timcassell Feb 14, 2026
3de0c6f
Make tcp host connect synchronously and change IHost methods back to …
timcassell Feb 14, 2026
f6c26ea
Merge branch 'master' into async-refactor
timcassell Feb 14, 2026
72196c5
Dispose client when process exited early.
timcassell Feb 14, 2026
7f2cd06
Backport Task.WaitAsync and StreamReader.ReadLineAsync with cancelati…
timcassell Feb 14, 2026
ade6d30
Use synchronous APIs on Full Framework + Windows Arm.
timcassell Feb 15, 2026
4656437
Add logs to find where the freeze is occurring.
timcassell Feb 16, 2026
4126c96
Check `awaiter.IsCompleted` instead of `valueTask.IsCompleted`. Moved…
timcassell Feb 16, 2026
ec874c7
Fix forever hang race condition.
timcassell Feb 16, 2026
d88b50e
Remove extra logs and revert IsFullFrameworkCompatibilityLayer.
timcassell Feb 16, 2026
e4bd4a4
Actual fix forever hang race condition.
timcassell Feb 17, 2026
b462278
Merge branch 'master' into async-refactor
timcassell Feb 17, 2026
d5b23e2
Add WebSocket IPC
timcassell Feb 17, 2026
6aa316a
Install ws
timcassell Feb 20, 2026
01bca09
Merge branch 'master' into async-refactor
timcassell Mar 5, 2026
68a97d0
Update wasm IPC to handle all engines.
timcassell Mar 7, 2026
ad7595b
Add cooperative cancelation and some cleanup.
timcassell Mar 8, 2026
ffafe00
Merge branch 'master' into async-refactor
timcassell Mar 8, 2026
8bb7b8f
Added [BenchmarkCancellation] attribute for users to participate in c…
timcassell Mar 8, 2026
24f89bd
Add code fixer.
timcassell Mar 8, 2026
f473599
Fix line endings.
timcassell Mar 8, 2026
fd8dc6f
Fix ExceptionHandlingTests
timcassell Mar 8, 2026
195070b
Fix NoHangs test.
timcassell Mar 9, 2026
e6430aa
Dispose WebSocketListener in case of exception.
timcassell Mar 9, 2026
6ce8bb5
Skip node tests in Framework.
timcassell Mar 9, 2026
bb012d7
Fix discoverer.
timcassell Mar 9, 2026
8387a1d
Update WebSocketListener and enable Framework tests.
timcassell Mar 9, 2026
1961870
Re-add v8 installation instructions.
timcassell Mar 10, 2026
2f934af
Refactor IDiagnoser, IBuilder, IGenerator to async.
timcassell Mar 14, 2026
995b78e
Update packages.
timcassell Mar 15, 2026
8343982
Update RunAsyncTests.
timcassell Mar 15, 2026
09190c4
Fix DisposeAtProcessTermination calling Dispose too early.
timcassell Mar 15, 2026
734c8ca
Fix child process deadlock after cancel.
timcassell Mar 15, 2026
16b00ea
Check if the token was canceled.
timcassell Mar 15, 2026
9dcf5d2
Refactor IExporter to async.
timcassell Mar 15, 2026
be6aa21
Fix ScottPlotExporterTests.
timcassell Mar 15, 2026
a4350c6
Fix ExceptionHelper methods and exception filters.
timcassell Mar 16, 2026
8637d0e
Use ConfigureAwait everywhere.
timcassell Mar 16, 2026
c5bbbeb
Some fixes.
timcassell Mar 16, 2026
3c46323
Replace magic string with constant.
timcassell Mar 16, 2026
ace6b4a
Fix InProcessNoEmitToolchain validation.
timcassell Mar 16, 2026
a195aaa
Fix usings.
timcassell Mar 16, 2026
8bd2a1a
Fix UserCodeRunsOnCallerThread test.
timcassell Mar 16, 2026
464caba
Refactor to make StreamOrLoggerWriter internal.
timcassell Mar 16, 2026
42d9dd7
Capture standard error.
timcassell Mar 16, 2026
eb07350
Remove file-based ipc timeout.
timcassell Mar 16, 2026
a8c8fa0
Fix test.
timcassell Mar 16, 2026
7620a76
Use async file streams.
timcassell Mar 22, 2026
b1b53e6
RPlotExporter LoadTemplateAsync
timcassell Mar 22, 2026
f8b2c54
Move all process cleanup logic to ProcessCleanupHelper and use it eve…
timcassell Mar 22, 2026
479dbc4
Fix base dispose call.
timcassell Mar 22, 2026
5aea465
Fixes from AI code review.
timcassell Mar 23, 2026
58dd6ce
Remove extra blank line.
timcassell Mar 23, 2026
4b89172
More small fixes.
timcassell Mar 23, 2026
f48a7f7
Fix comment.
timcassell Mar 23, 2026
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
4 changes: 4 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[*.cs]

# CS1998: Async method lacks 'await' operators and will run synchronously
dotnet_diagnostic.CS1998.severity = suggestion
3 changes: 3 additions & 0 deletions BenchmarkDotNet.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
<Project Path="samples/BenchmarkDotNet.Samples.FSharp/BenchmarkDotNet.Samples.FSharp.fsproj" />
<Project Path="samples/BenchmarkDotNet.Samples/BenchmarkDotNet.Samples.csproj" DefaultStartup="true" />
</Folder>
<Folder Name="/Solution Items/">
<File Path=".editorconfig" />
</Folder>
<Folder Name="/src/">
<Project Path="src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.csproj" />
<Project Path="src/BenchmarkDotNet.Annotations/BenchmarkDotNet.Annotations.csproj" />
Expand Down
4 changes: 0 additions & 4 deletions build/BenchmarkDotNet.Build/Runners/BuildRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
using Cake.Common.Tools.DotNet.Workload.Install;
using Cake.Core;
using Cake.Core.IO;
using System;
using System.IO;
using System.Linq;

Expand Down Expand Up @@ -51,7 +50,6 @@ public void PackWeaver()
{
MSBuildSettings = context.MsBuildSettingsRestore,
};
MaybeAppendArgument(restoreSettings);
context.DotNetRestore(weaverPath.GetDirectory().FullPath, restoreSettings);

context.Information("BuildSystemProvider: " + context.BuildSystem().Provider);
Expand All @@ -63,7 +61,6 @@ public void PackWeaver()
Configuration = context.BuildConfiguration,
Verbosity = context.BuildVerbosity
};
MaybeAppendArgument(buildSettings);
context.DotNetBuild(weaverPath.FullPath, buildSettings);

var packSettings = new DotNetPackSettings
Expand All @@ -72,7 +69,6 @@ public void PackWeaver()
MSBuildSettings = context.MsBuildSettingsPack,
Configuration = context.BuildConfiguration
};
MaybeAppendArgument(packSettings);
context.DotNetPack(weaverPath.FullPath, packSettings);
}

Expand Down
2 changes: 1 addition & 1 deletion build/common.props
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@

<PropertyGroup>
<!-- Increment this when the BenchmarkDotNet.Weaver package needs to be re-packed. -->
<WeaverVersionSuffix>-1</WeaverVersionSuffix>
<WeaverVersionSuffix>-2</WeaverVersionSuffix>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;

namespace BenchmarkDotNet.Attributes;

/// <summary>
/// When applied to an async benchmark method, overrides the return type of the async method that calls the benchmark method.
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public sealed class AsyncCallerTypeAttribute(Type asyncCallerType) : Attribute
{
/// <summary>
/// The return type of the async method that calls the benchmark method.
/// </summary>
public Type AsyncCallerType { get; private set; } = asyncCallerType;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;
using System.Runtime.CompilerServices;

namespace BenchmarkDotNet.Attributes.CompilerServices;

// MethodImplOptions.AggressiveOptimization is applied to all methods to force them to go straight to tier1 JIT,
// eliminating tiered JIT as a potential variable in measurements.
// This is necessary because C# does not support any way to apply attributes to compiler-generated state machine methods.
// This is applied both to the core Engine and auto-generated classes.
#pragma warning disable CS1574
/// <summary>
/// Instructs the BenchmarkDotNet assembly weaver to apply <see cref="MethodImplOptions.AggressiveOptimization"/> to all declared
/// methods in the annotated type and nested types that are not already annotated with <see cref="MethodImplOptions.NoOptimization"/>.
/// </summary>
#pragma warning restore CS1574
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public sealed class AggressivelyOptimizeMethodsAttribute : Attribute
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public void Handle(HostSignal signal, DiagnoserActionParameters parameters)

public IEnumerable<Metric> ProcessResults(DiagnoserResults results) => etwProfiler.ProcessResults(results);

public IEnumerable<ValidationError> Validate(ValidationParameters validationParameters) => etwProfiler.Validate(validationParameters);
public IAsyncEnumerable<ValidationError> ValidateAsync(ValidationParameters validationParameters) => etwProfiler.ValidateAsync(validationParameters);

private static EtwProfilerConfig CreateDefaultConfig()
{
Expand Down
4 changes: 2 additions & 2 deletions src/BenchmarkDotNet.Diagnostics.Windows/EtwProfiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ public EtwProfiler(EtwProfilerConfig config)

public RunMode GetRunMode(BenchmarkCase benchmarkCase) => runMode;

public IEnumerable<ValidationError> Validate(ValidationParameters validationParameters)
=> HardwareCounters.Validate(validationParameters, mandatory: false);
public IAsyncEnumerable<ValidationError> ValidateAsync(ValidationParameters validationParameters)
=> HardwareCounters.Validate(validationParameters, mandatory: false).ToAsyncEnumerable();

public void Handle(HostSignal signal, DiagnoserActionParameters parameters)
{
Expand Down
2 changes: 1 addition & 1 deletion src/BenchmarkDotNet.Diagnostics.Windows/JitDiagnoser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public void Handle(HostSignal signal, DiagnoserActionParameters parameters)

public virtual IEnumerable<Metric> ProcessResults(DiagnoserResults results) => Array.Empty<Metric>();

public IEnumerable<ValidationError> Validate(ValidationParameters validationParameters)
public async IAsyncEnumerable<ValidationError> ValidateAsync(ValidationParameters validationParameters)
{
if (!OsDetector.IsWindows())
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public IEnumerable<Metric> ProcessResults(DiagnoserResults results)
return new NativeMemoryLogParser(traceFilePath, results.BenchmarkCase, logger, results.BuildResult.ArtifactsPaths.ProgramName).Parse();
}

public IEnumerable<ValidationError> Validate(ValidationParameters validationParameters) => etwProfiler.Validate(validationParameters);
public IAsyncEnumerable<ValidationError> ValidateAsync(ValidationParameters validationParameters) => etwProfiler.ValidateAsync(validationParameters);

private static EtwProfilerConfig CreateDefaultConfig()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
Inputs="$(BenchmarkDotNetWeaveAssemblyPath)"
Outputs="$(BenchmarkDotNetWeaveAssembliesStampFile)">

<WeaveAssemblyTask TargetAssembly="$(BenchmarkDotNetWeaveAssemblyPath)" />
<WeaveAssemblyTask TargetAssembly="$(BenchmarkDotNetWeaveAssemblyPath)" TreatWarningsAsErrors="$(TreatWarningsAsErrors)" />

<!-- Create stamp file for incrementality -->
<Touch Files="$(BenchmarkDotNetWeaveAssembliesStampFile)" AlwaysCreate="true" />
Expand Down
Binary file not shown.
Binary file not shown.
67 changes: 53 additions & 14 deletions src/BenchmarkDotNet.Weaver/src/WeaveAssemblyTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,45 +24,76 @@ public sealed class WeaveAssemblyTask : Task
[Required]
public string TargetAssembly { get; set; }

/// <summary>
/// Whether to treat warnings as errors.
/// </summary>
public bool TreatWarningsAsErrors { get; set; }

/// <summary>
/// Runs the weave assembly task.
/// </summary>
/// <returns><see langword="true"/> if successful; <see langword="false"/> otherwise.</returns>
public override bool Execute()
{
{
if (!File.Exists(TargetAssembly))
{
Log.LogError($"Assembly not found: {TargetAssembly}");
return false;
}


bool benchmarkMethodsImplAdjusted = false;
try
{
var module = ModuleDefinition.FromFile(TargetAssembly);

bool anyAdjustments = false;
foreach (var type in module.GetAllTypes())
{
// We can skip non-public types as they are not valid for benchmarks.
if (type.IsNotPublic)
if (type.CustomAttributes.Any(attr => attr.Constructor.DeclaringType.FullName == "BenchmarkDotNet.Attributes.CompilerServices.AggressivelyOptimizeMethodsAttribute"))
{
continue;
ApplyAggressiveOptimizationToMethods(type);

void ApplyAggressiveOptimizationToMethods(TypeDefinition type)
{
// Apply AggressiveOptimization to all methods in the type and nested types that
// aren't annotated with NoOptimization (this includes compiler-generated state machines).
foreach (var method in type.Methods)
{
if ((method.ImplAttributes & MethodImplAttributes.NoOptimization) == 0)
{
var oldImpl = method.ImplAttributes;
method.ImplAttributes |= MethodImplAttributes.AggressiveOptimization;
anyAdjustments |= (oldImpl & MethodImplAttributes.AggressiveOptimization) == 0;
}
}

// Recurse into nested types
foreach (var nested in type.NestedTypes)
{
ApplyAggressiveOptimizationToMethods(nested);
}
}
}

foreach (var method in type.Methods)
// We can skip non-public types as they are not valid for benchmarks.
// !type.IsNotPublic handles nested types, while type.IsPublic does not.
if (!type.IsNotPublic)
{
if (method.CustomAttributes.Any(IsBenchmarkAttribute))
foreach (var method in type.Methods)
{
var oldImpl = method.ImplAttributes;
// Remove AggressiveInlining and add NoInlining.
method.ImplAttributes = (oldImpl & ~MethodImplAttributes.AggressiveInlining) | MethodImplAttributes.NoInlining;
benchmarkMethodsImplAdjusted |= (oldImpl & MethodImplAttributes.NoInlining) == 0;
if (method.CustomAttributes.Any(IsBenchmarkAttribute))
{
var oldImpl = method.ImplAttributes;
// Remove AggressiveInlining and add NoInlining.
method.ImplAttributes = (oldImpl & ~MethodImplAttributes.AggressiveInlining) | MethodImplAttributes.NoInlining;
benchmarkMethodsImplAdjusted |= (oldImpl & MethodImplAttributes.NoInlining) == 0;
anyAdjustments |= benchmarkMethodsImplAdjusted;
}
}
}
}

if (benchmarkMethodsImplAdjusted)
if (anyAdjustments)
{
// Write to a memory stream before overwriting the original file in case an exception occurs during the write (like unsupported platform).
// https://github.com/Washi1337/AsmResolver/issues/640
Expand Down Expand Up @@ -90,9 +121,17 @@ public override bool Execute()
}
catch (Exception e)
{
Log.LogWarning($"Assembly weaving failed. Benchmark methods found requiring NoInlining: {benchmarkMethodsImplAdjusted}. Error:{Environment.NewLine}{e}");
if (TreatWarningsAsErrors)
{
Log.LogError($"Assembly weaving failed. Benchmark methods found requiring NoInlining: {benchmarkMethodsImplAdjusted}.");
Log.LogErrorFromException(e, true, true, null);
}
else
{
Log.LogWarning($"Assembly weaving failed. Benchmark methods found requiring NoInlining: {benchmarkMethodsImplAdjusted}. Error:{Environment.NewLine}{e}");
}
}
return true;
return !Log.HasLoggedErrors;
}

private static bool IsBenchmarkAttribute(CustomAttribute attribute)
Expand Down
9 changes: 8 additions & 1 deletion src/BenchmarkDotNet/BenchmarkDotNet.csproj
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\build\common.props" />

<PropertyGroup>
<AssemblyTitle>BenchmarkDotNet</AssemblyTitle>
<TargetFrameworks>netstandard2.0;net6.0;net8.0;net9.0;net10.0</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<NoWarn>$(NoWarn);1701;1702;1705;1591;3005;NU1702;CS3001;CS3003</NoWarn>
<NoWarn>$(NoWarn);1701;1702;1705;1591;3005;NU1510;NU1702;CS3001;CS3003</NoWarn>
<AssemblyName>BenchmarkDotNet</AssemblyName>
<PackageId>BenchmarkDotNet</PackageId>
<RootNamespace>BenchmarkDotNet</RootNamespace>
Expand All @@ -27,6 +28,7 @@
<PackageReference Include="Microsoft.DotNet.PlatformAbstractions" Version="3.1.6" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="5.0.0" />
<PackageReference Include="System.Management" Version="10.0.1" />
<PackageReference Include="System.Linq.AsyncEnumerable" Version="10.0.1" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
Expand All @@ -49,5 +51,10 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<!-- The transitive weaver reference is stripped during full pack, so we need to reference it directly. -->
<ItemGroup Condition="'$(IsFullPack)' == 'true'">
<PackageReference Include="BenchmarkDotNet.Weaver" Version="$(Version)$(WeaverVersionSuffix)" PrivateAssets="all" />
</ItemGroup>

<Import Project="..\..\build\common.targets" />
</Project>
Loading