diff --git a/src/benchmark/Akka.Benchmarks/Utils/FastLazyBenchmarks.cs b/src/benchmark/Akka.Benchmarks/Utils/FastLazyBenchmarks.cs
index 59524cc250f..f90fc34b91b 100644
--- a/src/benchmark/Akka.Benchmarks/Utils/FastLazyBenchmarks.cs
+++ b/src/benchmark/Akka.Benchmarks/Utils/FastLazyBenchmarks.cs
@@ -50,7 +50,7 @@ public int FastLazy_get_value()
}
[Benchmark]
- public int FastLazy_satefull_get_value()
+ public int FastLazy_stateful_get_value()
{
return fastLazyWithInit.Value;
}
diff --git a/src/benchmark/Akka.Benchmarks/Utils/SpanHackBenchmarks.cs b/src/benchmark/Akka.Benchmarks/Utils/SpanHackBenchmarks.cs
new file mode 100644
index 00000000000..fcd83ce3391
--- /dev/null
+++ b/src/benchmark/Akka.Benchmarks/Utils/SpanHackBenchmarks.cs
@@ -0,0 +1,34 @@
+// //-----------------------------------------------------------------------
+//
+// Copyright (C) 2009-2022 Lightbend Inc.
+// Copyright (C) 2013-2022 .NET Foundation
+//
+//-----------------------------------------------------------------------
+
+using System;
+using Akka.Benchmarks.Configurations;
+using Akka.Util;
+using BenchmarkDotNet.Attributes;
+
+namespace Akka.Benchmarks.Utils
+{
+ [Config(typeof(MicroBenchmarkConfig))]
+ public class SpanHackBenchmarks
+ {
+ [Params(0, 1, -1, 1000, int.MaxValue, long.MaxValue)]
+ public long Formatted { get; set; }
+
+ [Benchmark]
+ public int Int64CharCountBenchmark()
+ {
+ return SpanHacks.Int64SizeInCharacters(Formatted);
+ }
+
+ [Benchmark]
+ public int TryFormatBenchmark()
+ {
+ Span buffer = stackalloc char[22];
+ return SpanHacks.TryFormat(Formatted, 0, ref buffer);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/core/Akka.Tests/Actor/ActorPathSpec.cs b/src/core/Akka.Tests/Actor/ActorPathSpec.cs
index b22f2eedc12..27207259380 100644
--- a/src/core/Akka.Tests/Actor/ActorPathSpec.cs
+++ b/src/core/Akka.Tests/Actor/ActorPathSpec.cs
@@ -22,6 +22,17 @@ public void SupportsParsingItsStringRep()
var path = new RootActorPath(new Address("akka.tcp", "mysys")) / "user";
ActorPathParse(path.ToString()).ShouldBe(path);
}
+
+ [Theory]
+ [InlineData(1)]
+ [InlineData(100)]
+ [InlineData(int.MaxValue)]
+ public void SupportsParsingItsStringRepWithUid(int uid)
+ {
+ var path = new RootActorPath(new Address("akka.tcp", "mysys", "localhost", 9110)) / "user";
+ var pathWithUid = path.WithUid(uid);
+ ActorPathParse(pathWithUid.ToSerializationFormat()).ShouldBe(pathWithUid);
+ }
private ActorPath ActorPathParse(string path)
{
diff --git a/src/core/Akka/Actor/ActorPath.cs b/src/core/Akka/Actor/ActorPath.cs
index fcd185acb6f..09a4c784e1e 100644
--- a/src/core/Akka/Actor/ActorPath.cs
+++ b/src/core/Akka/Actor/ActorPath.cs
@@ -557,8 +557,15 @@ public static bool TryParseParts(ReadOnlySpan path, out ReadOnlySpan
///
/// the address or empty
/// System.String.
- private string Join(ReadOnlySpan prefix)
+ private string Join(ReadOnlySpan prefix, long? uid = null)
{
+ void AppendUidSpan(ref Span writeable, int startPos, int sizeHint)
+ {
+ if (uid == null) return;
+ writeable[startPos] = '#';
+ SpanHacks.TryFormat(uid.Value, startPos+1, ref writeable, sizeHint);
+ }
+
if (_depth == 0)
{
Span buffer = prefix.Length < 1024 ? stackalloc char[prefix.Length + 1] : new char[prefix.Length + 1];
@@ -576,17 +583,28 @@ private string Join(ReadOnlySpan prefix)
totalLength += p._name.Length + 1;
p = p._parent;
}
+
+ // UID calculation
+ var uidSizeHint = 0;
+ if (uid != null)
+ {
+ // 1 extra character for the '#'
+ uidSizeHint = SpanHacks.Int64SizeInCharacters(uid.Value) + 1;
+ totalLength += uidSizeHint;
+ }
// Concatenate segments (in reverse order) into buffer with '/' prefixes
Span buffer = totalLength < 1024 ? stackalloc char[totalLength] : new char[totalLength];
prefix.CopyTo(buffer);
- var offset = buffer.Length;
- ReadOnlySpan name;
+ var offset = buffer.Length - uidSizeHint;
+ // append UID span first
+ AppendUidSpan(ref buffer, offset, uidSizeHint-1); // -1 for the '#'
+
p = this;
while (p._depth > 0)
{
- name = p._name.AsSpan();
+ var name = p._name.AsSpan();
offset -= name.Length + 1;
buffer[offset] = '/';
name.CopyTo(buffer.Slice(offset + 1, name.Length));
@@ -676,7 +694,12 @@ public override bool Equals(object obj)
/// System.String.
public string ToStringWithAddress()
{
- return ToStringWithAddress(_address);
+ return ToStringWithAddress(_address, false);
+ }
+
+ private string ToStringWithAddress(bool includeUid)
+ {
+ return ToStringWithAddress(_address, includeUid);
}
///
@@ -685,7 +708,7 @@ public string ToStringWithAddress()
/// TBD
public string ToSerializationFormat()
{
- return AppendUidFragment(ToStringWithAddress());
+ return ToStringWithAddress(true);
}
///
@@ -700,8 +723,7 @@ public string ToSerializationFormatWithAddress(Address address)
// we never change address for IgnoreActorRef
return ToString();
}
- var withAddress = ToStringWithAddress(address);
- var result = AppendUidFragment(withAddress);
+ var result = ToStringWithAddress(address, true);
return result;
}
@@ -718,16 +740,26 @@ private string AppendUidFragment(string withAddress)
/// The address.
/// System.String.
public string ToStringWithAddress(Address address)
+ {
+ return ToStringWithAddress(address, false);
+ }
+
+ private string ToStringWithAddress(Address address, bool includeUid)
{
if (IgnoreActorRef.IsIgnoreRefPath(this))
{
// we never change address for IgnoreActorRef
return ToString();
}
+
+ long? uid = null;
+ if (includeUid && _uid != ActorCell.UndefinedUid)
+ uid = _uid;
+
if (_address.Host != null && _address.Port.HasValue)
- return Join(_address.ToString().AsSpan());
+ return Join(_address.ToString().AsSpan(), uid);
- return Join(address.ToString().AsSpan());
+ return Join(address.ToString().AsSpan(), uid);
}
///
diff --git a/src/core/Akka/Akka.csproj b/src/core/Akka/Akka.csproj
index 4b18ab6cdd1..eaeade66477 100644
--- a/src/core/Akka/Akka.csproj
+++ b/src/core/Akka/Akka.csproj
@@ -7,7 +7,7 @@
$(NetStandardLibVersion)
$(AkkaPackageTags)
true
- 7.2
+ 9
diff --git a/src/core/Akka/Util/SpanHacks.cs b/src/core/Akka/Util/SpanHacks.cs
index 08464610126..41e7f4e8bf1 100644
--- a/src/core/Akka/Util/SpanHacks.cs
+++ b/src/core/Akka/Util/SpanHacks.cs
@@ -1,6 +1,4 @@
using System;
-using System.Collections.Generic;
-using System.Text;
namespace Akka.Util
{
@@ -28,7 +26,101 @@ public static int Parse(ReadOnlySpan str)
{
if (TryParse(str, out var i))
return i;
- throw new FormatException($"[{str.ToString()}] is now a valid numeric format");
+ throw new FormatException($"[{str.ToString()}] is not a valid numeric format");
+ }
+
+ private const char Negative = '-';
+ private static readonly char[] Numbers = { '0','1','2','3','4','5','6','7','8','9' };
+
+ ///
+ /// Can replace with int64.TryFormat in later versions of .NET.
+ ///
+ /// The integer we want to format into a string.
+ /// Starting position in the destination span we're going to write from
+ /// The span we're going to write our characters into.
+ /// Optional size hint, in order to avoid recalculating it.
+ ///
+ public static int TryFormat(long i, int startPos, ref Span span, int sizeHint = 0)
+ {
+ var index = 0;
+ if (i is < 10 and >= 0)
+ {
+ span[startPos] = (char)(i+'0');
+ return 1;
+ }
+
+ var negative = 0;
+ if (i < 0)
+ {
+ negative = 1;
+ i = Math.Abs(i);
+ }
+
+ var targetLength = sizeHint > 0 ? sizeHint : PositiveInt64SizeInCharacters(i, negative);
+
+ while (i > 0)
+ {
+ i = Math.DivRem(i, 10, out var rem);
+ span[startPos + targetLength - index++ - 1] = (char)(rem+'0');
+ }
+
+ if(negative == 1){
+ span[0] = Negative;
+ index++;
+ }
+
+ return index;
+ }
+
+ ///
+ /// How many characters do we need to represent this int as a string?
+ ///
+ /// The int.
+ /// Character length.
+ public static int Int64SizeInCharacters(long i)
+ {
+ // account for negative characters
+ var padding = 0;
+ if (i < 0)
+ {
+ i *= -1;
+ padding = 1;
+ }
+
+ return PositiveInt64SizeInCharacters(i, padding);
+ }
+
+ public static int PositiveInt64SizeInCharacters(long i, int padding)
+ {
+ switch (i)
+ {
+ case 0:
+ return 1;
+ case < 10:
+ return 1 + padding;
+ case < 100:
+ return 2 + padding;
+ case < 1000:
+ return 3 + padding;
+ case < 10000:
+ return 4 + padding;
+ case < 100000:
+ return 5 + padding;
+ case < 1_000_000:
+ return 6 + padding;
+ case < 10_000_000:
+ return 7 + padding;
+ case < 100_000_000:
+ return 8 + padding;
+ case < 1_000_000_000:
+ return 9 + padding;
+ case < 10_000_000_000:
+ return 10 + padding;
+ case < 100_000_000_000:
+ return 11 + padding;
+ default:
+ return (int)Math.Log10(i) + 1 + padding;
+ }
}
///