Skip to content

Commit 59e149d

Browse files
Double wildcard implementation for ActorSelection (#4375)
* 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>
1 parent 3705c8c commit 59e149d

File tree

4 files changed

+116
-11
lines changed

4 files changed

+116
-11
lines changed

src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt

+7
Original file line numberDiff line numberDiff line change
@@ -1591,6 +1591,13 @@ namespace Akka.Actor
15911591
public override int GetHashCode() { }
15921592
public override string ToString() { }
15931593
}
1594+
public class SelectChildRecursive : Akka.Actor.SelectionPathElement
1595+
{
1596+
public SelectChildRecursive() { }
1597+
public override bool Equals(object obj) { }
1598+
public override int GetHashCode() { }
1599+
public override string ToString() { }
1600+
}
15941601
public class SelectParent : Akka.Actor.SelectionPathElement
15951602
{
15961603
public SelectParent() { }

src/core/Akka.Tests/Actor/ActorSelectionSpec.cs

+35
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,41 @@ public void An_ActorSelection_must_identify_actors_with_wildcard_selection_corre
453453
probe.ExpectNoMsg(TimeSpan.FromMilliseconds(500));
454454
}
455455

456+
[Fact]
457+
public void An_ActorSelection_must_identify_actors_with_double_wildcard_selection_correctly()
458+
{
459+
var creator = CreateTestProbe();
460+
var top = Sys.ActorOf(Props, "a");
461+
var b1 = top.Ask<IActorRef>(new Create("b1"), TimeSpan.FromSeconds(3)).Result;
462+
var b2 = top.Ask<IActorRef>(new Create("b2"), TimeSpan.FromSeconds(3)).Result;
463+
var b3 = top.Ask<IActorRef>(new Create("b3"), TimeSpan.FromSeconds(3)).Result;
464+
var c1 = b2.Ask<IActorRef>(new Create("c1"), TimeSpan.FromSeconds(3)).Result;
465+
var c2 = b2.Ask<IActorRef>(new Create("c2"), TimeSpan.FromSeconds(3)).Result;
466+
var d = c1.Ask<IActorRef>(new Create("d"), TimeSpan.FromSeconds(3)).Result;
467+
468+
var probe = CreateTestProbe();
469+
470+
// grab everything below /user/a
471+
Sys.ActorSelection("/user/a/**").Tell(new Identify(1), probe.Ref);
472+
probe.ReceiveN(6)
473+
.Cast<ActorIdentity>()
474+
.Select(i => i.Subject)
475+
.ShouldAllBeEquivalentTo(new[] { b1, b2, b3, c1, c2, d });
476+
probe.ExpectNoMsg(TimeSpan.FromMilliseconds(500));
477+
478+
// grab everything below /user/a/b2
479+
Sys.ActorSelection("/user/a/b2/**").Tell(new Identify(2), probe.Ref);
480+
probe.ReceiveN(3)
481+
.Cast<ActorIdentity>()
482+
.Select(i => i.Subject)
483+
.ShouldAllBeEquivalentTo(new[] { c1, c2, d });
484+
probe.ExpectNoMsg(TimeSpan.FromMilliseconds(500));
485+
486+
// nothing under /user/a/b2/c1/d
487+
Sys.ActorSelection("/user/a/b2/c1/d/**").Tell(new Identify(3), probe.Ref);
488+
probe.ExpectNoMsg(TimeSpan.FromMilliseconds(500));
489+
}
490+
456491
[Fact]
457492
public void An_ActorSelection_must_forward_to_selection()
458493
{

src/core/Akka/Actor/ActorRef.cs

+17
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,23 @@ public abstract class ActorRefWithCell : InternalActorRefBase
646646
/// <returns>If the child exists, it returns the child actor. Otherwise, we return <see cref="ActorRefs.Nobody"/>.</returns>
647647
public abstract IInternalActorRef GetSingleChild(string name);
648648

649+
private IEnumerable<IActorRef> SelfAndChildren()
650+
{
651+
yield return this;
652+
foreach(var child in Children.SelectMany(x =>
653+
{
654+
switch(x)
655+
{
656+
case ActorRefWithCell cell:
657+
return cell.SelfAndChildren();
658+
default:
659+
return new[] { x };
660+
}
661+
}))
662+
{
663+
yield return child;
664+
}
665+
}
649666
}
650667

651668
/// <summary>

src/core/Akka/Actor/ActorSelection.cs

+57-11
Original file line numberDiff line numberDiff line change
@@ -75,18 +75,34 @@ public ActorSelection(IActorRef anchor, string path)
7575
public ActorSelection(IActorRef anchor, IEnumerable<string> elements)
7676
{
7777
Anchor = anchor;
78-
79-
Path = elements
80-
.Where(s => !string.IsNullOrWhiteSpace(s))
81-
.Select<string, SelectionPathElement>(e =>
78+
79+
var list = new List<SelectionPathElement>();
80+
var iter = elements.Iterator();
81+
while(!iter.IsEmpty())
82+
{
83+
var s = iter.Next();
84+
switch(s)
8285
{
83-
if (e.Contains("?") || e.Contains("*"))
84-
return new SelectChildPattern(e);
85-
if (e == "..")
86-
return new SelectParent();
87-
return new SelectChildName(e);
88-
})
89-
.ToArray();
86+
case null:
87+
case "":
88+
break;
89+
case "**":
90+
if(!iter.IsEmpty())
91+
throw new IllegalActorNameException("Double wildcard can only appear at the last path entry");
92+
list.Add(new SelectChildRecursive());
93+
break;
94+
case string e when e.Contains("?") || e.Contains("*"):
95+
list.Add(new SelectChildPattern(e));
96+
break;
97+
case string e when e == "..":
98+
list.Add(new SelectParent());
99+
break;
100+
default:
101+
list.Add(new SelectChildName(s));
102+
break;
103+
}
104+
}
105+
Path = list.ToArray();
90106
}
91107

92108
/// <summary>
@@ -212,6 +228,18 @@ void Rec(IInternalActorRef actorRef)
212228
Rec(child);
213229
}
214230

231+
break;
232+
case SelectChildRecursive _:
233+
var allChildren = refWithCell.Children.ToList();
234+
if (allChildren.Count == 0)
235+
return;
236+
237+
var msg = new ActorSelectionMessage(sel.Message, new[] { new SelectChildRecursive() }, true);
238+
foreach (var c in allChildren)
239+
{
240+
c.Tell(sel.Message, sender);
241+
DeliverSelection(c as IInternalActorRef, sender, msg);
242+
}
215243
break;
216244
case SelectChildPattern pattern:
217245
// fan-out when there is a wildcard
@@ -435,6 +463,24 @@ public override bool Equals(object obj)
435463
public override string ToString() => PatternStr;
436464
}
437465

466+
public class SelectChildRecursive : SelectionPathElement
467+
{
468+
/// <inheritdoc/>
469+
public override bool Equals(object obj)
470+
{
471+
if (obj is null) return false;
472+
if (ReferenceEquals(this, obj)) return true;
473+
if(!(obj is SelectChildRecursive)) return false;
474+
return true;
475+
}
476+
477+
/// <inheritdoc/>
478+
public override int GetHashCode() => "**".GetHashCode();
479+
480+
/// <inheritdoc/>
481+
public override string ToString() => "**";
482+
483+
}
438484

439485
/// <summary>
440486
/// Class SelectParent.

0 commit comments

Comments
 (0)