Skip to content

Commit 3d7ce7a

Browse files
committed
feat: FilterMemory serialization behavior is supported #17
1 parent e185b62 commit 3d7ce7a

32 files changed

+609
-69
lines changed

src/BloomFilter/AsyncLock.cs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
using System;
2+
using System.Runtime.CompilerServices;
3+
using System.Threading;
4+
using System.Threading.Tasks;
5+
6+
namespace BloomFilter;
7+
8+
/// <summary>
9+
/// AsyncLock
10+
/// </summary>
11+
internal readonly struct AsyncLock : IDisposable
12+
{
13+
private readonly SemaphoreSlim _semaphore;
14+
private readonly Releaser _releaser;
15+
private readonly Task<Releaser> _releaserTask;
16+
17+
public int MaxCount { get; }
18+
19+
public AsyncLock() : this(1)
20+
{
21+
}
22+
23+
public AsyncLock(int maxCount = 1)
24+
{
25+
MaxCount = maxCount;
26+
_semaphore = new SemaphoreSlim(maxCount);
27+
_releaser = new Releaser(_semaphore);
28+
_releaserTask = Task.FromResult(_releaser);
29+
}
30+
31+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
32+
public Releaser Acquire()
33+
{
34+
_semaphore.Wait();
35+
return _releaser;
36+
}
37+
38+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
39+
public Task<Releaser> AcquireAsync(bool continueOnCapturedContext = false)
40+
{
41+
var acquireAsync = _semaphore.WaitAsync();
42+
return Return(acquireAsync, continueOnCapturedContext);
43+
}
44+
45+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
46+
public void Dispose() => _semaphore.Dispose();
47+
48+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
49+
private Task<Releaser> Return(Task acquireAsync, bool continueOnCapturedContext)
50+
{
51+
return acquireAsync.Status == TaskStatus.RanToCompletion
52+
? _releaserTask
53+
: WaitForAcquireAsync(acquireAsync, continueOnCapturedContext);
54+
}
55+
56+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
57+
private async Task<Releaser> WaitForAcquireAsync(Task acquireAsync, bool continueOnCapturedContext)
58+
{
59+
await acquireAsync.ConfigureAwait(continueOnCapturedContext);
60+
return _releaser;
61+
}
62+
63+
public readonly struct Releaser(SemaphoreSlim? semaphore) : IDisposable
64+
{
65+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
66+
public void Dispose() => semaphore?.Release();
67+
}
68+
69+
70+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
71+
public int GetRemainingCount()
72+
{
73+
return MaxCount - _semaphore.CurrentCount;
74+
}
75+
76+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
77+
public int GetCurrentCount()
78+
{
79+
return _semaphore.CurrentCount;
80+
}
81+
}
Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,41 @@
1-
using Microsoft.Extensions.DependencyInjection;
1+
using System;
2+
using Microsoft.Extensions.DependencyInjection;
23
using Microsoft.Extensions.DependencyInjection.Extensions;
34

45
namespace BloomFilter.Configurations;
56

67
public class FilterMemoryOptionsExtension : IBloomFilterOptionsExtension
78
{
89
private readonly FilterMemoryOptions _options;
10+
private readonly Type? _serializerType;
911

1012
/// <summary>
1113
/// Initializes a new instance of the <see cref="FilterMemoryOptionsExtension"/> class.
1214
/// </summary>
1315
/// <param name="options">Configure.</param>
14-
public FilterMemoryOptionsExtension(FilterMemoryOptions options)
16+
/// <param name="serializerType"></param>
17+
public FilterMemoryOptionsExtension(FilterMemoryOptions options, Type? serializerType = null)
1518
{
1619
_options = options;
20+
_serializerType = serializerType;
1721
}
1822

1923
public void AddServices(IServiceCollection services)
2024
{
2125
services.TryAddSingleton<IBloomFilterFactory, DefaultBloomFilterFactory>();
26+
27+
if (_serializerType is null)
28+
{
29+
services.TryAddSingleton<IFilterMemorySerializer, DefaultFilterMemorySerializer>();
30+
}
31+
else
32+
{
33+
services.TryAddSingleton(typeof(IFilterMemorySerializer), _serializerType);
34+
}
35+
2236
services.AddSingleton<IBloomFilter, FilterMemory>(x =>
2337
{
24-
return new FilterMemory(_options);
38+
return new FilterMemory(_options, x.GetRequiredService<IFilterMemorySerializer>());
2539
});
2640
}
2741
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
using System;
2+
using System.Collections;
3+
using System.IO;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace BloomFilter;
8+
9+
public class DefaultFilterMemorySerializer : IFilterMemorySerializer
10+
{
11+
public async ValueTask SerializeAsync(FilterMemorySerializerParam param, Stream stream)
12+
{
13+
byte[] nameBytes = Encoding.UTF8.GetBytes(param.Name ?? string.Empty);
14+
await stream.WriteAsync(BitConverter.GetBytes(nameBytes.Length), 0, 4);
15+
await stream.WriteAsync(nameBytes, 0, nameBytes.Length);
16+
17+
await stream.WriteAsync(BitConverter.GetBytes(param.ExpectedElements), 0, 8);
18+
await stream.WriteAsync(BitConverter.GetBytes(param.ErrorRate), 0, 8);
19+
await stream.WriteAsync(BitConverter.GetBytes((int)param.Method), 0, 4);
20+
21+
await stream.WriteAsync(BitConverter.GetBytes(param.Buckets.Length), 0, 4);
22+
foreach (var bucket in param.Buckets)
23+
{
24+
byte[] bucketBytes = new byte[bucket.Length / 8 + Mod(bucket.Length)];
25+
bucket.CopyTo(bucketBytes, 0);
26+
await stream.WriteAsync(BitConverter.GetBytes(bucketBytes.Length), 0, 4).ConfigureAwait(false);
27+
await stream.WriteAsync(bucketBytes, 0, bucketBytes.Length).ConfigureAwait(false);
28+
}
29+
}
30+
31+
public async ValueTask<FilterMemorySerializerParam> DeserializeAsync(Stream stream)
32+
{
33+
var param = new FilterMemorySerializerParam();
34+
35+
byte[] lengthBytes = new byte[4];
36+
byte[] int64Bytes = new byte[8];
37+
38+
await ReadExactlyAsync(stream, lengthBytes);
39+
int nameLength = BitConverter.ToInt32(lengthBytes, 0);
40+
41+
byte[] nameBytes = new byte[nameLength];
42+
await ReadExactlyAsync(stream, nameBytes);
43+
param.Name = Encoding.UTF8.GetString(nameBytes);
44+
45+
await ReadExactlyAsync(stream, int64Bytes);
46+
param.ExpectedElements = BitConverter.ToInt64(int64Bytes, 0);
47+
48+
await ReadExactlyAsync(stream, int64Bytes);
49+
param.ErrorRate = BitConverter.ToDouble(int64Bytes, 0);
50+
51+
await ReadExactlyAsync(stream, lengthBytes);
52+
param.Method = (HashMethod)BitConverter.ToInt32(lengthBytes, 0);
53+
54+
await ReadExactlyAsync(stream, lengthBytes);
55+
int bucketsLength = BitConverter.ToInt32(lengthBytes, 0);
56+
param.Buckets = new BitArray[bucketsLength];
57+
58+
for (int i = 0; i < bucketsLength; i++)
59+
{
60+
await ReadExactlyAsync(stream, lengthBytes);
61+
int bitArrayLength = BitConverter.ToInt32(lengthBytes, 0);
62+
63+
byte[] bucketBytes = new byte[bitArrayLength];
64+
await ReadExactlyAsync(stream, bucketBytes);
65+
66+
param.Buckets[i] = new BitArray(bucketBytes);
67+
}
68+
69+
return param;
70+
}
71+
72+
private async Task ReadExactlyAsync(Stream stream, byte[] data)
73+
{
74+
#if NET7_0_OR_GREATER
75+
await stream.ReadExactlyAsync(data, 0, data.Length).ConfigureAwait(false);
76+
#else
77+
await stream.ReadAsync(data, 0, data.Length).ConfigureAwait(false);
78+
#endif
79+
}
80+
81+
private int Mod(int len) => len % 8 > 0 ? 1 : 0;
82+
}

src/BloomFilter/Filter.cs

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,27 +21,27 @@ public abstract class Filter : IBloomFilter
2121
/// <summary>
2222
/// <see cref="HashFunction"/>
2323
/// </summary>
24-
public HashFunction Hash { get; }
24+
public HashFunction Hash { get; private set; }
2525

2626
/// <summary>
2727
/// the Capacity of the Bloom filter
2828
/// </summary>
29-
public long Capacity { get; }
29+
public long Capacity { get; private set; }
3030

3131
/// <summary>
3232
/// number of hash functions
3333
/// </summary>
34-
public int Hashes { get; }
34+
public int Hashes { get; private set; }
3535

3636
/// <summary>
3737
/// the expected elements.
3838
/// </summary>
39-
public long ExpectedElements { get; }
39+
public long ExpectedElements { get; private set; }
4040

4141
/// <summary>
4242
/// the number of expected elements
4343
/// </summary>
44-
public double ErrorRate { get; }
44+
public double ErrorRate { get; private set; }
4545

4646
/// <summary>
4747
/// Initializes a new instance of the <see cref="Filter"/> class.
@@ -101,6 +101,21 @@ public Filter(string name, long capacity, int hashes, HashFunction hashFunction)
101101
ErrorRate = BestP(hashes, capacity, ExpectedElements);
102102
}
103103

104+
protected void SetFilterParam(long expectedElements, double errorRate, HashMethod method)
105+
{
106+
if (expectedElements < 1)
107+
throw new ArgumentOutOfRangeException("expectedElements", expectedElements, "expectedElements must be > 0");
108+
if (errorRate >= 1 || errorRate <= 0)
109+
throw new ArgumentOutOfRangeException("errorRate", errorRate, string.Format("errorRate must be between 0 and 1, exclusive. Was {0}", errorRate));
110+
111+
ExpectedElements = expectedElements;
112+
ErrorRate = errorRate;
113+
Hash = HashFunction.Functions[method];
114+
115+
Capacity = BestM(expectedElements, errorRate);
116+
Hashes = BestK(expectedElements, Capacity);
117+
}
118+
104119
/// <summary>
105120
/// Adds the passed value to the filter.
106121
/// </summary>

src/BloomFilter/FilterBuilder.cs

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,18 @@ public partial class FilterBuilder
1414
/// <returns></returns>
1515
public static IBloomFilter Build(FilterMemoryOptions options)
1616
{
17-
return new FilterMemory(options);
17+
return new FilterMemory(options, new DefaultFilterMemorySerializer());
18+
}
19+
20+
/// <summary>
21+
/// Creates a BloomFilter for the specified expected element
22+
/// </summary>
23+
/// <param name="options"><see cref="FilterMemoryOptions"/></param>
24+
/// <param name="filterMemorySerializer"><see cref="IFilterMemorySerializer"/></param>
25+
/// <returns></returns>
26+
public static IBloomFilter Build(FilterMemoryOptions options, IFilterMemorySerializer filterMemorySerializer)
27+
{
28+
return new FilterMemory(options, filterMemorySerializer);
1829
}
1930

2031
/// <summary>
@@ -71,10 +82,11 @@ public static IBloomFilter Build(long expectedElements, double errorRate, string
7182
/// <param name="errorRate">The error rate.</param>
7283
/// <param name="hashMethod">The hash method.</param>
7384
/// <param name="name"></param>
85+
/// <param name="filterMemorySerializer"></param>
7486
/// <returns></returns>
75-
public static IBloomFilter Build(long expectedElements, double errorRate, HashMethod hashMethod, string name = BloomFilterConstValue.DefaultInMemoryName)
87+
public static IBloomFilter Build(long expectedElements, double errorRate, HashMethod hashMethod, string name = BloomFilterConstValue.DefaultInMemoryName, IFilterMemorySerializer? filterMemorySerializer = null)
7688
{
77-
return new FilterMemory(name, expectedElements, errorRate, HashFunction.Functions[hashMethod]);
89+
return new FilterMemory(name, expectedElements, errorRate, HashFunction.Functions[hashMethod], filterMemorySerializer ?? new DefaultFilterMemorySerializer());
7890
}
7991

8092
/// <summary>
@@ -84,9 +96,10 @@ public static IBloomFilter Build(long expectedElements, double errorRate, HashMe
8496
/// <param name="errorRate">The error rate.</param>
8597
/// <param name="hashFunction">The hash function.</param>
8698
/// <param name="name"></param>
99+
/// <param name="filterMemorySerializer"></param>
87100
/// <returns></returns>
88-
public static IBloomFilter Build(long expectedElements, double errorRate, HashFunction hashFunction, string name = BloomFilterConstValue.DefaultInMemoryName)
101+
public static IBloomFilter Build(long expectedElements, double errorRate, HashFunction hashFunction, string name = BloomFilterConstValue.DefaultInMemoryName, IFilterMemorySerializer? filterMemorySerializer = null)
89102
{
90-
return new FilterMemory(name, expectedElements, errorRate, hashFunction);
103+
return new FilterMemory(name, expectedElements, errorRate, hashFunction, filterMemorySerializer ?? new DefaultFilterMemorySerializer());
91104
}
92105
}

0 commit comments

Comments
 (0)