Skip to content

Commit

Permalink
Add optional hash collision mitigation to ServiceRegistry
Browse files Browse the repository at this point in the history
  • Loading branch information
jwoodman510 authored and jeremydmiller committed Aug 16, 2024
1 parent c5f2b93 commit 2a93dc4
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 0 deletions.
14 changes: 14 additions & 0 deletions src/Lamar/IoC/LamarInstanceHashCollisionException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.Collections.Generic;
using System;
using System.Linq;
using JasperFx.Core.Reflection;

namespace Lamar.IoC;

public class LamarInstanceHashCollisionException : LamarException
{
public LamarInstanceHashCollisionException(int instanceHash, IEnumerable<Type> serviceTypes) : base(
$"Duplicate hash '{instanceHash}' generated for services: {string.Join(", ", serviceTypes.Select(x => x.FullNameInCode()))}")
{
}
}
75 changes: 75 additions & 0 deletions src/Lamar/ServiceRegistry.HashCollisionMitigation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using Lamar.IoC;
using Lamar.IoC.Instances;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Lamar;

public partial class ServiceRegistry
{
/// <summary>
/// Finds and mitigates <see cref="Instance"/> hash code collisions in the current <see cref="ServiceRegistry"/> instance.<br/>
/// Hash codes are regenerated by renaming the instances where collisions exist with the provided <paramref name="instanceRenamePolicy"/>.<br/>
/// When the <paramref name="retryLimit"/> is met, and a hash collision is detected, a <see cref="LamarInstanceHashCollisionException" /> is thrown.
/// </summary>
/// <param name="retryLimit"></param>
/// <param name="instanceRenamePolicy"></param>
/// <exception cref="LamarInstanceHashCollisionException"></exception>
public void MitigateInstanceHashCollisions(int retryLimit = 3, Func<string, string> instanceRenamePolicy = null)
{
bool shouldMitigateCollisions = true;
int remaininingRetries = retryLimit;

Func<string, string> renameInstance = instanceRenamePolicy ?? (name => $"{name}_x");

while (shouldMitigateCollisions && remaininingRetries > 0)
{
shouldMitigateCollisions = TryRemoveInstanceHashCollisions(renameInstance);
remaininingRetries--;
}

var collision = GetInstanceHashCollisions().FirstOrDefault();

if (collision != null)
{
throw new LamarInstanceHashCollisionException(collision.Key, collision.Select(x => x.ServiceType));
}
}

internal bool TryRemoveInstanceHashCollisions(Func<string, string> instanceRenamePolicy)
{
var hasCollision = false;

foreach (var collision in GetInstanceHashCollisions())
{
hasCollision = true;
HandleInstanceHashCollisions(collision, instanceRenamePolicy);
}

return hasCollision;
}

internal void HandleInstanceHashCollisions(IGrouping<int, Instance> collision, Func<string, string> instanceRenamePolicy)
{
var namedInstances = collision.Where(x => x.IsExplicitlyNamed).ToList();

if (namedInstances.Count > 1)
{
throw new LamarInstanceHashCollisionException(collision.Key, namedInstances.Select(x => x.ServiceType));
}

foreach (var instance in collision.Except(namedInstances))
{
instance.Name = instanceRenamePolicy(instance.Name);
}
}

internal IEnumerable<IGrouping<int, Instance>> GetInstanceHashCollisions()
{
return this.Select(x => x.ImplementationInstance)
.OfType<Instance>()
.GroupBy(x => x.Hash)
.Where(x => x.Select(y => y.ServiceType).Distinct().Count() > 1);
}
}

0 comments on commit 2a93dc4

Please sign in to comment.