Skip to content

Commit 76bf589

Browse files
authored
Merge pull request #512 from /issues/498
Fix collision handling in`FixedSizeDictionary`
2 parents 4d08171 + 9eecb5a commit 76bf589

3 files changed

Lines changed: 41 additions & 20 deletions

File tree

source/Handlebars.Test/Collections/FixedSizeDictionaryTests.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,31 @@ static FixedSizeDictionaryTests()
1515
FixedSizeDictionary = new FixedSizeDictionary<object, object, EqualityComparers.ReferenceEqualityComparer<object>>(15, 17, referenceEqualityComparer);
1616
}
1717

18+
[Fact]
19+
public void AddOrReplace_Collisions()
20+
{
21+
var comparer = new CollisionsComparer(new Random().Next());
22+
var dictionary = new FixedSizeDictionary<object, object, CollisionsComparer>(16, 7, comparer);
23+
for (var i = 0; i < dictionary.Capacity; i++)
24+
{
25+
dictionary.AddOrReplace(new object(), new object(), out _);
26+
}
27+
}
28+
29+
private readonly struct CollisionsComparer : IEqualityComparer<object>
30+
{
31+
private readonly int _hash;
32+
33+
public CollisionsComparer(int hash)
34+
{
35+
_hash = hash;
36+
}
37+
38+
public bool Equals(object x, object y) => false;
39+
40+
public int GetHashCode(object obj) => _hash;
41+
}
42+
1843
[Fact]
1944
public void AddOrReplace()
2045
{

source/Handlebars/Collections/FixedSizeDictionary.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,7 @@ public void AddOrReplace(in TKey key, in TValue value, out EntryIndex<TKey> inde
288288

289289
ref var entryReference = ref _entries[entry.Index];
290290
entryIndex = entryReference.Index + 1;
291+
var downstreamEntryIndex = entryIndex - 1;
291292

292293
for (; entryIndex < _entries.Length; entryIndex++)
293294
{
@@ -301,7 +302,10 @@ public void AddOrReplace(in TKey key, in TValue value, out EntryIndex<TKey> inde
301302
return;
302303
}
303304

304-
entryIndex = (bucketIndex * _bucketMask) - 1;
305+
// we've searched all entries in -> direction, now visiting in <- direction
306+
entryIndex = downstreamEntryIndex - 1;
307+
// handling special case when `downstreamEntryIndex` can result into value >= _entries.Length
308+
if (entryIndex >= _entries.Length) entryIndex = _entries.Length - 1;
305309
for (; entryIndex >= 0; entryIndex--)
306310
{
307311
entry = _entries[entryIndex];

source/Handlebars/Runtime/AmbientContext.cs

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Runtime.CompilerServices;
4-
#if !NET451 && !NET452
5-
using System.Threading;
6-
#else
7-
using HandlebarsDotNet.Polyfills;
8-
#endif
9-
using HandlebarsDotNet.Collections;
104
using HandlebarsDotNet.IO;
115
using HandlebarsDotNet.ObjectDescriptors;
126
using HandlebarsDotNet.PathStructure;
@@ -16,14 +10,18 @@ namespace HandlebarsDotNet.Runtime
1610
{
1711
public sealed class AmbientContext : IDisposable
1812
{
19-
private static readonly InternalObjectPool<AmbientContext, Policy> Pool = new InternalObjectPool<AmbientContext, Policy>(new Policy());
13+
private static readonly InternalObjectPool<AmbientContext, Policy> Pool = new(new Policy());
2014

21-
private static readonly AsyncLocal<ImmutableStack<AmbientContext>> Local = new AsyncLocal<ImmutableStack<AmbientContext>>();
15+
[ThreadStatic]
16+
private static Stack<AmbientContext> _local;
17+
18+
private static Stack<AmbientContext> Local =>
19+
_local ??= new Stack<AmbientContext>();
2220

2321
public static AmbientContext Current
2422
{
2523
[MethodImpl(MethodImplOptions.AggressiveInlining)]
26-
get => Local.Value.Peek();
24+
get => Local.Count > 0 ? Local.Peek() : null;
2725
}
2826

2927
public static AmbientContext Create(
@@ -67,12 +65,9 @@ public static AmbientContext Create(
6765

6866
public static DisposableContainer Use(AmbientContext ambientContext)
6967
{
70-
Local.Value = Local.Value.Push(ambientContext);
68+
Local.Push(ambientContext);
7169

72-
return new DisposableContainer(() =>
73-
{
74-
Local.Value = Local.Value.Pop(out _);
75-
});
70+
return new DisposableContainer(() => Local.Pop());
7671
}
7772

7873
private AmbientContext()
@@ -89,14 +84,11 @@ private AmbientContext()
8984

9085
public ObjectDescriptorFactory ObjectDescriptorFactory { get; private set; }
9186

92-
public Dictionary<string, object> Bag { get; } = new Dictionary<string, object>();
87+
public Dictionary<string, object> Bag { get; } = new();
9388

9489
private struct Policy : IInternalObjectPoolPolicy<AmbientContext>
9590
{
96-
public AmbientContext Create()
97-
{
98-
return new AmbientContext();
99-
}
91+
public AmbientContext Create() => new();
10092

10193
public bool Return(AmbientContext item)
10294
{

0 commit comments

Comments
 (0)