Skip to content

Commit 9eba0e2

Browse files
authored
Fix workload continuer (#3079)
* Fix workload continuer for synchronous continuations. * Fix test.
1 parent d246230 commit 9eba0e2

6 files changed

Lines changed: 288 additions & 422 deletions

File tree

src/BenchmarkDotNet/Code/DeclarationsProvider.cs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using BenchmarkDotNet.Attributes;
1+
using BenchmarkDotNet.Attributes;
22
using BenchmarkDotNet.Engines;
33
using BenchmarkDotNet.Environments;
44
using BenchmarkDotNet.Extensions;
@@ -177,7 +177,7 @@ internal class AsyncDeclarationsProvider(BenchmarkCase benchmark) : Declarations
177177
{
178178
public override string[] GetExtraFields() =>
179179
[
180-
$"public {typeof(WorkloadContinuerAndValueTaskSource).GetCorrectCSharpTypeName()} workloadContinuerAndValueTaskSource;",
180+
$"public {typeof(WorkloadValueTaskSource).GetCorrectCSharpTypeName()} workloadContinuerAndValueTaskSource;",
181181
$"public {typeof(IClock).GetCorrectCSharpTypeName()} clock;",
182182
"public long invokeCount;"
183183
];
@@ -224,7 +224,7 @@ protected override SmartStringBuilder ReplaceCore(SmartStringBuilder smartString
224224
this.__fieldsContainer.clock = clock;
225225
if (this.__fieldsContainer.workloadContinuerAndValueTaskSource == null)
226226
{
227-
this.__fieldsContainer.workloadContinuerAndValueTaskSource = new {{typeof(WorkloadContinuerAndValueTaskSource).GetCorrectCSharpTypeName()}}();
227+
this.__fieldsContainer.workloadContinuerAndValueTaskSource = new {{typeof(WorkloadValueTaskSource).GetCorrectCSharpTypeName()}}();
228228
this.__StartWorkload();
229229
}
230230
return this.__fieldsContainer.workloadContinuerAndValueTaskSource.Continue();
@@ -240,14 +240,12 @@ private async void __StartWorkload()
240240
{
241241
try
242242
{
243+
if (await this.__fieldsContainer.workloadContinuerAndValueTaskSource.GetIsComplete())
244+
{
245+
{{finalReturn}}
246+
}
243247
while (true)
244248
{
245-
await this.__fieldsContainer.workloadContinuerAndValueTaskSource;
246-
if (this.__fieldsContainer.workloadContinuerAndValueTaskSource.IsCompleted)
247-
{
248-
{{finalReturn}}
249-
}
250-
251249
{{typeof(StartedClock).GetCorrectCSharpTypeName()}} startedClock = {{typeof(ClockExtensions).GetCorrectCSharpTypeName()}}.Start(this.__fieldsContainer.clock);
252250
while (--this.__fieldsContainer.invokeCount >= 0)
253251
{
@@ -256,7 +254,10 @@ private async void __StartWorkload()
256254
unsafe { awaitable = {{workloadMethodCall}} }
257255
await awaitable;
258256
}
259-
this.__fieldsContainer.workloadContinuerAndValueTaskSource.SetResult(startedClock.GetElapsed());
257+
if (await this.__fieldsContainer.workloadContinuerAndValueTaskSource.SetResultAndGetIsComplete(startedClock.GetElapsed()))
258+
{
259+
{{finalReturn}}
260+
}
260261
}
261262
}
262263
catch (global::System.Exception e)

src/BenchmarkDotNet/Engines/WorkloadContinuerAndValueTaskSource.cs

Lines changed: 0 additions & 65 deletions
This file was deleted.
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using JetBrains.Annotations;
2+
using Perfolizer.Horology;
3+
using System.ComponentModel;
4+
using System.Threading.Tasks.Sources;
5+
6+
namespace BenchmarkDotNet.Engines;
7+
8+
// This is used to prevent allocating a new async state machine on every benchmark iteration.
9+
[UsedImplicitly]
10+
[EditorBrowsable(EditorBrowsableState.Never)]
11+
public sealed class WorkloadValueTaskSource : IValueTaskSource<ClockSpan>, IValueTaskSource<bool>
12+
{
13+
private ManualResetValueTaskSourceCore<bool> continuerSource;
14+
private ManualResetValueTaskSourceCore<ClockSpan> clockSpanSource;
15+
16+
public ValueTask<ClockSpan> Continue()
17+
{
18+
clockSpanSource.Reset();
19+
continuerSource.SetResult(false);
20+
return new(this, clockSpanSource.Version);
21+
}
22+
23+
public ValueTask<bool> GetIsComplete()
24+
=> new(this, continuerSource.Version);
25+
26+
public void Complete()
27+
=> continuerSource.SetResult(true);
28+
29+
public ValueTask<bool> SetResultAndGetIsComplete(ClockSpan result)
30+
{
31+
continuerSource.Reset();
32+
clockSpanSource.SetResult(result);
33+
return GetIsComplete();
34+
}
35+
36+
public void SetException(Exception exception)
37+
=> clockSpanSource.SetException(exception);
38+
39+
ValueTaskSourceStatus IValueTaskSource<bool>.GetStatus(short token)
40+
=> continuerSource.GetStatus(token);
41+
42+
void IValueTaskSource<bool>.OnCompleted(Action<object?> continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags)
43+
// Strip UseSchedulingContext to ensure Continue() and Complete() synchronously continue __WorkloadCore.
44+
=> continuerSource.OnCompleted(continuation, state, token, flags & ~ValueTaskSourceOnCompletedFlags.UseSchedulingContext);
45+
46+
bool IValueTaskSource<bool>.GetResult(short token)
47+
=> continuerSource.GetResult(token);
48+
49+
ClockSpan IValueTaskSource<ClockSpan>.GetResult(short token)
50+
=> clockSpanSource.GetResult(token);
51+
52+
ValueTaskSourceStatus IValueTaskSource<ClockSpan>.GetStatus(short token)
53+
=> clockSpanSource.GetStatus(token);
54+
55+
void IValueTaskSource<ClockSpan>.OnCompleted(Action<object?> continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags)
56+
=> clockSpanSource.OnCompleted(continuation, state, token, flags);
57+
}

0 commit comments

Comments
 (0)