Skip to content

fix: do not hold a lock while matching indexer setups - #918

Merged
vbreuss merged 1 commit into
mainfrom
fix/indexer-matching-lock
Oct 30, 2026
Merged

vbreuss merged 1 commit into
mainfrom
fix/indexer-matching-lock

Conversation

@vbreuss

@vbreuss vbreuss commented Oct 29, 2026

Copy link
Copy Markdown
Member

Indexer setups were matched inside the lock of their list, so a slow or blocking matcher stalled every other thread that accessed or set up an indexer on the same mock, and could deadlock when it waited for such a thread. The setups are now copied on write, like the member-id tables, so matching reads them without a lock and without allocating.

Indexer setups were matched inside the lock of their list, so a slow or blocking matcher stalled every other thread that accessed or set up an indexer on the same mock, and could deadlock when it waited for such a thread. The setups are now copied on write, like the member-id tables, so matching reads them without a lock and without allocating.
@vbreuss vbreuss self-assigned this Oct 29, 2026
Copilot AI balanced review requested due to automatic review settings October 29, 2026 23:24

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

@sonarqubecloud

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown

Test Results

    31 files  ± 0      31 suites  ±0   42m 41s ⏱️ - 1m 11s
 4 531 tests + 2   4 531 ✅ + 2  0 💤 ±0  0 ❌ ±0 
28 822 runs  +14  28 822 ✅ +14  0 💤 ±0  0 ❌ ±0 

Results for commit e1b2f6f. ± Comparison against base commit 5466462.

This pull request removes 22 and adds 18 tests. Note that renamed tests count towards both.
 x-myHeader3: baz, True)
)
Mockolate.Internal.Tests.Internals.WildcardTests ‑ ShouldSupportMultilineStrings(
Mockolate.Internal.Tests.Internals.WildcardTests ‑ ShouldSupportMultilineStrings(
Mockolate.SourceGenerators.Tests.MethodSetupsTests ‑ WhenImplementingObjectMethods_ShouldUseSpecialInterfaces(bool Equals(object obj);
Mockolate.SourceGenerators.Tests.MethodSetupsTests ‑ WhenImplementingObjectMethods_ShouldUseSpecialInterfaces(bool Equals(object obj);
Mockolate.SourceGenerators.Tests.MethodSetupsTests ‑ WhenImplementingObjectMethods_ShouldUseSpecialInterfaces(string ToString();
Mockolate.SourceGenerators.Tests.MethodSetupsTests ‑ WhenImplementingObjectMethods_ShouldUseSpecialInterfaces(string ToString();
Mockolate.Tests.Web.ItExtensionsTests+IsHttpContentTests+WithHeadersTests ‑ ShouldVerifyStringHeaders(x-myHeader1 : foo
Mockolate.Tests.Web.ItExtensionsTests+IsHttpContentTests+WithHeadersTests ‑ ShouldVerifyStringHeaders(x-myHeader1 : foo
…
Mockolate.Internal.Tests.Internals.WildcardTests ‑ ShouldSupportMultilineStrings(
)
Mockolate.Internal.Tests.Internals.WildcardTests ‑ ShouldSupportMultilineStrings(
)
Mockolate.Internal.Tests.Setup.MockSetupsTests+IndexersTests ‑ GetMatching_WithAccess_ShouldNotBlockOtherThreadsWhileMatching
Mockolate.Internal.Tests.Setup.MockSetupsTests+IndexersTests ‑ GetMatching_WithPredicate_ShouldNotBlockOtherThreadsWhileMatching
Mockolate.SourceGenerators.Tests.MethodSetupsTests ‑ WhenImplementingObjectMethods_ShouldUseSpecialInterfaces(bool Equals(object obj);
int GetHashCode();, public override bool Equals(object obj), public override int GetHashCode())
Mockolate.SourceGenerators.Tests.MethodSetupsTests ‑ WhenImplementingObjectMethods_ShouldUseSpecialInterfaces(bool Equals(object obj);
int GetHashCode();, public override bool Equals(object obj), public override int GetHashCode())
Mockolate.SourceGenerators.Tests.MethodSetupsTests ‑ WhenImplementingObjectMethods_ShouldUseSpecialInterfaces(string ToString();
bool Equals(object obj);, public override string ToString(), public override bool Equals(object obj))
Mockolate.SourceGenerators.Tests.MethodSetupsTests ‑ WhenImplementingObjectMethods_ShouldUseSpecialInterfaces(string ToString();
bool Equals(object? obj);
int GetHashCode();, public override string ToString(), public override int GetHashCode(), public override bool Equals(object? obj))
Mockolate.SourceGenerators.Tests.MethodSetupsTests ‑ WhenImplementingObjectMethods_ShouldUseSpecialInterfaces(string ToString();
int GetHashCode();, public override string ToString(), public override int GetHashCode())
Mockolate.SourceGenerators.Tests.MethodSetupsTests ‑ WhenImplementingObjectMethods_ShouldUseSpecialInterfaces(string ToString();
bool Equals(object obj);, public override string ToString(), public override bool Equals(object obj))
…

@github-actions

Copy link
Copy Markdown

🚀 Benchmark Results

Details

BenchmarkDotNet v0.15.8, Linux Ubuntu 26.04.1 LTS (Resolute Raccoon)
AMD EPYC 9V74 3.69GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.303
[Host] : .NET 10.0.14 (10.0.14, 10.0.1426.52326), X64 RyuJIT x86-64-v4

Job=InProcess Toolchain=InProcessEmitToolchain IterationCount=15
LaunchCount=1 WarmupCount=10

Method N Mean Error StdDev Ratio Allocated Alloc Ratio
baseline* 1 270.8 ns 2.96 ns 2.47 ns 0.94 1976 B 1.00
Mockolate 1 287.4 ns 5.17 ns 4.84 ns 1.00 1976 B 1.00
Imposter 1 200.1 ns 2.47 ns 2.31 ns 0.70 928 B 0.47
TUnitMocks 1 409.4 ns 8.03 ns 7.52 ns 1.42 2088 B 1.06
Moq 1 106,823.0 ns 284.57 ns 237.62 ns 371.78 14938 B 7.56
NSubstitute 1 4,913.9 ns 86.88 ns 77.02 ns 17.10 9336 B 4.72
FakeItEasy 1 4,623.5 ns 105.33 ns 93.37 ns 16.09 8244 B 4.17
baseline* 10 496.7 ns 13.62 ns 12.74 ns 0.97 2192 B 1.00
Mockolate 10 513.1 ns 6.30 ns 5.90 ns 1.00 2192 B 1.00
Imposter 10 489.2 ns 7.38 ns 6.90 ns 0.95 1792 B 0.82
TUnitMocks 10 1,137.3 ns 19.79 ns 17.54 ns 2.22 3840 B 1.75
Moq 10 110,737.0 ns 693.68 ns 614.93 ns 215.83 18930 B 8.64
NSubstitute 10 7,146.6 ns 42.20 ns 37.41 ns 13.93 12360 B 5.64
FakeItEasy 10 7,295.6 ns 108.16 ns 101.17 ns 14.22 15786 B 7.20
Details

BenchmarkDotNet v0.15.8, Linux Ubuntu 26.04.1 LTS (Resolute Raccoon)
AMD EPYC 7763 2.69GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.303
[Host] : .NET 10.0.14 (10.0.14, 10.0.1426.52326), X64 RyuJIT x86-64-v3

Job=InProcess Toolchain=InProcessEmitToolchain IterationCount=15
LaunchCount=1 WarmupCount=10

CreateMock Mean Error StdDev Ratio Allocated Alloc Ratio
baseline* 20.24 ns 0.276 ns 0.258 ns 1.07 160 B 1.00
Mockolate 19.00 ns 0.892 ns 0.834 ns 1.00 160 B 1.00
Imposter 116.88 ns 2.148 ns 1.794 ns 6.16 576 B 3.60
TUnitMocks 41.47 ns 0.942 ns 0.786 ns 2.19 216 B 1.35
Moq 1,477.07 ns 23.874 ns 22.332 ns 77.87 2096 B 13.10
NSubstitute 2,029.76 ns 47.064 ns 44.023 ns 107.01 5048 B 31.55
FakeItEasy 1,741.65 ns 7.286 ns 6.458 ns 91.82 2763 B 17.27
Details

BenchmarkDotNet v0.15.8, Linux Ubuntu 26.04.1 LTS (Resolute Raccoon)
AMD EPYC 7763 2.91GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.303
[Host] : .NET 10.0.14 (10.0.14, 10.0.1426.52326), X64 RyuJIT x86-64-v3

Job=InProcess Toolchain=InProcessEmitToolchain IterationCount=15
LaunchCount=1 WarmupCount=10

Event Mean Error StdDev Ratio Allocated Alloc Ratio
baseline* 310.9 ns 9.25 ns 8.65 ns 1.08 1744 B 1.00
Mockolate 288.5 ns 9.70 ns 9.07 ns 1.00 1744 B 1.00
Imposter 183.2 ns 1.81 ns 1.60 ns 0.64 512 B 0.29
TUnitMocks 186.8 ns 2.36 ns 2.21 ns 0.65 1392 B 0.80
Moq 15,651.4 ns 108.54 ns 90.64 ns 54.29 12810 B 7.35
NSubstitute 5,927.5 ns 32.07 ns 30.00 ns 20.56 9264 B 5.31
FakeItEasy 215,494.6 ns 1,478.59 ns 1,310.73 ns 747.51 15755 B 9.03
Details

BenchmarkDotNet v0.15.8, Linux Ubuntu 26.04.1 LTS (Resolute Raccoon)
AMD EPYC 7763 3.04GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.303
[Host] : .NET 10.0.14 (10.0.14, 10.0.1426.52326), X64 RyuJIT x86-64-v3

Job=InProcess Toolchain=InProcessEmitToolchain IterationCount=15
LaunchCount=1 WarmupCount=10

Property N Mean Error StdDev Ratio Allocated Alloc Ratio
baseline* 1 328.3 ns 5.68 ns 5.31 ns 0.63 2472 B 1.00
Mockolate 1 523.9 ns 21.24 ns 19.87 ns 1.00 2472 B 1.00
Imposter 1 179.3 ns 4.83 ns 4.52 ns 0.34 664 B 0.27
TUnitMocks 1 449.8 ns 4.09 ns 3.82 ns 0.86 1696 B 0.69
Moq 1 11,920.0 ns 73.56 ns 68.81 ns 22.78 10513 B 4.25
NSubstitute 1 7,476.0 ns 29.83 ns 24.91 ns 14.29 11720 B 4.74
FakeItEasy 1 8,286.8 ns 33.84 ns 28.26 ns 15.84 11508 B 4.66
baseline* 10 794.2 ns 4.16 ns 3.47 ns 0.81 2976 B 1.00
Mockolate 10 985.7 ns 1.85 ns 1.54 ns 1.00 2976 B 1.00
Imposter 10 346.3 ns 4.23 ns 3.75 ns 0.35 952 B 0.32
TUnitMocks 10 1,571.0 ns 18.28 ns 17.10 ns 1.59 4048 B 1.36
Moq 10 18,624.1 ns 177.88 ns 157.69 ns 18.89 17441 B 5.86
NSubstitute 10 17,557.2 ns 125.53 ns 104.82 ns 17.81 21585 B 7.25
FakeItEasy 10 20,501.5 ns 63.79 ns 56.55 ns 20.80 31546 B 10.60
Details

BenchmarkDotNet v0.15.8, Linux Ubuntu 26.04.1 LTS (Resolute Raccoon)
AMD EPYC 7763 3.24GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.303
[Host] : .NET 10.0.14 (10.0.14, 10.0.1426.52326), X64 RyuJIT x86-64-v3

Job=InProcess Toolchain=InProcessEmitToolchain IterationCount=15
LaunchCount=1 WarmupCount=10

Callback Mean Error StdDev Ratio Allocated Alloc Ratio
baseline* 675.4 ns 8.29 ns 7.75 ns 2.17 1608 B 1.00
Mockolate 311.7 ns 6.61 ns 5.86 ns 1.00 1608 B 1.00
Imposter 226.4 ns 2.00 ns 1.77 ns 0.73 744 B 0.46
TUnitMocks 480.8 ns 2.86 ns 2.53 ns 1.54 2064 B 1.28
Moq 97,685.3 ns 444.78 ns 371.41 ns 313.51 9096 B 5.66
NSubstitute 4,721.0 ns 35.37 ns 31.35 ns 15.15 7896 B 4.91
FakeItEasy 4,820.3 ns 53.88 ns 47.76 ns 15.47 6970 B 4.33
Details

BenchmarkDotNet v0.15.8, Linux Ubuntu 26.04.1 LTS (Resolute Raccoon)
AMD EPYC 7763 2.61GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.303
[Host] : .NET 10.0.14 (10.0.14, 10.0.1426.52326), X64 RyuJIT x86-64-v3

Job=InProcess Toolchain=InProcessEmitToolchain IterationCount=15
LaunchCount=1 WarmupCount=10

Indexer N Mean Error StdDev Ratio Allocated Alloc Ratio
baseline* 1 887.8 ns 7.54 ns 7.05 ns 0.86 3.77 KB 0.99
Mockolate 1 1,031.6 ns 26.03 ns 23.08 ns 1.00 3.81 KB 1.00
Imposter 1 390.4 ns 5.18 ns 4.84 ns 0.38 1.19 KB 0.31
Moq 1 221,081.3 ns 1,005.37 ns 891.23 ns 214.41 20.45 KB 5.36
NSubstitute 1 10,829.6 ns 72.14 ns 63.95 ns 10.50 12.78 KB 3.35
FakeItEasy 1 12,546.4 ns 63.36 ns 59.27 ns 12.17 13.63 KB 3.57
baseline* 10 2,446.2 ns 14.59 ns 13.64 ns 0.94 4.82 KB 0.99
Mockolate 10 2,605.2 ns 9.34 ns 8.28 ns 1.00 4.87 KB 1.00
Imposter 10 943.5 ns 8.33 ns 7.38 ns 0.36 2.24 KB 0.46
Moq 10 236,021.4 ns 915.29 ns 811.38 ns 90.60 29.97 KB 6.16
NSubstitute 10 26,162.8 ns 172.14 ns 161.02 ns 10.04 25.58 KB 5.26
FakeItEasy 10 26,392.5 ns 116.58 ns 109.05 ns 10.13 32.97 KB 6.77

baseline* rows show the corresponding Mockolate benchmark from the most recent successful main branch build with results, for regression comparison.

@vbreuss
vbreuss merged commit 1f0c6d3 into main Oct 30, 2026
21 checks passed
@vbreuss
vbreuss deleted the fix/indexer-matching-lock branch October 30, 2026 00:57
github-actions Bot added a commit that referenced this pull request Oct 30, 2026
github-actions Bot added a commit that referenced this pull request Oct 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants