From f15dd32cd6b06e4b5e72655ae4af841426bed6f3 Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Fri, 21 May 2021 16:17:41 -0500 Subject: [PATCH 01/13] `Span`-ifying ActorPath and Address parsing --- src/core/Akka/Actor/ActorPath.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/core/Akka/Actor/ActorPath.cs b/src/core/Akka/Actor/ActorPath.cs index db2a0488988..033637ef6ae 100644 --- a/src/core/Akka/Actor/ActorPath.cs +++ b/src/core/Akka/Actor/ActorPath.cs @@ -327,11 +327,19 @@ public static bool TryParse(string path, out ActorPath actorPath) { actorPath = null; - Address address; Uri uri; if (!TryParseAddress(path, out address, out uri)) return false; - var pathElements = uri.AbsolutePath.Split('/'); + var spanified = uri.AbsolutePath.AsSpan(); + var pathElements = new List(); + var curLen = 0; + var nextSlash = 0; + while ((nextSlash = spanified.IndexOf('/')) > 0) + { + pathElements.Add(spanified.Slice(curLen, nextSlash).ToString()); + curLen = nextSlash + 1; + } + actorPath = new RootActorPath(address) / pathElements.Skip(1); if (uri.Fragment.StartsWith("#")) { @@ -378,13 +386,14 @@ private static bool TryParseAddress(string path, out Address address, out Uri ur //port may not be specified for these types of paths return false; } + //System name is in the "host" position. According to rfc3986 host is case //insensitive, but should be produced as lowercase, so if we use uri.Host //we'll get it in lower case. //So we'll extract it ourselves using the original path. //We skip the protocol and "://" var systemNameLength = uri.Host.Length; - systemName = path.Substring(protocol.Length + 3, systemNameLength); + systemName = path.AsSpan().Slice(protocol.Length + 3, systemNameLength).ToString(); } else { From 606e829ea5165cf6d8834c69be860c200a8fbf62 Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Fri, 21 May 2021 16:57:38 -0500 Subject: [PATCH 02/13] fixed bugs in ActorPath.Parse --- .../Actor/NameAndUidBenchmarks.cs | 28 +++++++++++++++++++ src/core/Akka/Actor/ActorPath.cs | 11 +++++--- 2 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 src/benchmark/Akka.Benchmarks/Actor/NameAndUidBenchmarks.cs diff --git a/src/benchmark/Akka.Benchmarks/Actor/NameAndUidBenchmarks.cs b/src/benchmark/Akka.Benchmarks/Actor/NameAndUidBenchmarks.cs new file mode 100644 index 00000000000..fc630f82e4b --- /dev/null +++ b/src/benchmark/Akka.Benchmarks/Actor/NameAndUidBenchmarks.cs @@ -0,0 +1,28 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2021 Lightbend Inc. +// Copyright (C) 2013-2021 .NET Foundation +// +//----------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Text; +using Akka.Actor; +using Akka.Benchmarks.Configurations; +using BenchmarkDotNet.Attributes; + +namespace Akka.Benchmarks.Actor +{ + [Config(typeof(MicroBenchmarkConfig))] + public class NameAndUidBenchmarks + { + public const string ActorPath = "foo#11241311"; + + [Benchmark] + public NameAndUid ActorCell_SplitNameAndUid() + { + return ActorCell.SplitNameAndUid(ActorPath); + } + } +} diff --git a/src/core/Akka/Actor/ActorPath.cs b/src/core/Akka/Actor/ActorPath.cs index 033637ef6ae..531e532eff9 100644 --- a/src/core/Akka/Actor/ActorPath.cs +++ b/src/core/Akka/Actor/ActorPath.cs @@ -332,15 +332,18 @@ public static bool TryParse(string path, out ActorPath actorPath) if (!TryParseAddress(path, out address, out uri)) return false; var spanified = uri.AbsolutePath.AsSpan(); var pathElements = new List(); - var curLen = 0; var nextSlash = 0; + var firstSlash = spanified.IndexOf('/'); + if (firstSlash == 0) + spanified = spanified.Slice(1); // skip while ((nextSlash = spanified.IndexOf('/')) > 0) { - pathElements.Add(spanified.Slice(curLen, nextSlash).ToString()); - curLen = nextSlash + 1; + pathElements.Add(spanified.Slice(0, nextSlash).ToString()); + spanified = spanified.Slice(nextSlash + 1, spanified.Length - nextSlash - 1); } + pathElements.Add(spanified.ToString()); // final element - actorPath = new RootActorPath(address) / pathElements.Skip(1); + actorPath = new RootActorPath(address) / pathElements; if (uri.Fragment.StartsWith("#")) { var uid = int.Parse(uri.Fragment.Substring(1)); From 7bbab9411137001225120a9958e0d6d2873aa4c9 Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Fri, 21 May 2021 17:02:51 -0500 Subject: [PATCH 03/13] eliminate `List` allocation in `ActorPath.Parse` --- src/core/Akka/Actor/ActorPath.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/core/Akka/Actor/ActorPath.cs b/src/core/Akka/Actor/ActorPath.cs index 531e532eff9..bb7270850c8 100644 --- a/src/core/Akka/Actor/ActorPath.cs +++ b/src/core/Akka/Actor/ActorPath.cs @@ -327,23 +327,22 @@ public static bool TryParse(string path, out ActorPath actorPath) { actorPath = null; - Address address; - Uri uri; - if (!TryParseAddress(path, out address, out uri)) return false; + if (!TryParseAddress(path, out var address, out var uri)) return false; var spanified = uri.AbsolutePath.AsSpan(); - var pathElements = new List(); var nextSlash = 0; var firstSlash = spanified.IndexOf('/'); if (firstSlash == 0) spanified = spanified.Slice(1); // skip + + actorPath = new RootActorPath(address); + while ((nextSlash = spanified.IndexOf('/')) > 0) { - pathElements.Add(spanified.Slice(0, nextSlash).ToString()); + actorPath /= spanified.Slice(0, nextSlash).ToString(); spanified = spanified.Slice(nextSlash + 1, spanified.Length - nextSlash - 1); } - pathElements.Add(spanified.ToString()); // final element + actorPath /= spanified.ToString(); // final path element - actorPath = new RootActorPath(address) / pathElements; if (uri.Fragment.StartsWith("#")) { var uid = int.Parse(uri.Fragment.Substring(1)); From 2041b0027d19c2a48a14dd6653f9f17ee6c1c906 Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Fri, 21 May 2021 18:16:04 -0500 Subject: [PATCH 04/13] fixed some more `ActorPath` cases --- .../Akka.Tests/Actor/ActorSelectionSpec.cs | 6 ++---- src/core/Akka/Actor/ActorPath.cs | 21 ++++++++++++++++++- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/core/Akka.Tests/Actor/ActorSelectionSpec.cs b/src/core/Akka.Tests/Actor/ActorSelectionSpec.cs index 89a863597a3..49f6152c646 100644 --- a/src/core/Akka.Tests/Actor/ActorSelectionSpec.cs +++ b/src/core/Akka.Tests/Actor/ActorSelectionSpec.cs @@ -76,12 +76,10 @@ private IActorRef AskNode(IActorRef node, IQuery query) { var result = node.Ask(query).Result; - var actorRef = result as IActorRef; - if (actorRef != null) + if (result is IActorRef actorRef) return actorRef; - var selection = result as ActorSelection; - return selection != null ? Identify(selection) : null; + return result is ActorSelection selection ? Identify(selection) : null; } [Fact] diff --git a/src/core/Akka/Actor/ActorPath.cs b/src/core/Akka/Actor/ActorPath.cs index bb7270850c8..01139fb093a 100644 --- a/src/core/Akka/Actor/ActorPath.cs +++ b/src/core/Akka/Actor/ActorPath.cs @@ -341,7 +341,26 @@ public static bool TryParse(string path, out ActorPath actorPath) actorPath /= spanified.Slice(0, nextSlash).ToString(); spanified = spanified.Slice(nextSlash + 1, spanified.Length - nextSlash - 1); } - actorPath /= spanified.ToString(); // final path element + + // have a final path element - and we need to make sure it's not a trailing slash + if (spanified.Length > 0) + { + // edge case - leading slash + if (spanified.IndexOf('/') == 0) + { + spanified = spanified.Slice(1); // skip it + // edge case edge case - leading slash as was also trailing + if (spanified.Length > 0) + { + actorPath /= spanified.ToString(); + } + } + else + { + actorPath /= spanified.ToString(); + } + } + if (uri.Fragment.StartsWith("#")) { From 080ddd656d49aafe0c19747a495ccacfd6b4c772 Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Fri, 21 May 2021 19:10:19 -0500 Subject: [PATCH 05/13] cleaned up `ActorPath` code --- src/core/Akka/Actor/ActorPath.cs | 31 +++++++++---------------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/src/core/Akka/Actor/ActorPath.cs b/src/core/Akka/Actor/ActorPath.cs index 01139fb093a..abf0854b937 100644 --- a/src/core/Akka/Actor/ActorPath.cs +++ b/src/core/Akka/Actor/ActorPath.cs @@ -330,37 +330,24 @@ public static bool TryParse(string path, out ActorPath actorPath) if (!TryParseAddress(path, out var address, out var uri)) return false; var spanified = uri.AbsolutePath.AsSpan(); var nextSlash = 0; - var firstSlash = spanified.IndexOf('/'); - if (firstSlash == 0) - spanified = spanified.Slice(1); // skip actorPath = new RootActorPath(address); - while ((nextSlash = spanified.IndexOf('/')) > 0) + do { - actorPath /= spanified.Slice(0, nextSlash).ToString(); - spanified = spanified.Slice(nextSlash + 1, spanified.Length - nextSlash - 1); - } - - // have a final path element - and we need to make sure it's not a trailing slash - if (spanified.Length > 0) - { - // edge case - leading slash - if (spanified.IndexOf('/') == 0) + nextSlash = spanified.IndexOf('/'); + if (nextSlash > 0) { - spanified = spanified.Slice(1); // skip it - // edge case edge case - leading slash as was also trailing - if (spanified.Length > 0) - { - actorPath /= spanified.ToString(); - } + actorPath /= spanified.Slice(0, nextSlash).ToString(); } - else + else if (nextSlash < 0 && spanified.Length > 0) { actorPath /= spanified.ToString(); } - } - + + spanified = spanified.Slice(nextSlash + 1); + } while (nextSlash >= 0); + if (uri.Fragment.StartsWith("#")) { From b970064d313dfd7aef2a1c308d11494e54af9f2b Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Sat, 22 May 2021 22:28:26 -0500 Subject: [PATCH 06/13] made `NameAndUid` a `readonly struct`, made `internal` Breaking API change - removed `NameAndUid` from the public API and made it into a `readonly struct`. API wasn't used anywhere except internally anyway - should probably have been a `ValueTuple`. --- src/benchmark/Akka.Benchmarks/Actor/NameAndUidBenchmarks.cs | 4 ++-- src/core/Akka/Actor/ActorCell.cs | 2 +- src/core/Akka/Actor/NameAndUid.cs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/benchmark/Akka.Benchmarks/Actor/NameAndUidBenchmarks.cs b/src/benchmark/Akka.Benchmarks/Actor/NameAndUidBenchmarks.cs index fc630f82e4b..98c72709fb9 100644 --- a/src/benchmark/Akka.Benchmarks/Actor/NameAndUidBenchmarks.cs +++ b/src/benchmark/Akka.Benchmarks/Actor/NameAndUidBenchmarks.cs @@ -20,9 +20,9 @@ public class NameAndUidBenchmarks public const string ActorPath = "foo#11241311"; [Benchmark] - public NameAndUid ActorCell_SplitNameAndUid() + public void ActorCell_SplitNameAndUid() { - return ActorCell.SplitNameAndUid(ActorPath); + ActorCell.SplitNameAndUid(ActorPath); } } } diff --git a/src/core/Akka/Actor/ActorCell.cs b/src/core/Akka/Actor/ActorCell.cs index 13b5a00e6b7..02cba59657f 100644 --- a/src/core/Akka/Actor/ActorCell.cs +++ b/src/core/Akka/Actor/ActorCell.cs @@ -483,7 +483,7 @@ protected void SetActorFields(ActorBase actor) /// /// TBD /// TBD - public static NameAndUid SplitNameAndUid(string name) + internal static NameAndUid SplitNameAndUid(string name) { var i = name.IndexOf('#'); return i < 0 diff --git a/src/core/Akka/Actor/NameAndUid.cs b/src/core/Akka/Actor/NameAndUid.cs index 104a45d4a77..eba23fdd080 100644 --- a/src/core/Akka/Actor/NameAndUid.cs +++ b/src/core/Akka/Actor/NameAndUid.cs @@ -8,9 +8,9 @@ namespace Akka.Actor { /// - /// TBD + /// INTERNAL API /// - public class NameAndUid + internal readonly struct NameAndUid { private readonly string _name; private readonly int _uid; From 883dcad2aca1884112e4fb7274b5dd46391da459 Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Sat, 22 May 2021 22:41:54 -0500 Subject: [PATCH 07/13] approved removal of `NameAndUid` from public API --- .../Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt b/src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt index 8aee29b1c71..79950772e75 100644 --- a/src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt +++ b/src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt @@ -115,7 +115,6 @@ namespace Akka.Actor protected bool SetChildrenTerminationReason(Akka.Actor.Internal.SuspendReason reason) { } public void SetReceiveTimeout(System.Nullable timeout = null) { } protected void SetTerminated() { } - public static Akka.Actor.NameAndUid SplitNameAndUid(string name) { } public virtual void Start() { } protected void Stash(Akka.Dispatch.SysMsg.SystemMessage msg) { } public void Stop(Akka.Actor.IActorRef child) { } @@ -1325,13 +1324,6 @@ namespace Akka.Actor public override void Suspend() { } protected override void TellInternal(object message, Akka.Actor.IActorRef sender) { } } - public class NameAndUid - { - public NameAndUid(string name, int uid) { } - public string Name { get; } - public int Uid { get; } - public override string ToString() { } - } public sealed class Nobody : Akka.Actor.MinimalActorRef { public static Akka.Actor.Nobody Instance; From 973be1a6fd291b86b799539038b2f62a31c18479 Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Sat, 22 May 2021 22:42:23 -0500 Subject: [PATCH 08/13] Revert "approved removal of `NameAndUid` from public API" This reverts commit 883dcad2aca1884112e4fb7274b5dd46391da459. --- .../Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt b/src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt index 79950772e75..8aee29b1c71 100644 --- a/src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt +++ b/src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt @@ -115,6 +115,7 @@ namespace Akka.Actor protected bool SetChildrenTerminationReason(Akka.Actor.Internal.SuspendReason reason) { } public void SetReceiveTimeout(System.Nullable timeout = null) { } protected void SetTerminated() { } + public static Akka.Actor.NameAndUid SplitNameAndUid(string name) { } public virtual void Start() { } protected void Stash(Akka.Dispatch.SysMsg.SystemMessage msg) { } public void Stop(Akka.Actor.IActorRef child) { } @@ -1324,6 +1325,13 @@ namespace Akka.Actor public override void Suspend() { } protected override void TellInternal(object message, Akka.Actor.IActorRef sender) { } } + public class NameAndUid + { + public NameAndUid(string name, int uid) { } + public string Name { get; } + public int Uid { get; } + public override string ToString() { } + } public sealed class Nobody : Akka.Actor.MinimalActorRef { public static Akka.Actor.Nobody Instance; From 3adfbf807f705db00015dc613c8e1aee6d82bd0e Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Sat, 22 May 2021 22:42:28 -0500 Subject: [PATCH 09/13] Revert "made `NameAndUid` a `readonly struct`, made `internal`" This reverts commit b970064d313dfd7aef2a1c308d11494e54af9f2b. --- src/benchmark/Akka.Benchmarks/Actor/NameAndUidBenchmarks.cs | 4 ++-- src/core/Akka/Actor/ActorCell.cs | 2 +- src/core/Akka/Actor/NameAndUid.cs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/benchmark/Akka.Benchmarks/Actor/NameAndUidBenchmarks.cs b/src/benchmark/Akka.Benchmarks/Actor/NameAndUidBenchmarks.cs index 98c72709fb9..fc630f82e4b 100644 --- a/src/benchmark/Akka.Benchmarks/Actor/NameAndUidBenchmarks.cs +++ b/src/benchmark/Akka.Benchmarks/Actor/NameAndUidBenchmarks.cs @@ -20,9 +20,9 @@ public class NameAndUidBenchmarks public const string ActorPath = "foo#11241311"; [Benchmark] - public void ActorCell_SplitNameAndUid() + public NameAndUid ActorCell_SplitNameAndUid() { - ActorCell.SplitNameAndUid(ActorPath); + return ActorCell.SplitNameAndUid(ActorPath); } } } diff --git a/src/core/Akka/Actor/ActorCell.cs b/src/core/Akka/Actor/ActorCell.cs index 02cba59657f..13b5a00e6b7 100644 --- a/src/core/Akka/Actor/ActorCell.cs +++ b/src/core/Akka/Actor/ActorCell.cs @@ -483,7 +483,7 @@ protected void SetActorFields(ActorBase actor) /// /// TBD /// TBD - internal static NameAndUid SplitNameAndUid(string name) + public static NameAndUid SplitNameAndUid(string name) { var i = name.IndexOf('#'); return i < 0 diff --git a/src/core/Akka/Actor/NameAndUid.cs b/src/core/Akka/Actor/NameAndUid.cs index eba23fdd080..104a45d4a77 100644 --- a/src/core/Akka/Actor/NameAndUid.cs +++ b/src/core/Akka/Actor/NameAndUid.cs @@ -8,9 +8,9 @@ namespace Akka.Actor { /// - /// INTERNAL API + /// TBD /// - internal readonly struct NameAndUid + public class NameAndUid { private readonly string _name; private readonly int _uid; From 24d6393f53908e3d4e376e61687984125880e6dd Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Sat, 22 May 2021 22:52:06 -0500 Subject: [PATCH 10/13] deprecated old `NameAndUid` API in non-breaking way. --- .../Actor/NameAndUidBenchmarks.cs | 6 ++++++ .../CoreAPISpec.ApproveCore.approved.txt | 2 ++ src/core/Akka.Remote/RemoteSystemDaemon.cs | 6 +++--- src/core/Akka/Actor/ActorCell.Children.cs | 7 +++---- src/core/Akka/Actor/ActorCell.cs | 16 +++++++++++++++- src/core/Akka/Actor/ActorPath.cs | 4 ++-- src/core/Akka/Actor/NameAndUid.cs | 5 ++++- src/core/Akka/Actor/RepointableActorRef.cs | 10 ++++------ 8 files changed, 39 insertions(+), 17 deletions(-) diff --git a/src/benchmark/Akka.Benchmarks/Actor/NameAndUidBenchmarks.cs b/src/benchmark/Akka.Benchmarks/Actor/NameAndUidBenchmarks.cs index fc630f82e4b..c616f5527b4 100644 --- a/src/benchmark/Akka.Benchmarks/Actor/NameAndUidBenchmarks.cs +++ b/src/benchmark/Akka.Benchmarks/Actor/NameAndUidBenchmarks.cs @@ -24,5 +24,11 @@ public NameAndUid ActorCell_SplitNameAndUid() { return ActorCell.SplitNameAndUid(ActorPath); } + + [Benchmark] + public (string name, int uid) ActorCell_GetNameAndUid() + { + return ActorCell.GetNameAndUid(ActorPath); + } } } diff --git a/src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt b/src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt index 8aee29b1c71..cde266ae92e 100644 --- a/src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt +++ b/src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt @@ -115,6 +115,7 @@ namespace Akka.Actor protected bool SetChildrenTerminationReason(Akka.Actor.Internal.SuspendReason reason) { } public void SetReceiveTimeout(System.Nullable timeout = null) { } protected void SetTerminated() { } + [System.ObsoleteAttribute("Not used. Will be removed in Akka.NET v1.5.")] public static Akka.Actor.NameAndUid SplitNameAndUid(string name) { } public virtual void Start() { } protected void Stash(Akka.Dispatch.SysMsg.SystemMessage msg) { } @@ -1325,6 +1326,7 @@ namespace Akka.Actor public override void Suspend() { } protected override void TellInternal(object message, Akka.Actor.IActorRef sender) { } } + [System.ObsoleteAttribute("Not used. Will be removed in Akka.NET v1.5.")] public class NameAndUid { public NameAndUid(string name, int uid) { } diff --git a/src/core/Akka.Remote/RemoteSystemDaemon.cs b/src/core/Akka.Remote/RemoteSystemDaemon.cs index 7393d0aa5b6..1a820b62fb3 100644 --- a/src/core/Akka.Remote/RemoteSystemDaemon.cs +++ b/src/core/Akka.Remote/RemoteSystemDaemon.cs @@ -308,10 +308,10 @@ public override IActorRef GetChild(IEnumerable name) var n = 0; while (true) { - var nameAndUid = ActorCell.SplitNameAndUid(path); - if (TryGetChild(nameAndUid.Name, out var child)) + var (s, uid) = ActorCell.GetNameAndUid(path); + if (TryGetChild(s, out var child)) { - if (nameAndUid.Uid != ActorCell.UndefinedUid && nameAndUid.Uid != child.Path.Uid) + if (uid != ActorCell.UndefinedUid && uid != child.Path.Uid) return Nobody.Instance; return n == 0 ? child : child.GetChild(name.TakeRight(n)); } diff --git a/src/core/Akka/Actor/ActorCell.Children.cs b/src/core/Akka/Actor/ActorCell.Children.cs index cc38364269d..9c69a74df31 100644 --- a/src/core/Akka/Actor/ActorCell.Children.cs +++ b/src/core/Akka/Actor/ActorCell.Children.cs @@ -387,17 +387,16 @@ public bool TryGetSingleChild(string name, out IInternalActorRef child) } else { - var nameAndUid = SplitNameAndUid(name); - if (TryGetChildRestartStatsByName(nameAndUid.Name, out var stats)) + var (s, uid) = GetNameAndUid(name); + if (TryGetChildRestartStatsByName(s, out var stats)) { - var uid = nameAndUid.Uid; if (uid == ActorCell.UndefinedUid || uid == stats.Uid) { child = stats.Child; return true; } } - else if (TryGetFunctionRef(nameAndUid.Name, nameAndUid.Uid, out var functionRef)) + else if (TryGetFunctionRef(s, uid, out var functionRef)) { child = functionRef; return true; diff --git a/src/core/Akka/Actor/ActorCell.cs b/src/core/Akka/Actor/ActorCell.cs index 13b5a00e6b7..891b9843718 100644 --- a/src/core/Akka/Actor/ActorCell.cs +++ b/src/core/Akka/Actor/ActorCell.cs @@ -479,10 +479,11 @@ protected void SetActorFields(ActorBase actor) actor?.Unclear(); } /// - /// TBD + /// INTERNAL API /// /// TBD /// TBD + [Obsolete("Not used. Will be removed in Akka.NET v1.5.")] public static NameAndUid SplitNameAndUid(string name) { var i = name.IndexOf('#'); @@ -491,6 +492,19 @@ public static NameAndUid SplitNameAndUid(string name) : new NameAndUid(name.Substring(0, i), Int32.Parse(name.Substring(i + 1))); } + /// + /// INTERNAL API + /// + /// The full name of the actor, including the UID if known + /// A new (string name, int uid) instance. + internal static (string name, int uid) GetNameAndUid(string name) + { + var i = name.IndexOf('#'); + return i < 0 + ? (name, UndefinedUid) + : (name.Substring(0, i), Int32.Parse(name.Substring(i + 1))); + } + /// /// TBD /// diff --git a/src/core/Akka/Actor/ActorPath.cs b/src/core/Akka/Actor/ActorPath.cs index abf0854b937..9695d5b0377 100644 --- a/src/core/Akka/Actor/ActorPath.cs +++ b/src/core/Akka/Actor/ActorPath.cs @@ -276,8 +276,8 @@ public bool Equals(ActorPath other) /// A newly created public static ActorPath operator /(ActorPath path, string name) { - var nameAndUid = ActorCell.SplitNameAndUid(name); - return new ChildActorPath(path, nameAndUid.Name, nameAndUid.Uid); + var (s, uid) = ActorCell.GetNameAndUid(name); + return new ChildActorPath(path, s, uid); } /// diff --git a/src/core/Akka/Actor/NameAndUid.cs b/src/core/Akka/Actor/NameAndUid.cs index 104a45d4a77..fdef5823b4f 100644 --- a/src/core/Akka/Actor/NameAndUid.cs +++ b/src/core/Akka/Actor/NameAndUid.cs @@ -5,11 +5,14 @@ // //----------------------------------------------------------------------- +using System; + namespace Akka.Actor { /// - /// TBD + /// INTERNAL API /// + [Obsolete("Not used. Will be removed in Akka.NET v1.5.")] public class NameAndUid { private readonly string _name; diff --git a/src/core/Akka/Actor/RepointableActorRef.cs b/src/core/Akka/Actor/RepointableActorRef.cs index c0e3773a8d9..6933f23da21 100644 --- a/src/core/Akka/Actor/RepointableActorRef.cs +++ b/src/core/Akka/Actor/RepointableActorRef.cs @@ -292,12 +292,10 @@ public override IActorRef GetChild(IEnumerable name) case "": return ActorRefs.Nobody; default: - var nameAndUid = ActorCell.SplitNameAndUid(next); - if (Lookup.TryGetChildStatsByName(nameAndUid.Name, out var stats)) + var (s, uid) = ActorCell.GetNameAndUid(next); + if (Lookup.TryGetChildStatsByName(s, out var stats)) { - var crs = stats as ChildRestartStats; - var uid = nameAndUid.Uid; - if (crs != null && (uid == ActorCell.UndefinedUid || uid == crs.Uid)) + if (stats is ChildRestartStats crs && (uid == ActorCell.UndefinedUid || uid == crs.Uid)) { if (name.Skip(1).Any()) return crs.Child.GetChild(name.Skip(1)); @@ -305,7 +303,7 @@ public override IActorRef GetChild(IEnumerable name) return crs.Child; } } - else if (Lookup is ActorCell cell && cell.TryGetFunctionRef(nameAndUid.Name, nameAndUid.Uid, out var functionRef)) + else if (Lookup is ActorCell cell && cell.TryGetFunctionRef(s, uid, out var functionRef)) { return functionRef; } From e9da9ee91dbeef8389ff47b910bb9d4d178e7336 Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Sun, 23 May 2021 00:16:53 -0500 Subject: [PATCH 11/13] experimenting with Span Polyfills --- src/core/Akka/Actor/ActorCell.cs | 2 +- src/core/Akka/Actor/ActorPath.cs | 2 +- src/core/Akka/Util/SpanHacks.cs | 48 ++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 src/core/Akka/Util/SpanHacks.cs diff --git a/src/core/Akka/Actor/ActorCell.cs b/src/core/Akka/Actor/ActorCell.cs index 891b9843718..0894b4707e5 100644 --- a/src/core/Akka/Actor/ActorCell.cs +++ b/src/core/Akka/Actor/ActorCell.cs @@ -502,7 +502,7 @@ internal static (string name, int uid) GetNameAndUid(string name) var i = name.IndexOf('#'); return i < 0 ? (name, UndefinedUid) - : (name.Substring(0, i), Int32.Parse(name.Substring(i + 1))); + : (name.Substring(0, i), SpanHacks.Parse(name.AsSpan(i + 1))); } /// diff --git a/src/core/Akka/Actor/ActorPath.cs b/src/core/Akka/Actor/ActorPath.cs index 9695d5b0377..fa1a730ff36 100644 --- a/src/core/Akka/Actor/ActorPath.cs +++ b/src/core/Akka/Actor/ActorPath.cs @@ -351,7 +351,7 @@ public static bool TryParse(string path, out ActorPath actorPath) if (uri.Fragment.StartsWith("#")) { - var uid = int.Parse(uri.Fragment.Substring(1)); + var uid = SpanHacks.Parse(uri.Fragment.AsSpan(1)); actorPath = actorPath.WithUid(uid); } return true; diff --git a/src/core/Akka/Util/SpanHacks.cs b/src/core/Akka/Util/SpanHacks.cs new file mode 100644 index 00000000000..147ac8a240c --- /dev/null +++ b/src/core/Akka/Util/SpanHacks.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Akka.Util +{ + /// + /// INTERNAL API. + /// + /// polyfills that should be deleted once we drop .NET Standard 2.0 support. + /// + internal static class SpanHacks + { + /// + /// Parses an integer from a string. + /// + /// + /// PERFORMS NO INPUT VALIDATION. + /// + /// The span of input characters. + /// An . + public static int Parse(ReadOnlySpan str) + { + var pos = 0; + var returnValue = 0; + var sign = 1; + if (str[0] == '-') + { + sign = -1; + pos++; + } + + bool IsNumeric(char x) + { + return (x >= '0' && x <= '9'); + } + + for (; pos < str.Length; pos++) + { + if (!IsNumeric(str[pos])) + throw new FormatException($"[{str.ToString()}] is now a valid numeric format"); + returnValue = returnValue * 10 + str[pos] - '0'; + } + + return sign * returnValue; + } + } +} From 17376066267b386545816fcba218a3f438e890fe Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Mon, 24 May 2021 08:46:56 -0500 Subject: [PATCH 12/13] added some more ActorPath benchmarks --- .../Actor/ActorPathBenchmarks.cs | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/benchmark/Akka.Benchmarks/Actor/ActorPathBenchmarks.cs b/src/benchmark/Akka.Benchmarks/Actor/ActorPathBenchmarks.cs index 02c72881c98..b9885c1290c 100644 --- a/src/benchmark/Akka.Benchmarks/Actor/ActorPathBenchmarks.cs +++ b/src/benchmark/Akka.Benchmarks/Actor/ActorPathBenchmarks.cs @@ -16,12 +16,16 @@ public class ActorPathBenchmarks { private ActorPath x; private ActorPath y; + private ActorPath _childPath; + private Address _sysAdr = new Address("akka.tcp", "system", "127.0.0.1", 1337); + private Address _otherAdr = new Address("akka.tcp", "system", "127.0.0.1", 1338); [GlobalSetup] public void Setup() { - x = new RootActorPath(new Address("akka.tcp", "system", "127.0.0.1", 1337), "user"); - y = new RootActorPath(new Address("akka.tcp", "system", "127.0.0.1", 1337), "system"); + x = new RootActorPath(_sysAdr, "user"); + y = new RootActorPath(_sysAdr, "system"); + _childPath = x / "parent" / "child"; } [Benchmark] @@ -45,7 +49,19 @@ public bool ActorPath_Equals() [Benchmark] public string ActorPath_ToString() { - return x.ToString(); + return _childPath.ToString(); + } + + [Benchmark] + public string ActorPath_ToSerializationFormat() + { + return _childPath.ToSerializationFormat(); + } + + [Benchmark] + public string ActorPath_ToSerializationFormatWithAddress() + { + return _childPath.ToSerializationFormatWithAddress(_otherAdr); } } } From 1baa2729c8ee4540828eb729285812fbf518709f Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Tue, 25 May 2021 19:33:38 -0500 Subject: [PATCH 13/13] typesafe FastHash --- src/core/Akka.Remote/Serialization/LruBoundedCache.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/Akka.Remote/Serialization/LruBoundedCache.cs b/src/core/Akka.Remote/Serialization/LruBoundedCache.cs index f5b0e9b5111..e2a99fde2f3 100644 --- a/src/core/Akka.Remote/Serialization/LruBoundedCache.cs +++ b/src/core/Akka.Remote/Serialization/LruBoundedCache.cs @@ -25,7 +25,7 @@ internal static class FastHash /// A 32-bit pseudo-random hash value. public static int OfString(string s) { - var chars = s.ToCharArray(); + var chars = s.AsSpan(); var s0 = 391408L; // seed value 1, DON'T CHANGE var s1 = 601258L; // seed value 2, DON'T CHANGE unchecked