Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 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
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
Expand Up @@ -26,6 +26,7 @@
<PackageReference Include="System.IO.Hashing" Version="$(SihVersion)" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.1" />
<PackageReference Include="System.Drawing.Common" Version="10.0.1" />
<!-- The Test SDK is required only for the VSTest Adapter to work -->
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
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 @@ -28,6 +29,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" />
<PackageReference Include="System.Text.Json" Version="10.0.1" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
Expand All @@ -51,5 +53,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>
10 changes: 10 additions & 0 deletions src/BenchmarkDotNet/Code/CodeGenEntryPointType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace BenchmarkDotNet.Code;

/// <summary>
/// Specifies how to generate the entry-point for the benchmark process.
/// </summary>
public enum CodeGenEntryPointType
{
Synchronous,
Asynchronous
}
Loading