Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 102 additions & 3 deletions src/Orleans.Core.Abstractions/Manifest/ClusterManifest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using Orleans.Runtime;

Expand All @@ -22,10 +23,32 @@ public sealed class ClusterManifest
public ClusterManifest(
MajorMinorVersion version,
ImmutableDictionary<SiloAddress, GrainManifest> silos)
: this(version, silos, silos.Values.ToImmutableArray())
{
}

/// <summary>
/// Initializes a new instance of the <see cref="ClusterManifest"/> class.
/// </summary>
/// <param name="version">
/// The manifest version.
/// </param>
/// <param name="silos">
/// The silo manifests.
/// </param>
/// <param name="allGrainManifests">
/// All grain manifests, including manifests which are not associated with entries in <paramref name="silos"/>.
/// </param>
public ClusterManifest(
MajorMinorVersion version,
ImmutableDictionary<SiloAddress, GrainManifest> silos,
ImmutableArray<GrainManifest> allGrainManifests)
{
ArgumentNullException.ThrowIfNull(silos);
var deduplicated = Deduplicate(silos, allGrainManifests);
Version = version;
Silos = silos;
AllGrainManifests = silos.Values.ToImmutableArray();
Silos = deduplicated.Silos;
AllGrainManifests = deduplicated.AllGrainManifests;
}

/// <summary>
Expand All @@ -41,9 +64,85 @@ public ClusterManifest(
public ImmutableDictionary<SiloAddress, GrainManifest> Silos { get; }

/// <summary>
/// Gets all grain manifests.
/// Gets all unique grain manifests.
/// </summary>
[Id(2)]
public ImmutableArray<GrainManifest> AllGrainManifests { get; }

private static (ImmutableDictionary<SiloAddress, GrainManifest> Silos, ImmutableArray<GrainManifest> AllGrainManifests) Deduplicate(
ImmutableDictionary<SiloAddress, GrainManifest> silos,
ImmutableArray<GrainManifest> allGrainManifests)
{
var canonicalGrainProperties = new Dictionary<GrainProperties, GrainProperties>();
var canonicalInterfaceProperties = new Dictionary<GrainInterfaceProperties, GrainInterfaceProperties>();
var canonicalManifests = new Dictionary<GrainManifest, GrainManifest>();
var uniqueManifests = ImmutableArray.CreateBuilder<GrainManifest>();
var siloBuilder = ImmutableDictionary.CreateBuilder<SiloAddress, GrainManifest>(silos.KeyComparer, silos.ValueComparer);

foreach (var entry in silos)
{
siloBuilder[entry.Key] = GetCanonicalManifest(entry.Value);
}

if (!allGrainManifests.IsDefault)
{
foreach (var manifest in allGrainManifests)
{
GetCanonicalManifest(manifest);
}
}

return (siloBuilder.ToImmutable(), uniqueManifests.ToImmutable());

GrainManifest GetCanonicalManifest(GrainManifest manifest)
{
manifest = DeduplicateManifest(manifest);
if (canonicalManifests.TryGetValue(manifest, out var canonicalManifest))
{
return canonicalManifest;
}

canonicalManifests.Add(manifest, manifest);
uniqueManifests.Add(manifest);
return manifest;
}

GrainManifest DeduplicateManifest(GrainManifest manifest)
{
var grains = DeduplicateProperties(manifest.Grains, canonicalGrainProperties, out var grainsModified);
var interfaces = DeduplicateProperties(manifest.Interfaces, canonicalInterfaceProperties, out var interfacesModified);
return grainsModified || interfacesModified ? new GrainManifest(grains, interfaces) : manifest;
}

static ImmutableDictionary<TKey, TValue> DeduplicateProperties<TKey, TValue>(
ImmutableDictionary<TKey, TValue> properties,
Dictionary<TValue, TValue> canonicalProperties,
out bool modified)
where TKey : notnull
where TValue : class
{
var builder = ImmutableDictionary.CreateBuilder<TKey, TValue>(properties.KeyComparer, properties.ValueComparer);
modified = false;
foreach (var entry in properties)
{
if (canonicalProperties.TryGetValue(entry.Value, out var canonicalProperty))
{
if (!ReferenceEquals(canonicalProperty, entry.Value))
{
modified = true;
}

builder[entry.Key] = canonicalProperty;
}
else
{
canonicalProperties.Add(entry.Value, entry.Value);
builder[entry.Key] = entry.Value;
}
}

return modified ? builder.ToImmutable() : properties;
}
Comment on lines +124 to +145
}
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
Expand All @@ -10,8 +11,11 @@ namespace Orleans.Metadata
/// Information about a communication interface.
/// </summary>
[Serializable, GenerateSerializer, Immutable]
public sealed class GrainInterfaceProperties
public sealed class GrainInterfaceProperties : IEquatable<GrainInterfaceProperties>
{
[NonSerialized]
private int? _hashCode;

/// <summary>
/// Initializes a new instance of the <see cref="GrainInterfaceProperties"/> class.
/// </summary>
Expand All @@ -20,6 +24,7 @@ public sealed class GrainInterfaceProperties
/// </param>
public GrainInterfaceProperties(ImmutableDictionary<string, string> values)
{
ArgumentNullException.ThrowIfNull(values);
this.Properties = values;
}

Expand Down Expand Up @@ -54,6 +59,54 @@ public string ToDetailedString()

return result.ToString();
}

public override bool Equals(object? obj) => obj is GrainInterfaceProperties other && Equals(other);

public bool Equals(GrainInterfaceProperties? other)
{
if (ReferenceEquals(this, other)) return true;
if (other is null) return false;
return PropertiesEqual(Properties, other.Properties);
}

public override int GetHashCode() => _hashCode ??= ComputeHashCode(Properties);

private static bool PropertiesEqual(ImmutableDictionary<string, string> left, ImmutableDictionary<string, string> right)
{
if (ReferenceEquals(left, right))
{
return true;
}

if (left.Count != right.Count)
{
return false;
}

foreach (var entry in left)
{
if (!right.TryGetValue(entry.Key, out var value)
|| !string.Equals(entry.Value, value, StringComparison.Ordinal))
{
return false;
}
}

return true;
}

private static int ComputeHashCode(ImmutableDictionary<string, string> properties)
{
var hash = 0;
foreach (var entry in properties)
{
hash ^= HashCode.Combine(
StringComparer.Ordinal.GetHashCode(entry.Key),
entry.Value is null ? 0 : StringComparer.Ordinal.GetHashCode(entry.Value));
}

return hash;
}
}

/// <summary>
Expand Down
61 changes: 60 additions & 1 deletion src/Orleans.Core.Abstractions/Manifest/GrainManifest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using Orleans.Runtime;

Expand All @@ -8,8 +9,11 @@ namespace Orleans.Metadata
/// Information about available grains.
/// </summary>
[Serializable, GenerateSerializer, Immutable]
public sealed class GrainManifest
public sealed class GrainManifest : IEquatable<GrainManifest>
{
[NonSerialized]
private int? _hashCode;

/// <summary>
/// Initializes a new instance of the <see cref="GrainManifest"/> class.
/// </summary>
Expand All @@ -23,6 +27,8 @@ public GrainManifest(
ImmutableDictionary<GrainType, GrainProperties> grains,
ImmutableDictionary<GrainInterfaceType, GrainInterfaceProperties> interfaces)
{
ArgumentNullException.ThrowIfNull(grains);
ArgumentNullException.ThrowIfNull(interfaces);
this.Interfaces = interfaces;
this.Grains = grains;
}
Expand All @@ -38,5 +44,58 @@ public GrainManifest(
/// </summary>
[Id(1)]
public ImmutableDictionary<GrainType, GrainProperties> Grains { get; }

public override int GetHashCode() => _hashCode ??= HashCode.Combine(
ComputeHashCode(Interfaces),
ComputeHashCode(Grains));

public override bool Equals(object? obj) => obj is GrainManifest other && Equals(other);

public bool Equals(GrainManifest? other)
{
if (ReferenceEquals(this, other)) return true;
if (other is null) return false;
return DictionariesEqual(Interfaces, other.Interfaces) && DictionariesEqual(Grains, other.Grains);
}

private static bool DictionariesEqual<TKey, TValue>(
ImmutableDictionary<TKey, TValue> left,
ImmutableDictionary<TKey, TValue> right)
where TKey : notnull
{
if (ReferenceEquals(left, right))
{
return true;
}

if (left.Count != right.Count)
{
return false;
}

var comparer = EqualityComparer<TValue>.Default;
foreach (var entry in left)
{
if (!right.TryGetValue(entry.Key, out var value)
|| !comparer.Equals(entry.Value, value))
{
return false;
}
}

return true;
}

private static int ComputeHashCode<TKey, TValue>(ImmutableDictionary<TKey, TValue> dictionary)
where TKey : notnull
{
var hash = 0;
foreach (var entry in dictionary)
{
hash ^= HashCode.Combine(entry.Key, entry.Value);
}

return hash;
}
}
}
59 changes: 56 additions & 3 deletions src/Orleans.Core.Abstractions/Manifest/GrainProperties.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
Expand All @@ -11,8 +12,11 @@ namespace Orleans.Metadata
/// Information about a logical grain type <see cref="GrainType"/>.
/// </summary>
[Serializable, GenerateSerializer, Immutable]
public sealed class GrainProperties
public sealed class GrainProperties : IEquatable<GrainProperties>
{
[NonSerialized]
private int? _hashCode;

/// <summary>
/// Initializes a new instance of the <see cref="GrainProperties"/> class.
/// </summary>
Expand All @@ -21,6 +25,7 @@ public sealed class GrainProperties
/// </param>
public GrainProperties(ImmutableDictionary<string, string> values)
{
ArgumentNullException.ThrowIfNull(values);
this.Properties = values;
}

Expand All @@ -38,10 +43,10 @@ public GrainProperties(ImmutableDictionary<string, string> values)
/// </returns>
public string ToDetailedString()
{
if (this.Properties is null) return string.Empty;
if (Properties is null) return string.Empty;
var result = new StringBuilder("[");
bool first = true;
foreach (var entry in this.Properties)
foreach (var entry in Properties)
{
if (!first)
{
Expand All @@ -55,6 +60,54 @@ public string ToDetailedString()

return result.ToString();
}

public override bool Equals(object? obj) => obj is GrainProperties other && Equals(other);

public bool Equals(GrainProperties? other)
{
if (ReferenceEquals(this, other)) return true;
if (other is null) return false;
return PropertiesEqual(Properties, other.Properties);
}

public override int GetHashCode() => _hashCode ??= ComputeHashCode(Properties);

private static bool PropertiesEqual(ImmutableDictionary<string, string> left, ImmutableDictionary<string, string> right)
{
if (ReferenceEquals(left, right))
{
return true;
}

if (left.Count != right.Count)
{
return false;
}

foreach (var entry in left)
{
if (!right.TryGetValue(entry.Key, out var value)
|| !string.Equals(entry.Value, value, StringComparison.Ordinal))
{
return false;
}
}

return true;
}

private static int ComputeHashCode(ImmutableDictionary<string, string> properties)
{
var hash = 0;
foreach (var entry in properties)
{
hash ^= HashCode.Combine(
StringComparer.Ordinal.GetHashCode(entry.Key),
entry.Value is null ? 0 : StringComparer.Ordinal.GetHashCode(entry.Value));
}

return hash;
}
}

/// <summary>
Expand Down
Loading
Loading