Skip to content

Commit

Permalink
Add double wildcard support for deployment (#4372)
Browse files Browse the repository at this point in the history
* Add double wildcard support for deployment

* Akka.API.Tests approved API update

* WildcardIndex is WildcardTree replacement that supports double widlcard

* Use unchecked in hashcode calculation

* Turn WindcardIndex into an internal class
  • Loading branch information
Arkatufus committed Apr 9, 2020
1 parent 898a3fd commit 55512a9
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 60 deletions.
14 changes: 0 additions & 14 deletions src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt
Expand Up @@ -628,7 +628,6 @@ namespace Akka.Actor
public Deployer(Akka.Actor.Settings settings) { }
public Akka.Actor.Deploy Lookup(Akka.Actor.ActorPath path) { }
public Akka.Actor.Deploy Lookup(System.Collections.Generic.IEnumerable<string> path) { }
public Akka.Actor.Deploy Lookup(System.Collections.Generic.IEnumerator<string> path) { }
public virtual Akka.Actor.Deploy ParseConfig(string key, Akka.Configuration.Config config) { }
public void SetDeploy(Akka.Actor.Deploy deploy) { }
}
Expand Down Expand Up @@ -4898,19 +4897,6 @@ namespace Akka.Util
{
public static bool Like(this string text, string pattern, bool caseSensitive = False) { }
}
public sealed class WildcardTree<T>
where T : class
{
public static readonly Akka.Util.WildcardTree<T> Empty;
public WildcardTree() { }
public WildcardTree(T data, System.Collections.Generic.IDictionary<string, Akka.Util.WildcardTree<T>> children) { }
public System.Collections.Generic.IDictionary<string, Akka.Util.WildcardTree<T>> Children { get; }
public T Data { get; }
public override bool Equals(object obj) { }
public Akka.Util.WildcardTree<T> Find(System.Collections.Generic.IEnumerator<string> elements) { }
public override int GetHashCode() { }
public Akka.Util.WildcardTree<T> Insert(System.Collections.Generic.IEnumerator<string> elements, T data) { }
}
}
namespace Akka.Util.Extensions
{
Expand Down
42 changes: 41 additions & 1 deletion src/core/Akka.Tests/Actor/DeployerSpec.cs
Expand Up @@ -66,14 +66,20 @@ private static string GetConfig()
router = round-robin-pool
}
""/some/*"" {
router = round-robin-pool
router = random-pool
}
""/*/some"" {
router = round-robin-pool
}
""/*/so.me"" {
router = round-robin-pool
}
""/double/**"" {
router = random-pool
}
""/double/more/**"" {
router = round-robin-pool
}
}
";
}
Expand Down Expand Up @@ -166,7 +172,41 @@ public void ActorSystem_fallback_deployment_is_not_null_when_config_has_value()
Assert.NotEqual(worker1.Path, worker2.Path);
}

[Fact]
public void Deployer_should_be_able_to_use_wildcards()
{
AssertRouting("/some/wildcardmatch", new RandomPool(1), "/some/*");
AssertRouting("/somewildcardmatch/some", new RoundRobinPool(1), "/*/some");
}

[Fact]
public void Deployer_should_be_able_to_use_double_wildcards()
{
AssertRouting("/double/wildcardmatch", new RandomPool(1), "/double/**");
AssertRouting("/double/wildcardmatch/anothermatch", new RandomPool(1), "/double/**");
AssertRouting("/double/more/anothermatch", new RoundRobinPool(1), "/double/more/**");
AssertNoRouting("/double");
}

#endregion

private void AssertNoRouting(string service)
{
var deployment = ((ActorSystemImpl)Sys).Provider.Deployer.Lookup(service.Split('/').Drop(1));
Assert.Null(deployment);
}

private void AssertRouting(string service, RouterConfig expected, string expectPath)
{
var deployment = ((ActorSystemImpl)Sys).Provider.Deployer.Lookup(service.Split('/').Drop(1));
Assert.Equal(expectPath, deployment.Path);
Assert.Equal(expected.GetType(), deployment.RouterConfig.GetType());
Assert.Equal(Deploy.NoScopeGiven, deployment.Scope);
if(expected is Pool pool)
{
Assert.Equal(pool.Resizer, ((Pool)deployment.RouterConfig).Resizer);
}
}
}
}

49 changes: 15 additions & 34 deletions src/core/Akka/Actor/Deployer.cs
Expand Up @@ -10,7 +10,6 @@
using System.Diagnostics;
using System.Linq;
using Akka.Configuration;
using Akka.Configuration;
using Akka.Routing;
using Akka.Util;
using Akka.Util.Internal;
Expand All @@ -27,7 +26,8 @@ public class Deployer
/// </summary>
protected readonly Config Default;
private readonly Settings _settings;
private readonly AtomicReference<WildcardTree<Deploy>> _deployments = new AtomicReference<WildcardTree<Deploy>>(new WildcardTree<Deploy>());
private readonly AtomicReference<WildcardIndex<Deploy>> _deployments =
new AtomicReference<WildcardIndex<Deploy>>(new WildcardIndex<Deploy>());

/// <summary>
/// Initializes a new instance of the <see cref="Deployer"/> class.
Expand Down Expand Up @@ -75,17 +75,7 @@ public Deploy Lookup(ActorPath path)
/// <returns>TBD</returns>
public Deploy Lookup(IEnumerable<string> path)
{
return Lookup(path.GetEnumerator());
}

/// <summary>
/// TBD
/// </summary>
/// <param name="path">TBD</param>
/// <returns>TBD</returns>
public Deploy Lookup(IEnumerator<string> path)
{
return _deployments.Value.Find(path).Data;
return _deployments.Value.Find(path);
}

/// <summary>
Expand All @@ -99,26 +89,22 @@ public Deploy Lookup(IEnumerator<string> path)
/// </exception>
public void SetDeploy(Deploy deploy)
{
Action<IList<string>, Deploy> add = (path, d) =>
void add(IList<string> path, Deploy d)
{
bool set;
do
var w = _deployments.Value;
foreach (var t in path)
{
var w = _deployments.Value;
foreach (var t in path)
if (string.IsNullOrEmpty(t))
throw new IllegalActorNameException($"Actor name in deployment [{d.Path}] must not be empty");
if (!ActorPath.IsValidPathElement(t))
{
var curPath = t;
if (string.IsNullOrEmpty(curPath))
throw new IllegalActorNameException($"Actor name in deployment [{d.Path}] must not be empty");
if (!ActorPath.IsValidPathElement(t))
{
throw new IllegalActorNameException(
$"Illegal actor name [{t}] in deployment [${d.Path}]. Actor paths MUST: not start with `$`, include only ASCII letters and can only contain these special characters: ${new string(ActorPath.ValidSymbols)}.");
}
throw new IllegalActorNameException(
$"Illegal actor name [{t}] in deployment [${d.Path}]. Actor paths MUST: not start with `$`, include only ASCII letters and can only contain these special characters: ${new string(ActorPath.ValidSymbols)}.");
}
set = _deployments.CompareAndSet(w, w.Insert(path.GetEnumerator(), d));
} while (!set);
};
}
if (!_deployments.CompareAndSet(w, w.Insert(path, d))) add(path, d);
}

var elements = deploy.Path.Split('/').Drop(1).ToList();
add(elements, deploy);
}
Expand All @@ -131,11 +117,6 @@ public void SetDeploy(Deploy deploy)
/// <returns>A configured actor deployment to the given path.</returns>
public virtual Deploy ParseConfig(string key, Config config)
{
/*
if (config.IsNullOrEmpty())
throw ConfigurationException.NullOrEmptyConfig<Deploy>();
*/

var deployment = config.WithFallback(Default);
var routerType = deployment.GetString("router", null);
var router = CreateRouterConfig(routerType, deployment);
Expand Down
80 changes: 80 additions & 0 deletions src/core/Akka/Util/WildcardIndex.cs
@@ -0,0 +1,80 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Akka.Util
{
internal sealed class WildcardIndex<T> : IEquatable<WildcardIndex<T>> where T : class
{
private readonly WildcardTree<T> _wildcardTree;
private readonly WildcardTree<T> _doubleWildcardTree;

public bool IsEmpty => _wildcardTree.IsEmpty && _doubleWildcardTree.IsEmpty;

public WildcardIndex()
{
_wildcardTree = new WildcardTree<T>();
_doubleWildcardTree = new WildcardTree<T>();
}

private WildcardIndex(WildcardTree<T> singleWildcard, WildcardTree<T> doubleWildcard)
{
_wildcardTree = singleWildcard;
_doubleWildcardTree = doubleWildcard;
}

public WildcardIndex<T> Insert(IEnumerable<string> elems, T data)
{
if (elems == null) return this;

switch(elems.Last())
{
case "**":
return new WildcardIndex<T>(_wildcardTree, _doubleWildcardTree.Insert(elems.GetEnumerator(), data));
default:
return new WildcardIndex<T>(_wildcardTree.Insert(elems.GetEnumerator(), data), _doubleWildcardTree);
}
}

public T Find(IEnumerable<string> elems)
{
if(_wildcardTree.IsEmpty)
{
if (_doubleWildcardTree.IsEmpty)
return default;
return _doubleWildcardTree.FindWithTerminalDoubleWildcard(elems.GetEnumerator(), null).Data;
}

var withSingleWildcard = _wildcardTree.FindWithSingleWildcard(elems.GetEnumerator());
if (!withSingleWildcard.IsEmpty)
return withSingleWildcard.Data;
return _doubleWildcardTree.FindWithTerminalDoubleWildcard(elems.GetEnumerator(), null).Data;
}

public bool Equals(WildcardIndex<T> other)
{
return _wildcardTree.Equals(other._wildcardTree)
&& _doubleWildcardTree.Equals(other._doubleWildcardTree);
}

public override bool Equals(object obj)
{
if (obj is null) return false;
if (ReferenceEquals(obj, this)) return true;
if (!(obj is WildcardIndex<T> other)) return false;
return Equals(other);
}

public override int GetHashCode()
{
unchecked
{
var hashCode = -872755323;
hashCode = hashCode * -1521134295 + _wildcardTree.GetHashCode();
hashCode = hashCode * -1521134295 + _doubleWildcardTree.GetHashCode();
return hashCode;
}
}
}
}
38 changes: 27 additions & 11 deletions src/core/Akka/Util/WildcardTree.cs
Expand Up @@ -15,8 +15,10 @@ namespace Akka.Util
/// A searchable nested dictionary, represents a searchable tree structure underneath
/// </summary>
/// <typeparam name="T">TBD</typeparam>
public sealed class WildcardTree<T> where T:class
internal sealed class WildcardTree<T> where T:class
{
public bool IsEmpty => Data == null && Children.Count == 0;

/// <summary>
/// TBD
/// </summary>
Expand Down Expand Up @@ -65,19 +67,33 @@ public WildcardTree<T> Insert(IEnumerator<string> elements, T data)
}
}

/// <summary>
/// TBD
/// </summary>
/// <param name="elements">TBD</param>
/// <returns>TBD</returns>
public WildcardTree<T> Find(IEnumerator<string> elements)
public WildcardTree<T> FindWithSingleWildcard(IEnumerator<string> elements)
{
if (!elements.MoveNext()) return this;

if(Children.TryGetValue(elements.Current, out var next))
return next.FindWithSingleWildcard(elements);
else
{
var next = Children.GetOrElse(elements.Current, Children.GetOrElse("*", null));
return next == null ? Empty : next.Find(elements);
}
if (Children.TryGetValue("*", out next))
return next.FindWithSingleWildcard(elements);
else
return Empty;
}

public WildcardTree<T> FindWithTerminalDoubleWildcard(IEnumerator<string> elements, WildcardTree<T> alt)
{
if (!elements.MoveNext()) return this;
if (alt == null) alt = Empty;

var newAlt = Children.GetOrElse("**", alt);

if (Children.TryGetValue(elements.Current, out var next))
return next.FindWithTerminalDoubleWildcard(elements, newAlt);
else
if (Children.TryGetValue("*", out next))
return next.FindWithTerminalDoubleWildcard(elements, newAlt);
else
return newAlt;
}

/// <inheritdoc/>
Expand Down

0 comments on commit 55512a9

Please sign in to comment.