Skip to content

Commit

Permalink
Merge branch 'main' into dotty/test-updates-2025-Jan-15
Browse files Browse the repository at this point in the history
  • Loading branch information
tippmar-nr authored Jan 16, 2025
2 parents 0e5e3f0 + f70b64a commit 471afd5
Show file tree
Hide file tree
Showing 22 changed files with 1,299 additions and 190 deletions.
7 changes: 7 additions & 0 deletions .github/workflows/build_profiler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ on:
- "feature/**"
paths-ignore:
- ".github/**" # skip changes to the .github folder (workflows, etc.)

# needs to run on every push to main to keep CodeCov in sync
push:
branches:
- main
paths-ignore:
- ".github/**" # skip changes to the .github folder (workflows, etc.)

# this workflow can be called from another workflow
workflow_call:
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/run_unit_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ on:
branches:
- main
- "feature/**"

# needs to run on every push to main to keep CodeCov in sync
push:
branches:
- main

workflow_dispatch: # allows for manual trigger

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// Copyright 2020 New Relic, Inc. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

using System;
using System.Collections.Generic;
using System.Threading;

namespace NewRelic.Agent.Extensions.Caching
{
/// <summary>
/// A thread-safe LRU cache implementation.
/// </summary>
/// <typeparam name="TKey"></typeparam>
/// <typeparam name="TValue"></typeparam>
public class LRUCache<TKey, TValue>
{
private readonly int _capacity;
private readonly Dictionary<TKey, LinkedListNode<CacheItem>> _cacheMap;
private readonly LinkedList<CacheItem> _lruList;
private readonly ReaderWriterLockSlim _lock = new();

public LRUCache(int capacity)
{
if (capacity <= 0)
{
throw new ArgumentException("Capacity must be greater than zero.", nameof(capacity));
}

_capacity = capacity;
_cacheMap = new Dictionary<TKey, LinkedListNode<CacheItem>>(capacity);
_lruList = new LinkedList<CacheItem>();
}

public TValue Get(TKey key)
{
_lock.EnterUpgradeableReadLock();
try
{
if (_cacheMap.TryGetValue(key, out var node))
{
// Move the accessed node to the front of the list
_lock.EnterWriteLock();
try
{
_lruList.Remove(node);
_lruList.AddFirst(node);
}
finally
{
_lock.ExitWriteLock();
}
return node.Value.Value;
}
throw new KeyNotFoundException("The given key was not present in the cache.");
}
finally
{
_lock.ExitUpgradeableReadLock();
}
}

public void Put(TKey key, TValue value)
{
_lock.EnterWriteLock();
try
{
if (_cacheMap.TryGetValue(key, out var node))
{
// Update the value and move the node to the front of the list
node.Value.Value = value;
_lruList.Remove(node);
_lruList.AddFirst(node);
}
else
{
if (_cacheMap.Count >= _capacity)
{
// Remove the least recently used item
var lruNode = _lruList.Last;
_cacheMap.Remove(lruNode.Value.Key);
_lruList.RemoveLast();
}

// Add the new item to the cache
var cacheItem = new CacheItem(key, value);
var newNode = new LinkedListNode<CacheItem>(cacheItem);
_lruList.AddFirst(newNode);
_cacheMap[key] = newNode;
}
}
finally
{
_lock.ExitWriteLock();
}
}
public bool ContainsKey(TKey key)
{
_lock.EnterReadLock();
try
{
return _cacheMap.ContainsKey(key);
}
finally
{
_lock.ExitReadLock();
}
}

private class CacheItem
{
public TKey Key { get; }
public TValue Value { get; set; }

public CacheItem(TKey key, TValue value)
{
Key = key;
Value = value;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Copyright 2020 New Relic, Inc. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

using System;
using System.Collections.Generic;
using System.Threading;

namespace NewRelic.Agent.Extensions.Caching
{
/// <summary>
/// A thread-safe LRU HashSet implementation.
/// </summary>
/// <typeparam name="T"></typeparam>
public class LRUHashSet<T>
{
private readonly int _capacity;
private readonly HashSet<T> _hashSet;
private readonly LinkedList<T> _lruList;
private readonly ReaderWriterLockSlim _lock = new();

public LRUHashSet(int capacity)
{
if (capacity <= 0)
{
throw new ArgumentException("Capacity must be greater than zero.", nameof(capacity));
}

_capacity = capacity;
_hashSet = new HashSet<T>();
_lruList = new LinkedList<T>();
}

public bool Add(T item)
{
_lock.EnterWriteLock();
try
{
if (_hashSet.Contains(item))
{
// Move the accessed item to the front of the list
_lruList.Remove(item);
_lruList.AddFirst(item);
return false;
}
else
{
if (_hashSet.Count >= _capacity)
{
// Remove the least recently used item
var lruItem = _lruList.Last.Value;
_hashSet.Remove(lruItem);
_lruList.RemoveLast();
}

// Add the new item to the set and list
_hashSet.Add(item);
_lruList.AddFirst(item);
return true;
}
}
finally
{
_lock.ExitWriteLock();
}
}

public bool Contains(T item)
{
_lock.EnterReadLock();
try
{
return _hashSet.Contains(item);
}
finally
{
_lock.ExitReadLock();
}
}

public bool Remove(T item)
{
_lock.EnterWriteLock();
try
{
if (_hashSet.Remove(item))
{
_lruList.Remove(item);
return true;
}
return false;
}
finally
{
_lock.ExitWriteLock();
}
}

public int Count
{
get
{
_lock.EnterReadLock();
try
{
return _hashSet.Count;
}
finally
{
_lock.ExitReadLock();
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright 2020 New Relic, Inc. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

using System;

namespace NewRelic.Agent.Extensions.Caching
{
/// <summary>
/// Creates an object that can be used as a dictionary key, which holds a WeakReference&lt;T&gt;
/// </summary>
/// <typeparam name="T"></typeparam>
public class WeakReferenceKey<T> where T : class
{
private WeakReference<T> WeakReference { get; }

public WeakReferenceKey(T cacheKey)
{
WeakReference = new WeakReference<T>(cacheKey);
}

public override bool Equals(object obj)
{
if (obj is WeakReferenceKey<T> otherKey)
{
if (WeakReference.TryGetTarget(out var thisTarget) &&
otherKey.WeakReference.TryGetTarget(out var otherTarget))
{
return ReferenceEquals(thisTarget, otherTarget);
}
}

return false;
}

public override int GetHashCode()
{
if (WeakReference.TryGetTarget(out var target))
{
return target.GetHashCode();
}

return 0;
}

/// <summary>
/// Gets the value from the WeakReference or null if the target has been garbage collected.
/// </summary>
public T Value => WeakReference.TryGetTarget(out var target) ? target : null;
}
}
Loading

0 comments on commit 471afd5

Please sign in to comment.