Skip to content

Commit

Permalink
Double wildcard implementation for ActorSelection (#4375)
Browse files Browse the repository at this point in the history
* Double wildcard actor selector implementation

* Add spec

* Remove recursion in ActorRef

* Simplify ActorSelection, remove wrong copy-paste code

* Akka.API.Tests approved API update

Co-authored-by: Aaron Stannard <aaron@petabridge.com>
  • Loading branch information
Arkatufus and Aaronontheweb authored Oct 30, 2020
1 parent 3705c8c commit 59e149d
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 11 deletions.
7 changes: 7 additions & 0 deletions src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1591,6 +1591,13 @@ namespace Akka.Actor
public override int GetHashCode() { }
public override string ToString() { }
}
public class SelectChildRecursive : Akka.Actor.SelectionPathElement
{
public SelectChildRecursive() { }
public override bool Equals(object obj) { }
public override int GetHashCode() { }
public override string ToString() { }
}
public class SelectParent : Akka.Actor.SelectionPathElement
{
public SelectParent() { }
Expand Down
35 changes: 35 additions & 0 deletions src/core/Akka.Tests/Actor/ActorSelectionSpec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,41 @@ public void An_ActorSelection_must_identify_actors_with_wildcard_selection_corre
probe.ExpectNoMsg(TimeSpan.FromMilliseconds(500));
}

[Fact]
public void An_ActorSelection_must_identify_actors_with_double_wildcard_selection_correctly()
{
var creator = CreateTestProbe();
var top = Sys.ActorOf(Props, "a");
var b1 = top.Ask<IActorRef>(new Create("b1"), TimeSpan.FromSeconds(3)).Result;
var b2 = top.Ask<IActorRef>(new Create("b2"), TimeSpan.FromSeconds(3)).Result;
var b3 = top.Ask<IActorRef>(new Create("b3"), TimeSpan.FromSeconds(3)).Result;
var c1 = b2.Ask<IActorRef>(new Create("c1"), TimeSpan.FromSeconds(3)).Result;
var c2 = b2.Ask<IActorRef>(new Create("c2"), TimeSpan.FromSeconds(3)).Result;
var d = c1.Ask<IActorRef>(new Create("d"), TimeSpan.FromSeconds(3)).Result;

var probe = CreateTestProbe();

// grab everything below /user/a
Sys.ActorSelection("/user/a/**").Tell(new Identify(1), probe.Ref);
probe.ReceiveN(6)
.Cast<ActorIdentity>()
.Select(i => i.Subject)
.ShouldAllBeEquivalentTo(new[] { b1, b2, b3, c1, c2, d });
probe.ExpectNoMsg(TimeSpan.FromMilliseconds(500));

// grab everything below /user/a/b2
Sys.ActorSelection("/user/a/b2/**").Tell(new Identify(2), probe.Ref);
probe.ReceiveN(3)
.Cast<ActorIdentity>()
.Select(i => i.Subject)
.ShouldAllBeEquivalentTo(new[] { c1, c2, d });
probe.ExpectNoMsg(TimeSpan.FromMilliseconds(500));

// nothing under /user/a/b2/c1/d
Sys.ActorSelection("/user/a/b2/c1/d/**").Tell(new Identify(3), probe.Ref);
probe.ExpectNoMsg(TimeSpan.FromMilliseconds(500));
}

[Fact]
public void An_ActorSelection_must_forward_to_selection()
{
Expand Down
17 changes: 17 additions & 0 deletions src/core/Akka/Actor/ActorRef.cs
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,23 @@ public abstract class ActorRefWithCell : InternalActorRefBase
/// <returns>If the child exists, it returns the child actor. Otherwise, we return <see cref="ActorRefs.Nobody"/>.</returns>
public abstract IInternalActorRef GetSingleChild(string name);

private IEnumerable<IActorRef> SelfAndChildren()
{
yield return this;
foreach(var child in Children.SelectMany(x =>
{
switch(x)
{
case ActorRefWithCell cell:
return cell.SelfAndChildren();
default:
return new[] { x };
}
}))
{
yield return child;
}
}
}

/// <summary>
Expand Down
68 changes: 57 additions & 11 deletions src/core/Akka/Actor/ActorSelection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,18 +75,34 @@ public ActorSelection(IActorRef anchor, string path)
public ActorSelection(IActorRef anchor, IEnumerable<string> elements)
{
Anchor = anchor;

Path = elements
.Where(s => !string.IsNullOrWhiteSpace(s))
.Select<string, SelectionPathElement>(e =>

var list = new List<SelectionPathElement>();
var iter = elements.Iterator();
while(!iter.IsEmpty())
{
var s = iter.Next();
switch(s)
{
if (e.Contains("?") || e.Contains("*"))
return new SelectChildPattern(e);
if (e == "..")
return new SelectParent();
return new SelectChildName(e);
})
.ToArray();
case null:
case "":
break;
case "**":
if(!iter.IsEmpty())
throw new IllegalActorNameException("Double wildcard can only appear at the last path entry");
list.Add(new SelectChildRecursive());
break;
case string e when e.Contains("?") || e.Contains("*"):
list.Add(new SelectChildPattern(e));
break;
case string e when e == "..":
list.Add(new SelectParent());
break;
default:
list.Add(new SelectChildName(s));
break;
}
}
Path = list.ToArray();
}

/// <summary>
Expand Down Expand Up @@ -212,6 +228,18 @@ void Rec(IInternalActorRef actorRef)
Rec(child);
}

break;
case SelectChildRecursive _:
var allChildren = refWithCell.Children.ToList();
if (allChildren.Count == 0)
return;

var msg = new ActorSelectionMessage(sel.Message, new[] { new SelectChildRecursive() }, true);
foreach (var c in allChildren)
{
c.Tell(sel.Message, sender);
DeliverSelection(c as IInternalActorRef, sender, msg);
}
break;
case SelectChildPattern pattern:
// fan-out when there is a wildcard
Expand Down Expand Up @@ -435,6 +463,24 @@ public override bool Equals(object obj)
public override string ToString() => PatternStr;
}

public class SelectChildRecursive : SelectionPathElement
{
/// <inheritdoc/>
public override bool Equals(object obj)
{
if (obj is null) return false;
if (ReferenceEquals(this, obj)) return true;
if(!(obj is SelectChildRecursive)) return false;
return true;
}

/// <inheritdoc/>
public override int GetHashCode() => "**".GetHashCode();

/// <inheritdoc/>
public override string ToString() => "**";

}

/// <summary>
/// Class SelectParent.
Expand Down

0 comments on commit 59e149d

Please sign in to comment.