Skip to content

Commit

Permalink
added lru cache, first draft
Browse files Browse the repository at this point in the history
  • Loading branch information
mizrael committed Oct 22, 2023
1 parent 0ed692c commit 9bd9303
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 10 deletions.
9 changes: 9 additions & 0 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project>
<PropertyGroup>
<AccelerateBuildsInVisualStudio>true</AccelerateBuildsInVisualStudio>
</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<Optimize>true</Optimize>
</PropertyGroup>
</Project>
6 changes: 1 addition & 5 deletions src/EvenireDB.Server/EvenireDB.Server.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,9 @@
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<Version>0.1.0.0</Version>
<Version>0.1.0.0</Version>
</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<Optimize>true</Optimize>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Asp.Versioning.Http" Version="7.1.0" />
<PackageReference Include="Grpc.AspNetCore" Version="2.32.0" />
Expand Down
6 changes: 1 addition & 5 deletions src/EvenireDB/EvenireDB.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,9 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<Optimize>true</Optimize>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="7.0.0" />
</ItemGroup>
Expand Down
85 changes: 85 additions & 0 deletions src/EvenireDB/Utils/LRUCache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
namespace EvenireDB.Utils
{
public class LRUCache<TKey, TValue>
where TKey : notnull
{
private class Node
{
public TKey Key { get; init; }

Check warning on line 8 in src/EvenireDB/Utils/LRUCache.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'Key' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
public TValue Value { get; init; }

Check warning on line 9 in src/EvenireDB/Utils/LRUCache.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'Value' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
public Node? Next { get; set; }
public Node? Previous { get; set; }
}

private readonly Dictionary<TKey, Node> _cache;
private readonly uint _capacity;
private Node? _head;
private Node? _tail;

public LRUCache(uint capacity)
{
_capacity = capacity;
_cache = new Dictionary<TKey, Node>((int)capacity);
}

public async ValueTask<TValue> GetOrAddAsync(
TKey key,
Func<TKey, CancellationToken, ValueTask<TValue>> valueFactory,
CancellationToken cancellationToken = default)
{
if (_cache.TryGetValue(key, out var node))
{
MoveToHead(node);
return node.Value;
}

var value = await valueFactory(key, cancellationToken).ConfigureAwait(false);
if (_cache.Count == _capacity)
{
_cache.Remove(_tail.Key);

Check warning on line 39 in src/EvenireDB/Utils/LRUCache.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.

_tail = _tail.Previous;

if(_tail != null)
_tail.Next = null;
}

node = new Node { Key = key, Value = value, Next = _head };
if (_head != null)
_head.Previous = node;

_head = node;
if (_tail == null)
_tail = node;

_cache.Add(key, node);

return node.Value;
}

private void MoveToHead(Node node)
{
if (node == _head)
return;

if (node.Previous != null)
node.Previous.Next = node.Next;

if (node.Next != null)
node.Next.Previous = node.Previous;

if (_tail == node)
_tail = node.Previous;

node.Previous = null;
node.Next = _head;

if (_head != null)
_head.Previous = node;

_head = node;
}

public int Count => _cache.Count;
}
}
1 change: 1 addition & 0 deletions tests/EvenireDB.Tests/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
global using Xunit;
global using FluentAssertions;
global using NSubstitute;
global using EvenireDB.Utils;
34 changes: 34 additions & 0 deletions tests/EvenireDB.Tests/LRUCacheTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
namespace EvenireDB.Tests
{
public class LRUCacheTests
{
[Fact]
public async Task GetOrAddAsync_should_add_when_not_existing()
{
var cache = new LRUCache<string, string>(1);
cache.Count.Should().Be(0);

var value = await cache.GetOrAddAsync("key", (_, _) => new ValueTask<string>("value"));
value.Should().Be("value");

cache.Count.Should().Be(1);
}

[Fact]
public async Task GetOrAddAsync_should_keep_capacity()
{
var cache = new LRUCache<string, string>(1);
cache.Count.Should().Be(0);

var value = await cache.GetOrAddAsync("key", (_, _) => new ValueTask<string>("value"));
value.Should().Be("value");

cache.Count.Should().Be(1);

value = await cache.GetOrAddAsync("key2", (_, _) => new ValueTask<string>("value2"));
value.Should().Be("value2");

cache.Count.Should().Be(1);
}
}
}

0 comments on commit 9bd9303

Please sign in to comment.