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); } } } diff --git a/src/benchmark/Akka.Benchmarks/Actor/NameAndUidBenchmarks.cs b/src/benchmark/Akka.Benchmarks/Actor/NameAndUidBenchmarks.cs new file mode 100644 index 00000000000..c616f5527b4 --- /dev/null +++ b/src/benchmark/Akka.Benchmarks/Actor/NameAndUidBenchmarks.cs @@ -0,0 +1,34 @@ +//----------------------------------------------------------------------- +// +// 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); + } + + [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.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 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/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..0894b4707e5 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), SpanHacks.Parse(name.AsSpan(i + 1))); + } + /// /// TBD /// diff --git a/src/core/Akka/Actor/ActorPath.cs b/src/core/Akka/Actor/ActorPath.cs index db2a0488988..fa1a730ff36 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); } /// @@ -327,15 +327,31 @@ public static bool TryParse(string path, out ActorPath actorPath) { actorPath = null; + if (!TryParseAddress(path, out var address, out var uri)) return false; + var spanified = uri.AbsolutePath.AsSpan(); + var nextSlash = 0; + + actorPath = new RootActorPath(address); + + do + { + nextSlash = spanified.IndexOf('/'); + if (nextSlash > 0) + { + actorPath /= spanified.Slice(0, nextSlash).ToString(); + } + else if (nextSlash < 0 && spanified.Length > 0) + { + actorPath /= spanified.ToString(); + } + + spanified = spanified.Slice(nextSlash + 1); + } while (nextSlash >= 0); + - Address address; - Uri uri; - if (!TryParseAddress(path, out address, out uri)) return false; - var pathElements = uri.AbsolutePath.Split('/'); - actorPath = new RootActorPath(address) / pathElements.Skip(1); 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; @@ -378,13 +394,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 { 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; } 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; + } + } +}