Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replacement for Dictionary holding Clients #7

Open
Chroma72 opened this issue Jan 25, 2024 · 1 comment
Open

Replacement for Dictionary holding Clients #7

Chroma72 opened this issue Jan 25, 2024 · 1 comment

Comments

@Chroma72
Copy link

Chroma72 commented Jan 25, 2024

Fixed your dictionary issue:

 public class ExpiringCache
 {
     private ConcurrentDictionary<SocketAddress, (IPEndPoint endpoint, DateTime lastAccess)> _cache;
     private TimeSpan _expirationTime;
     private readonly Timer _cleanupTimer;

     public ExpiringCache(TimeSpan expirationTime)
     {
         _cache = new ConcurrentDictionary<SocketAddress, (IPEndPoint, DateTime)>();
         _expirationTime = expirationTime;
         _cleanupTimer = new Timer(Cleanup, null, _expirationTime, _expirationTime);
     }

     public IPEndPoint AddOrUpdateEndPoint(SocketAddress address)
     {
         var now = DateTime.UtcNow;

         // Check if the SocketAddress key already exists
         if (!_cache.TryGetValue(address, out var cacheEntry))
         {
             Console.WriteLine($"New client added.");
             // Create new IPEndPoint from SocketAddress
             var endpoint = new IPEndPoint(IPAddress.Any, 0);
             endpoint = (IPEndPoint)endpoint.Create(address);

             // Add to cache
             cacheEntry = (endpoint, now);
             _cache[address] = cacheEntry;
         }
         else
         {
             // Update last access time for existing entry
             Console.WriteLine($"Existing client activity updated.");
             cacheEntry = (cacheEntry.endpoint, now);
             _cache[address] = cacheEntry;
         }

         return cacheEntry.endpoint;
     }

     private void Cleanup(object state)
     {
         Console.WriteLine($"Performing client cleanup.");

         var now = DateTime.UtcNow;
         foreach (var kvp in _cache)
         {
             if (now - kvp.Value.lastAccess > _expirationTime)
             {
                 // Remove old entries
                 Console.WriteLine($"Client removed for inactivity.");
                 _cache.TryRemove(kvp.Key, out _);
             }
         }
     }
 }
@enclave-alistair
Copy link
Contributor

enclave-alistair commented Jan 25, 2024

There's a couple of issues/improvements here:

  • Make sure to clone the SocketAddress when you insert it as a key into the dictionary; I made this mistake originally, and put the same instance into the dictionary, meaning the addresses would always be equal.

  • Don't write to console, certainly not in the successful lookup case, that will lock on the console and generally ruin the performance.

  • Because the entry in the dictionary is a struct, you should consider using CollectionsMarshal.GetValueRefOrNullRef to get a reference to the value type inside the dictionary, rather than needing to copy the value in and out of the dictionary when you update the time.

  • Don't use DateTime.UtcNow to update; DateTime.UtcNow is relatively slow compared to something like Environment.TickCount64:

    | Method          | Mean      | Error     | StdDev    | Ratio |
    |---------------- |----------:|----------:|----------:|------:|
    | DateTime.UtcNow | 25.618 ns | 0.4870 ns | 0.8530 ns |  1.00 |
    | TickCount64     |  1.856 ns | 0.0737 ns | 0.2092 ns |  0.08 | 
    

    Because you only compare about relative time rather than absolute time, you can save a bunch of CPU cycles by using TickCount64, and store the long instead of the DateTime.

  • There's no explicit disposal on this class to stop the Timer from running.

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

No branches or pull requests

2 participants