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

Heterogenous silos support #2443

Merged
merged 21 commits into from Dec 7, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
bbc118d
Add possibility to exclude some grain types in assembly loading
benjaminpetit Oct 27, 2016
f793f2f
Add test for excluded grains
benjaminpetit Sep 12, 2016
568cf00
Add method to merge two GrainInterfaceMap
benjaminpetit Oct 27, 2016
4bdc77a
GrainTypeManager store the cluster GrainInterfaceMap and is able to r…
benjaminpetit Oct 28, 2016
3aab754
Better implementation of GrainTypeManager.GetSupportedSilos
benjaminpetit Nov 4, 2016
7773313
Add/rename method to return the TypeCodeMap for the whole cluster and…
benjaminpetit Oct 27, 2016
a259b31
TypeManager refresh the cluster GrainInterfaceMapTimer every 500ms
benjaminpetit Oct 28, 2016
adf6ea1
TypeManager implements ISiloStatusListener and refresh cluster only if
benjaminpetit Oct 27, 2016
83057de
Add a method in IPlacementContext to return a list of compatible silo…
benjaminpetit Oct 28, 2016
77e80d8
Update PlacementDirectors to use only compatible silos
benjaminpetit Oct 28, 2016
6c5133f
Add test for heterogeneous silos
benjaminpetit Oct 28, 2016
7cca65d
Add config for TypeMapRefreshTimeout value
benjaminpetit Nov 17, 2016
bde9f2f
Reduce waiting time in MergeGrainResolverTests
benjaminpetit Nov 18, 2016
d619e3e
Fix ExceptionPropagationTests.ExceptionPropagationGrainToGrainMessage…
benjaminpetit Nov 23, 2016
8b054d9
Fix SilosStopTests
benjaminpetit Dec 1, 2016
25cd773
Fix ElasticPlacementTests
benjaminpetit Dec 1, 2016
fc940b6
Use IReadOnlyDictionary for GrainInterfaceMapsBySilo in GrainTypeMana…
benjaminpetit Dec 6, 2016
8dd7422
Rename TypeMapRefreshTimeout to TypeMapRefreshInterval for better cla…
benjaminpetit Dec 6, 2016
44e117b
Cleaning unused method in IPlacementContext
benjaminpetit Dec 6, 2016
2c5f893
Revert "Use IReadOnlyDictionary for GrainInterfaceMapsBySilo in Grain…
benjaminpetit Dec 6, 2016
5da8979
Use IReadOnlyDictionary for GrainInterfaceMapsBySilo in GrainTypeMana…
benjaminpetit Dec 7, 2016
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/Orleans/Configuration/GlobalConfiguration.cs
Expand Up @@ -170,6 +170,10 @@ public bool PrimaryNodeIsRequired
/// The number of seconds to attempt to join a cluster of silos before giving up.
/// </summary>
public TimeSpan MaxJoinAttemptTime { get; set; }
/// <summary>
/// The number of seconds to refresh the cluster grain interface map
/// </summary>
public TimeSpan TypeMapRefreshInterval { get; set; }
internal ConfigValue<int> ExpectedClusterSizeConfigValue { get; set; }
/// <summary>
/// The expected size of a cluster. Need not be very accurate, can be an overestimate.
Expand Down Expand Up @@ -470,6 +474,7 @@ internal bool UseAzureSystemStore
private static readonly TimeSpan DEFAULT_LIVENESS_DEATH_VOTE_EXPIRATION_TIMEOUT = TimeSpan.FromSeconds(120);
private static readonly TimeSpan DEFAULT_LIVENESS_I_AM_ALIVE_TABLE_PUBLISH_TIMEOUT = TimeSpan.FromMinutes(5);
private static readonly TimeSpan DEFAULT_LIVENESS_MAX_JOIN_ATTEMPT_TIME = TimeSpan.FromMinutes(5); // 5 min
private static readonly TimeSpan DEFAULT_REFRESH_CLUSTER_INTERFACEMAP_TIME = TimeSpan.FromMinutes(1);
private const int DEFAULT_LIVENESS_NUM_MISSED_PROBES_LIMIT = 3;
private const int DEFAULT_LIVENESS_NUM_PROBED_SILOS = 3;
private const int DEFAULT_LIVENESS_NUM_VOTES_FOR_DEATH_DECLARATION = 2;
Expand Down Expand Up @@ -522,6 +527,7 @@ internal GlobalConfiguration()
UseLivenessGossip = DEFAULT_LIVENESS_USE_LIVENESS_GOSSIP;
ValidateInitialConnectivity = DEFAULT_VALIDATE_INITIAL_CONNECTIVITY;
MaxJoinAttemptTime = DEFAULT_LIVENESS_MAX_JOIN_ATTEMPT_TIME;
TypeMapRefreshInterval = DEFAULT_REFRESH_CLUSTER_INTERFACEMAP_TIME;
MaxMultiClusterGateways = DEFAULT_MAX_MULTICLUSTER_GATEWAYS;
BackgroundGossipInterval = DEFAULT_BACKGROUND_GOSSIP_INTERVAL;
UseGlobalSingleInstanceByDefault = DEFAULT_USE_GLOBAL_SINGLE_INSTANCE;
Expand Down
6 changes: 5 additions & 1 deletion src/Orleans/Configuration/NodeConfiguration.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
Expand Down Expand Up @@ -212,6 +213,8 @@ public string TraceFilePattern

public Dictionary<string, SearchOption> AdditionalAssemblyDirectories { get; set; }

public List<string> ExcludedGrainTypes { get; set; }

public string SiloShutdownEventName { get; set; }

internal const string DEFAULT_NODE_NAME = "default";
Expand Down Expand Up @@ -270,6 +273,7 @@ public NodeConfiguration()
UseNagleAlgorithm = false;

AdditionalAssemblyDirectories = new Dictionary<string, SearchOption>();
ExcludedGrainTypes = new List<string>();
}

public NodeConfiguration(NodeConfiguration other)
Expand Down Expand Up @@ -320,6 +324,7 @@ public NodeConfiguration(NodeConfiguration other)

StartupTypeName = other.StartupTypeName;
AdditionalAssemblyDirectories = other.AdditionalAssemblyDirectories;
ExcludedGrainTypes = other.ExcludedGrainTypes.ToList();
}

public override string ToString()
Expand Down Expand Up @@ -487,7 +492,6 @@ internal void Load(XmlElement root)
case "AdditionalAssemblyDirectories":
ConfigUtilities.ParseAdditionalAssemblyDirectories(AdditionalAssemblyDirectories, child);
break;

}
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/Orleans/Logging/ErrorCodes.cs
Expand Up @@ -1093,6 +1093,8 @@ internal enum ErrorCode
GlobalSingleInstance_MaintainerException = GlobalSingleInstanceBase + 3,
GlobalSingleInstance_MultipleOwners = GlobalSingleInstanceBase + 4,

TypeManagerBase = Runtime + 4200,
TypeManager_GetSiloGrainInterfaceMapError = TypeManagerBase + 1,
}
}
// ReSharper restore InconsistentNaming
6 changes: 3 additions & 3 deletions src/Orleans/Messaging/ProxiedMessageCenter.cs
Expand Up @@ -278,7 +278,7 @@ private void RejectOrResend(Message msg)
public Task<IGrainTypeResolver> GetTypeCodeMap(GrainFactory grainFactory)
{
var silo = GetLiveGatewaySiloAddress();
return GetTypeManager(silo, grainFactory).GetTypeCodeMap(silo);
return GetTypeManager(silo, grainFactory).GetClusterTypeCodeMap();
}

public Task<Streams.ImplicitStreamSubscriberTable> GetImplicitStreamSubscriberTable(GrainFactory grainFactory)
Expand Down Expand Up @@ -397,9 +397,9 @@ public int ReceiveQueueLength

#endregion

private ITypeManager GetTypeManager(SiloAddress destination, GrainFactory grainFactory)
private IClusterTypeManager GetTypeManager(SiloAddress destination, GrainFactory grainFactory)
{
return grainFactory.GetSystemTarget<ITypeManager>(Constants.TypeManagerId, destination);
return grainFactory.GetSystemTarget<IClusterTypeManager>(Constants.TypeManagerId, destination);
}

private SiloAddress GetLiveGatewaySiloAddress()
Expand Down
50 changes: 47 additions & 3 deletions src/Orleans/Runtime/GrainInterfaceMap.cs
Expand Up @@ -67,30 +67,66 @@ public override string ToString()
private readonly Dictionary<int, GrainInterfaceData> table;
private readonly HashSet<int> unordered;

[NonSerialized]
private readonly Dictionary<int, GrainClassData> implementationIndex;

[NonSerialized] // Client shouldn't need this
private readonly Dictionary<string, string> primaryImplementations;

private readonly bool localTestMode;
private readonly HashSet<string> loadedGrainAsemblies;

private readonly PlacementStrategy defaultPlacementStrategy;

private readonly PlacementStrategy defaultPlacementStrategy;
internal IList<int> SupportedGrainTypes
{
get { return implementationIndex.Keys.ToList(); }
}

public GrainInterfaceMap(bool localTestMode, PlacementStrategy defaultPlacementStrategy)
{
this.defaultPlacementStrategy = defaultPlacementStrategy;
table = new Dictionary<int, GrainInterfaceData>();
typeToInterfaceData = new Dictionary<string, GrainInterfaceData>();
primaryImplementations = new Dictionary<string, string>();
implementationIndex = new Dictionary<int, GrainClassData>();
unordered = new HashSet<int>();
this.localTestMode = localTestMode;
this.defaultPlacementStrategy = defaultPlacementStrategy;
if(localTestMode) // if we are running in test mode, we'll build a list of loaded grain assemblies to help with troubleshooting deployment issue
loadedGrainAsemblies = new HashSet<string>();
}

internal void AddMap(GrainInterfaceMap map)
{
foreach (var kvp in map.typeToInterfaceData)
{
if (!typeToInterfaceData.ContainsKey(kvp.Key))
{
typeToInterfaceData.Add(kvp.Key, kvp.Value);
}
}

foreach (var kvp in map.table)
{
if (!table.ContainsKey(kvp.Key))
{
table.Add(kvp.Key, kvp.Value);
}
}

foreach (var grainClassTypeCode in map.unordered)
{
unordered.Add(grainClassTypeCode);
}

foreach (var kvp in map.implementationIndex)
{
if (!implementationIndex.ContainsKey(kvp.Key))
{
implementationIndex.Add(kvp.Key, kvp.Value);
}
}
}

internal void AddEntry(int interfaceId, Type iface, int grainTypeCode, string grainInterface, string grainClass, string assembly,
bool isGenericGrainClass, PlacementStrategy placement, MultiClusterRegistrationStrategy registrationStrategy, bool primaryImplementation = false)
{
Expand Down Expand Up @@ -173,6 +209,14 @@ internal bool ContainsGrainInterface(int interfaceId)
}
}

internal bool ContainsGrainImplementation(int typeCode)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No usages of this?

{
lock (this)
{
return implementationIndex.ContainsKey(typeCode);
}
}

internal bool TryGetTypeInfo(int typeCode, out string grainClass, out PlacementStrategy placement, out MultiClusterRegistrationStrategy registrationStrategy, string genericArguments = null)
{
lock (this)
Expand Down
17 changes: 15 additions & 2 deletions src/Orleans/Runtime/ITypeManager.cs
Expand Up @@ -6,10 +6,23 @@ namespace Orleans.Runtime
/// <summary>
/// Client gateway interface for obtaining the grain interface/type map.
/// </summary>
internal interface ITypeManager : ISystemTarget
internal interface IClusterTypeManager : ISystemTarget
{
Task<IGrainTypeResolver> GetTypeCodeMap(SiloAddress silo);
/// <summary>
/// Acquires grain interface map for all grain types supported across the entire cluster
/// </summary>
/// <returns></returns>
Task<IGrainTypeResolver> GetClusterTypeCodeMap();

Task<Streams.ImplicitStreamSubscriberTable> GetImplicitStreamSubscriberTable(SiloAddress silo);
}

internal interface ISiloTypeManager : ISystemTarget
{
/// <summary>
/// Acquires grain interface map for all grain types supported by hosted silo.
/// </summary>
/// <returns></returns>
Task<GrainInterfaceMap> GetSiloTypeCodeMap();
}
}
10 changes: 10 additions & 0 deletions src/OrleansRuntime/Catalog/Catalog.cs
Expand Up @@ -119,6 +119,7 @@ public override void GetObjectData(SerializationInfo info, StreamingContext cont


public GrainTypeManager GrainTypeManager { get; private set; }

public SiloAddress LocalSilo { get; private set; }
internal ISiloStatusOracle SiloStatusOracle { get; set; }
internal readonly ActivationCollector ActivationCollector;
Expand Down Expand Up @@ -200,6 +201,15 @@ public override void GetObjectData(SerializationInfo info, StreamingContext cont
/// </summary>
public Dispatcher Dispatcher { get; }

public IList<SiloAddress> GetCompatibleSiloList(GrainId grain)
{
var typeCode = grain.GetTypeCode();
var compatibleSilos = GrainTypeManager.GetSupportedSilos(typeCode).Intersect(AllActiveSilos).ToList();
Copy link
Member

@ReubenBond ReubenBond Dec 6, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method has to be cheap, since it's on the warm/hot path. Currently, it is too allocation-happy for comfort.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is really more a warm path than a hot path (this code is executed for new placements only). I think that yes, we could optimize this method, with some caching mechanism. But it is not that trivial to implement (a bug here and we could miss forever a new silos that joined, or try to place activations on a silos dead for a long time, ect.).

So yes, it can be optimize, but it will add complexity. I am not sure it is worth doing it, especially in this PR.

I can add a comment in this method and open an issue to track this if you want?

Copy link
Member

@ReubenBond ReubenBond Dec 6, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, that makes sense. We can consider looking into it afterwards, then

if (compatibleSilos.Count == 0)
throw new OrleansException($"TypeCode ${typeCode} not supported in the cluster");
return compatibleSilos;
}

internal void SetStorageManager(IStorageProviderManager storageManager)
{
storageProviderManager = storageManager;
Expand Down
51 changes: 48 additions & 3 deletions src/OrleansRuntime/GrainTypeManager/GrainTypeManager.cs
@@ -1,5 +1,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Net;
using System.Reflection;
Expand All @@ -13,18 +15,26 @@ namespace Orleans.Runtime
internal class GrainTypeManager
{
private IDictionary<string, GrainTypeData> grainTypes;
private Dictionary<SiloAddress, GrainInterfaceMap> grainInterfaceMapsBySilo;
private Dictionary<int, IList<SiloAddress>> supportedSilosByTypeCode;
private readonly Logger logger = LogManager.GetLogger("GrainTypeManager");
private readonly GrainInterfaceMap grainInterfaceMap;
private readonly Dictionary<int, InvokerData> invokers = new Dictionary<int, InvokerData>();
private readonly SiloAssemblyLoader loader;
private static readonly object lockable = new object();
private readonly PlacementStrategy defaultPlacementStrategy;

private readonly PlacementStrategy defaultPlacementStrategy;
internal IReadOnlyDictionary<SiloAddress, GrainInterfaceMap> GrainInterfaceMapsBySilo
{
get { return grainInterfaceMapsBySilo; }
}

public static GrainTypeManager Instance { get; private set; }

public IEnumerable<KeyValuePair<string, GrainTypeData>> GrainClassTypeData { get { return grainTypes; } }

public GrainInterfaceMap ClusterGrainInterfaceMap { get; private set; }

public static void Stop()
{
Instance = null;
Expand All @@ -40,6 +50,8 @@ public GrainTypeManager(bool localTestMode, SiloAssemblyLoader loader, DefaultPl
this.defaultPlacementStrategy = defaultPlacementStrategy.PlacementStrategy;
this.loader = loader;
grainInterfaceMap = new GrainInterfaceMap(localTestMode, this.defaultPlacementStrategy);
ClusterGrainInterfaceMap = grainInterfaceMap;
grainInterfaceMapsBySilo = new Dictionary<SiloAddress, GrainInterfaceMap>();
lock (lockable)
{
if (Instance != null)
Expand Down Expand Up @@ -134,10 +146,21 @@ internal bool TryGetPrimaryImplementation(string grainInterface, out string grai

internal void GetTypeInfo(int typeCode, out string grainClass, out PlacementStrategy placement, out MultiClusterRegistrationStrategy activationStrategy, string genericArguments = null)
{
if (!grainInterfaceMap.TryGetTypeInfo(typeCode, out grainClass, out placement, out activationStrategy, genericArguments))
if (!ClusterGrainInterfaceMap.TryGetTypeInfo(typeCode, out grainClass, out placement, out activationStrategy, genericArguments))
throw new OrleansException(String.Format("Unexpected: Cannot find an implementation class for grain interface {0}", typeCode));
}

internal void SetInterfaceMapsBySilo(Dictionary<SiloAddress, GrainInterfaceMap> value)
{
grainInterfaceMapsBySilo = value;
RebuildFullGrainInterfaceMap();
}

internal IList<SiloAddress> GetSupportedSilos(int typeCode)
{
return supportedSilosByTypeCode[typeCode];
}

private void InitializeGrainClassData(SiloAssemblyLoader loader, bool strict)
{
grainTypes = loader.GetGrainClassTypes(strict);
Expand Down Expand Up @@ -196,7 +219,7 @@ public bool TryGetData(string name, out GrainTypeData result)
return grainTypes.TryGetValue(name, out result);
}

internal IGrainTypeResolver GetTypeCodeMap()
internal GrainInterfaceMap GetTypeCodeMap()
{
// the map is immutable at this point
return grainInterfaceMap;
Expand Down Expand Up @@ -241,6 +264,28 @@ internal IGrainMethodInvoker GetInvoker(int interfaceId, string genericGrainType
interfaceName, interfaceId));
}

private void RebuildFullGrainInterfaceMap()
{
var newClusterGrainInterfaceMap = new GrainInterfaceMap(false, defaultPlacementStrategy);
var newSupportedSilosByTypeCode = new Dictionary<int, IList<SiloAddress>>();
newClusterGrainInterfaceMap.AddMap(grainInterfaceMap);
foreach (var kvp in grainInterfaceMapsBySilo)
{
newClusterGrainInterfaceMap.AddMap(kvp.Value);
foreach (var grainType in kvp.Value.SupportedGrainTypes)
{
IList<SiloAddress> supportedSilos;
if (!newSupportedSilosByTypeCode.TryGetValue(grainType, out supportedSilos))
{
newSupportedSilosByTypeCode[grainType] = supportedSilos = new List<SiloAddress>();
}
supportedSilos.Add(kvp.Key);
}
}
ClusterGrainInterfaceMap = newClusterGrainInterfaceMap;
supportedSilosByTypeCode = newSupportedSilosByTypeCode;
}

private class InvokerData
{
private readonly Type baseInvokerType;
Expand Down
11 changes: 9 additions & 2 deletions src/OrleansRuntime/GrainTypeManager/SiloAssemblyLoader.cs
Expand Up @@ -13,17 +13,21 @@ namespace Orleans.Runtime
{
internal class SiloAssemblyLoader
{
private readonly List<string> excludedGrains;
private readonly LoggerImpl logger = LogManager.GetLogger("AssemblyLoader.Silo");
private List<string> discoveredAssemblyLocations;
private Dictionary<string, SearchOption> directories;

public SiloAssemblyLoader(NodeConfiguration nodeConfig)
: this(nodeConfig.AdditionalAssemblyDirectories)
: this(nodeConfig.AdditionalAssemblyDirectories, nodeConfig.ExcludedGrainTypes)
{
}

public SiloAssemblyLoader(IDictionary<string, SearchOption> additionalDirectories)
public SiloAssemblyLoader(IDictionary<string, SearchOption> additionalDirectories, IEnumerable<string> excludedGrains = null)
{
this.excludedGrains = excludedGrains != null
? new List<string>(excludedGrains)
: new List<string>();
var exeRoot = Path.GetDirectoryName(typeof(SiloAssemblyLoader).GetTypeInfo().Assembly.Location);
var appRoot = Path.Combine(exeRoot, "Applications");
var cwd = Directory.GetCurrentDirectory();
Expand Down Expand Up @@ -80,6 +84,9 @@ private void LoadApplicationAssemblies()
foreach (var grainType in grainTypes)
{
var className = TypeUtils.GetFullName(grainType);
if (excludedGrains.Contains(className))
continue;

if (result.ContainsKey(className))
throw new InvalidOperationException(
string.Format("Precondition violated: GetLoadedGrainTypes should not return a duplicate type ({0})", className));
Expand Down