-
-
Notifications
You must be signed in to change notification settings - Fork 119
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add optional hash collision mitigation to ServiceRegistry
- Loading branch information
1 parent
c5f2b93
commit 2a93dc4
Showing
2 changed files
with
89 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()))}") | ||
{ | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |