diff --git a/src/Akka.sln b/src/Akka.sln
index bf0782f7236..d5e76f9ac0e 100644
--- a/src/Akka.sln
+++ b/src/Akka.sln
@@ -248,6 +248,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DDataStressTest", "examples
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Akka.Cluster.Benchmarks", "benchmark\Akka.Cluster.Benchmarks\Akka.Cluster.Benchmarks.csproj", "{3CEBB0AE-6A88-4C32-A1D3-A8FB1E7E236B}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Akka.Cluster.Benchmark.DotTrace", "benchmark\Akka.Cluster.Benchmark.DotTrace\Akka.Cluster.Benchmark.DotTrace.csproj", "{9D721A3E-4D4D-4715-B1E2-2B392DA9581F}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -1151,6 +1153,18 @@ Global
{3CEBB0AE-6A88-4C32-A1D3-A8FB1E7E236B}.Release|x64.Build.0 = Release|Any CPU
{3CEBB0AE-6A88-4C32-A1D3-A8FB1E7E236B}.Release|x86.ActiveCfg = Release|Any CPU
{3CEBB0AE-6A88-4C32-A1D3-A8FB1E7E236B}.Release|x86.Build.0 = Release|Any CPU
+ {9D721A3E-4D4D-4715-B1E2-2B392DA9581F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9D721A3E-4D4D-4715-B1E2-2B392DA9581F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9D721A3E-4D4D-4715-B1E2-2B392DA9581F}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {9D721A3E-4D4D-4715-B1E2-2B392DA9581F}.Debug|x64.Build.0 = Debug|Any CPU
+ {9D721A3E-4D4D-4715-B1E2-2B392DA9581F}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {9D721A3E-4D4D-4715-B1E2-2B392DA9581F}.Debug|x86.Build.0 = Debug|Any CPU
+ {9D721A3E-4D4D-4715-B1E2-2B392DA9581F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9D721A3E-4D4D-4715-B1E2-2B392DA9581F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9D721A3E-4D4D-4715-B1E2-2B392DA9581F}.Release|x64.ActiveCfg = Release|Any CPU
+ {9D721A3E-4D4D-4715-B1E2-2B392DA9581F}.Release|x64.Build.0 = Release|Any CPU
+ {9D721A3E-4D4D-4715-B1E2-2B392DA9581F}.Release|x86.ActiveCfg = Release|Any CPU
+ {9D721A3E-4D4D-4715-B1E2-2B392DA9581F}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -1260,6 +1274,7 @@ Global
{2E4B9584-42CC-4D17-B719-9F462B16C94D} = {73108242-625A-4D7B-AA09-63375DBAE464}
{44B3DDD6-6103-4E8F-8AC2-0F4BA3CF6B50} = {C50E1A9E-820C-4E75-AE39-6F96A99AC4A7}
{3CEBB0AE-6A88-4C32-A1D3-A8FB1E7E236B} = {73108242-625A-4D7B-AA09-63375DBAE464}
+ {9D721A3E-4D4D-4715-B1E2-2B392DA9581F} = {73108242-625A-4D7B-AA09-63375DBAE464}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {03AD8E21-7507-4E68-A4E9-F4A7E7273164}
diff --git a/src/benchmark/Akka.Benchmarks/Remoting/FastHashBenchmarks.cs b/src/benchmark/Akka.Benchmarks/Remoting/FastHashBenchmarks.cs
index 87d3155de53..ce82f216654 100644
--- a/src/benchmark/Akka.Benchmarks/Remoting/FastHashBenchmarks.cs
+++ b/src/benchmark/Akka.Benchmarks/Remoting/FastHashBenchmarks.cs
@@ -23,5 +23,11 @@ public int FastHash_OfStringUnsafe()
{
return FastHash.OfStringFast(HashKey1);
}
+
+ [Benchmark]
+ public int FastHash_Djb2()
+ {
+ return FastHash.GetDjb2HashCode(HashKey1);
+ }
}
}
diff --git a/src/benchmark/Akka.Benchmarks/Remoting/LruBoundedCacheBenchmarks.cs b/src/benchmark/Akka.Benchmarks/Remoting/LruBoundedCacheBenchmarks.cs
index d01ca7eda69..c0b9eb94e12 100644
--- a/src/benchmark/Akka.Benchmarks/Remoting/LruBoundedCacheBenchmarks.cs
+++ b/src/benchmark/Akka.Benchmarks/Remoting/LruBoundedCacheBenchmarks.cs
@@ -90,13 +90,13 @@ public void AddressHitBenchmark()
[Benchmark]
public void ActorPathCacheHitBenchmark()
{
- _pathCache.Cache.GetOrCompute(_cacheHitPath);
+ _pathCache.Cache.GetOrCompute(_cacheHitPath, out var isTemp);
}
[Benchmark]
public void ActorPathCacheMissBenchmark()
{
- _pathCache.Cache.GetOrCompute(_cacheMissPath);
+ _pathCache.Cache.GetOrCompute(_cacheMissPath, out var isTemp);
}
[GlobalCleanup]
diff --git a/src/benchmark/Akka.Cluster.Benchmark.DotTrace/Akka.Cluster.Benchmark.DotTrace.csproj b/src/benchmark/Akka.Cluster.Benchmark.DotTrace/Akka.Cluster.Benchmark.DotTrace.csproj
new file mode 100644
index 00000000000..53da5b90db9
--- /dev/null
+++ b/src/benchmark/Akka.Cluster.Benchmark.DotTrace/Akka.Cluster.Benchmark.DotTrace.csproj
@@ -0,0 +1,20 @@
+
+
+
+ Exe
+ net5.0;netcoreapp3.1
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
diff --git a/src/benchmark/Akka.Cluster.Benchmark.DotTrace/Program.cs b/src/benchmark/Akka.Cluster.Benchmark.DotTrace/Program.cs
new file mode 100644
index 00000000000..bd22daee304
--- /dev/null
+++ b/src/benchmark/Akka.Cluster.Benchmark.DotTrace/Program.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Diagnostics;
+using System.Threading.Tasks;
+using Akka.Cluster.Benchmarks.Sharding;
+using Akka.Cluster.Sharding;
+using JetBrains.Profiler.Api;
+
+namespace Akka.Cluster.Benchmark.DotTrace
+{
+ class Program
+ {
+ static async Task Main(string[] args)
+ {
+
+ await Task.Run(async () =>
+ {
+
+
+ var container = new ShardMessageRoutingBenchmarks();
+ container.StateMode = StateStoreMode.DData;
+ container.MsgCount = 1;
+ await container.Setup();
+ await runIters(20, container);
+ await container.SingleRequestResponseToRemoteEntity();
+ var sw = new Stopwatch();
+ MeasureProfiler.StartCollectingData();
+ for (int i = 0; i < 20; i++)
+ {
+ sw.Restart();
+ Console.WriteLine($"Try {i+1}");
+ await runIters(10000, container);
+ Console.WriteLine($"Completed {i+1} in {sw.Elapsed.TotalSeconds:F2} seconds");
+ }
+
+ MeasureProfiler.SaveData();
+ GC.KeepAlive(container);
+ return 1;
+ });
+ Console.ReadLine();
+ }
+
+ private static async Task runIters(int iters,
+ ShardMessageRoutingBenchmarks container)
+ {
+ for (int i = 0; i < iters; i++)
+ {
+ await container.SingleRequestResponseToRemoteEntity();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/benchmark/Akka.Cluster.Benchmarks/Akka.Cluster.Benchmarks.csproj b/src/benchmark/Akka.Cluster.Benchmarks/Akka.Cluster.Benchmarks.csproj
index 21ccbdcb825..a0a1b99385c 100644
--- a/src/benchmark/Akka.Cluster.Benchmarks/Akka.Cluster.Benchmarks.csproj
+++ b/src/benchmark/Akka.Cluster.Benchmarks/Akka.Cluster.Benchmarks.csproj
@@ -12,6 +12,7 @@
+
diff --git a/src/benchmark/Akka.Cluster.Benchmarks/Sharding/ShardMessageRoutingBenchmarks.cs b/src/benchmark/Akka.Cluster.Benchmarks/Sharding/ShardMessageRoutingBenchmarks.cs
index c2fc8d0ead0..baede264b2e 100644
--- a/src/benchmark/Akka.Cluster.Benchmarks/Sharding/ShardMessageRoutingBenchmarks.cs
+++ b/src/benchmark/Akka.Cluster.Benchmarks/Sharding/ShardMessageRoutingBenchmarks.cs
@@ -21,7 +21,7 @@ namespace Akka.Cluster.Benchmarks.Sharding
[Config(typeof(MonitoringConfig))]
public class ShardMessageRoutingBenchmarks
{
- [Params(StateStoreMode.Persistence, StateStoreMode.DData)]
+ [Params(StateStoreMode.Persistence)]
public StateStoreMode StateMode;
[Params(10000)]
@@ -141,14 +141,14 @@ public void PerIteration()
_batchActor = _sys1.ActorOf(Props.Create(() => new BulkSendActor(tcs, MsgCount)));
}
- [Benchmark]
+ //[Benchmark]
public async Task SingleRequestResponseToLocalEntity()
{
for (var i = 0; i < MsgCount; i++)
await _shardRegion1.Ask(_messageToSys1);
}
- [Benchmark]
+ //[Benchmark]
public async Task StreamingToLocalEntity()
{
_batchActor.Tell(new BulkSendActor.BeginSend(_messageToSys1, _shardRegion1, BatchSize));
@@ -161,16 +161,31 @@ public async Task SingleRequestResponseToRemoteEntity()
for (var i = 0; i < MsgCount; i++)
await _shardRegion1.Ask(_messageToSys2);
}
+ //[Benchmark]
+ public async Task DoubleRequestResponseToRemoteEntity()
+ {
+ for (var i = 0; i < MsgCount; i++)
+ {
+ Task[] tasks = new Task[8];
+ for (int j = 0; j < tasks.Length; j++)
+ {
+ tasks[j] =_shardRegion1.Ask(_messageToSys2);
+ }
+
+ await Task.WhenAll(tasks);
+ }
+
+ }
- [Benchmark]
+ //[Benchmark]
public async Task SingleRequestResponseToRemoteEntityWithLocalProxy()
{
for (var i = 0; i < MsgCount; i++)
await _localRouter.Ask(new SendShardedMessage(_messageToSys2.EntityId, _messageToSys2));
}
- [Benchmark]
+ //[Benchmark]
public async Task StreamingToRemoteEntity()
{
_batchActor.Tell(new BulkSendActor.BeginSend(_messageToSys2, _shardRegion1, BatchSize));
diff --git a/src/benchmark/Akka.Cluster.Benchmarks/Sharding/ShardingInfrastructure.cs b/src/benchmark/Akka.Cluster.Benchmarks/Sharding/ShardingInfrastructure.cs
index e016ea70052..bd34423c5e0 100644
--- a/src/benchmark/Akka.Cluster.Benchmarks/Sharding/ShardingInfrastructure.cs
+++ b/src/benchmark/Akka.Cluster.Benchmarks/Sharding/ShardingInfrastructure.cs
@@ -5,6 +5,7 @@
// //
// //-----------------------------------------------------------------------
+using System;
using System.Threading.Tasks;
using Akka.Actor;
using Akka.Cluster.Sharding;
@@ -223,6 +224,12 @@ public static Config CreatePersistenceConfig(bool rememberEntities = false)
var connectionString =
"Filename=file:memdb-journal-" + DbId.IncrementAndGet() + ".db;Mode=Memory;Cache=Shared";
var config = $@"
+akka.actor {{
+serializers.hyperion = ""Akka.Serialization.HyperionSerializer, Akka.Serialization.Hyperion""
+ serialization-bindings {{
+ ""System.Object"" = hyperion
+ }}
+}}
akka.actor.provider = cluster
akka.remote.dot-netty.tcp.port = 0
akka.cluster.sharding.state-store-mode=persistence
@@ -248,6 +255,12 @@ class = ""Akka.Persistence.Sqlite.Snapshot.SqliteSnapshotStore, Akka.Persistence
public static Config CreateDDataConfig(bool rememberEntities = false)
{
var config = $@"
+akka.actor {{
+serializers.hyperion = ""Akka.Serialization.HyperionSerializer, Akka.Serialization.Hyperion""
+ serialization-bindings {{
+ ""System.Object"" = hyperion
+ }}
+}}
akka.actor.provider = cluster
akka.remote.dot-netty.tcp.port = 0
akka.cluster.sharding.state-store-mode=ddata
@@ -260,7 +273,9 @@ public static IActorRef StartShardRegion(ActorSystem system, string entityName =
{
var props = Props.Create(() => new ShardedEntityActor());
var sharding = ClusterSharding.Get(system);
- return sharding.Start(entityName, s => props, ClusterShardingSettings.Create(system),
+ return sharding.Start(entityName, s => props, ClusterShardingSettings.Create(system)
+ .WithPassivateIdleAfter(TimeSpan.Zero)
+ ,
new ShardMessageExtractor());
}
}
diff --git a/src/benchmark/RemotePingPong/Program.cs b/src/benchmark/RemotePingPong/Program.cs
index 969e48315e5..421d966ea4b 100644
--- a/src/benchmark/RemotePingPong/Program.cs
+++ b/src/benchmark/RemotePingPong/Program.cs
@@ -70,7 +70,7 @@ public static Config CreateActorSystemConfig(string actorSystemName, string ipOr
private static async Task Main(params string[] args)
{
- Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;
+ //Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;
uint timesToRun;
if (args.Length == 0 || !uint.TryParse(args[0], out timesToRun))
{
diff --git a/src/benchmark/RemotePingPong/derp.cs b/src/benchmark/RemotePingPong/derp.cs
new file mode 100644
index 00000000000..f241ceedf9f
--- /dev/null
+++ b/src/benchmark/RemotePingPong/derp.cs
@@ -0,0 +1,375 @@
+// //-----------------------------------------------------------------------
+// //
+// // Copyright (C) 2009-2021 Lightbend Inc.
+// // Copyright (C) 2013-2021 .NET Foundation
+// //
+// //-----------------------------------------------------------------------
+
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+namespace Blargh
+{
+ [StructLayout(LayoutKind.Sequential)]
+ public sealed class UnfairSemaphoreV2
+ {
+ public const int MaxWorker = 0x7FFF;
+
+ private static readonly int ProcessorCount = Environment.ProcessorCount;
+
+ // We track everything we care about in a single 64-bit struct to allow us to
+ // do CompareExchanges on this for atomic updates.
+ private struct SemaphoreStateV2
+ {
+ private const byte CurrentSpinnerCountShift = 0;
+ private const byte CountForSpinnerCountShift = 16;
+ private const byte WaiterCountShift = 32;
+ private const byte CountForWaiterCountShift = 48;
+
+ //Ugh. So, Older versions of .NET,
+ //for whatever reason, don't have
+ //Interlocked compareexchange for ULong.
+ public long _data;
+
+ private SemaphoreStateV2(ulong data)
+ {
+ unchecked
+ {
+ _data = (long)data;
+ }
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void AddSpinners(ushort value)
+ {
+ Debug.Assert(value <= uint.MaxValue - Spinners);
+ unchecked
+ {
+ _data += (long)value << CurrentSpinnerCountShift;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void DecrSpinners(ushort value)
+ {
+ Debug.Assert(value >= ushort.MinValue + Spinners);
+ unchecked
+ {
+ _data -= (long)value << CurrentSpinnerCountShift;
+ }
+ }
+
+ private uint GetUInt32Value(byte shift) => (uint)(_data >> shift);
+ private void SetUInt32Value(uint value, byte shift)
+ {
+ unchecked
+ {
+ _data = (_data & ~((long)uint.MaxValue << shift)) | ((long)value << shift);
+ }
+
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private ushort GetUInt16Value(byte shift) => (ushort)(_data >> shift);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void SetUInt16Value(ushort value, byte shift)
+ {
+ unchecked
+ {
+ _data = (_data & ~((long)ushort.MaxValue << shift)) | ((long)value << shift);
+ }
+
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private byte GetByteValue(byte shift) => (byte)(_data >> shift);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void SetByteValue(byte value, byte shift)
+ {
+ unchecked
+ {
+ _data = (_data & ~((long)byte.MaxValue << shift)) | ((long)value << shift);
+ }
+
+ }
+
+
+ //how many threads are currently spin-waiting for this semaphore?
+ public ushort Spinners
+ {
+ get { return GetUInt16Value(CurrentSpinnerCountShift); }
+ //set{SetUInt16Value(value,CurrentSpinnerCountShift);}
+
+ }
+
+ //how much of the semaphore's count is available to spinners?
+ //[FieldOffset(2)]
+ public ushort CountForSpinners
+ {
+ get { return GetUInt16Value(CountForSpinnerCountShift); }
+ //set{SetUInt16Value(value,CountForSpinnerCountShift);}
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void IncrementCountForSpinners(ushort count)
+ {
+ Debug.Assert(CountForSpinners+count < ushort.MaxValue);
+ unchecked
+ {
+ _data += (long)count << CountForSpinnerCountShift;
+ }
+
+ }
+
+ public void DecrementCountForSpinners()
+ {
+ Debug.Assert(CountForSpinners != 0);
+ unchecked
+ {
+ _data -= (long)1 << CountForSpinnerCountShift;
+ }
+
+ }
+
+
+ //how many threads are blocked in the OS waiting for this semaphore?
+ public ushort Waiters
+ {
+ get { return GetUInt16Value(WaiterCountShift); }
+ //set{SetUInt16Value(value,WaiterCountShift);}
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void AddWaiters(ushort value)
+ {
+ Debug.Assert(value <= uint.MaxValue - Waiters);
+ unchecked
+ {
+ _data += (long)value << WaiterCountShift;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void DecrWaiters(ushort value)
+ {
+ Debug.Assert(value >= ushort.MinValue + Waiters);
+ unchecked
+ {
+ _data -= (long)value << WaiterCountShift;
+ }
+ }
+ //how much count is available to waiters?
+ public ushort CountForWaiters
+ {
+ get { return GetUInt16Value(CountForWaiterCountShift); }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void IncrCountForWaiters(ushort value)
+ {
+ Debug.Assert(value <= ushort.MaxValue + CountForWaiters);
+ unchecked
+ {
+ _data += (long)value << CountForWaiterCountShift;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void DecrCountForWaiters(ushort value)
+ {
+ Debug.Assert(value >= ushort.MinValue + CountForWaiters);
+ unchecked
+ {
+ _data -= (long)value << CountForWaiterCountShift;
+ }
+ }
+ }
+
+ [StructLayout(LayoutKind.Explicit, Size = 64)]
+ private struct CacheLinePadding
+ { }
+
+ private readonly Semaphore m_semaphore;
+
+ // padding to ensure we get our own cache line
+#pragma warning disable 169
+ private readonly CacheLinePadding m_padding1;
+ private SemaphoreStateV2 m_state;
+ private readonly CacheLinePadding m_padding2;
+#pragma warning restore 169
+
+ public UnfairSemaphoreV2()
+ {
+ m_semaphore = new Semaphore(0, short.MaxValue);
+ }
+
+ public bool Wait()
+ {
+ return Wait(Timeout.InfiniteTimeSpan);
+ }
+
+ public bool Wait(TimeSpan timeout)
+ {
+ while (true)
+ {
+ SemaphoreStateV2 currentCounts = GetCurrentState();
+ SemaphoreStateV2 newCounts = currentCounts;
+
+ // First, just try to grab some count.
+ if (currentCounts.CountForSpinners > 0)
+ {
+ newCounts.DecrementCountForSpinners();
+ if (TryUpdateState(newCounts, currentCounts))
+ return true;
+ }
+ else
+ {
+ // No count available, become a spinner
+ newCounts.AddSpinners(1);
+ if (TryUpdateState(newCounts, currentCounts))
+ break;
+ }
+ }
+
+ //
+ // Now we're a spinner.
+ //
+ int numSpins = 0;
+ const int spinLimitPerProcessor = 50;
+ while (true)
+ {
+ SemaphoreStateV2 currentCounts = GetCurrentState();
+ SemaphoreStateV2 newCounts = currentCounts;
+
+ if (currentCounts.CountForSpinners > 0)
+ {
+ newCounts.DecrementCountForSpinners();
+ newCounts.DecrSpinners(1);
+ if (TryUpdateState(newCounts, currentCounts))
+ return true;
+ }
+ else
+ {
+ double spinnersPerProcessor = (double)currentCounts.Spinners / ProcessorCount;
+ int spinLimit = (int)((spinLimitPerProcessor / spinnersPerProcessor) + 0.5);
+ if (numSpins >= spinLimit)
+ {
+ newCounts.DecrSpinners(1);
+ newCounts.AddWaiters(1);
+ if (TryUpdateState(newCounts, currentCounts))
+ break;
+ }
+ else
+ {
+ //
+ // We yield to other threads using Thread.Sleep(0) rather than the more traditional Thread.Yield().
+ // This is because Thread.Yield() does not yield to threads currently scheduled to run on other
+ // processors. On a 4-core machine, for example, this means that Thread.Yield() is only ~25% likely
+ // to yield to the correct thread in some scenarios.
+ // Thread.Sleep(0) has the disadvantage of not yielding to lower-priority threads. However, this is ok because
+ // once we've called this a few times we'll become a "waiter" and wait on the Semaphore, and that will
+ // yield to anything that is runnable.
+ //
+ Thread.Sleep(0);
+ numSpins++;
+ }
+ }
+ }
+
+ //
+ // Now we're a waiter
+ //
+ bool waitSucceeded = m_semaphore.WaitOne(timeout);
+
+ while (true)
+ {
+ SemaphoreStateV2 currentCounts = GetCurrentState();
+ SemaphoreStateV2 newCounts = currentCounts;
+
+ newCounts.DecrWaiters(1);
+
+ if (waitSucceeded)
+ newCounts.DecrCountForWaiters(1);
+
+ if (TryUpdateState(newCounts, currentCounts))
+ return waitSucceeded;
+ }
+ }
+
+ public void Release()
+ {
+ Release(1);
+ }
+
+ public void Release(short count)
+ {
+ while (true)
+ {
+ SemaphoreStateV2 currentState = GetCurrentState();
+ SemaphoreStateV2 newState = currentState;
+
+ ushort remainingCount = (ushort)count;
+
+ // First, prefer to release existing spinners,
+ // because a) they're hot, and b) we don't need a kernel
+ // transition to release them.
+
+ ushort spinnersToRelease = (ushort)Math.Max((short)0, Math.Min(remainingCount, (short)(currentState.Spinners - currentState.CountForSpinners)));
+ newState.IncrementCountForSpinners((ushort)spinnersToRelease);// .CountForSpinners = (ushort)(newState.CountForSpinners + spinnersToRelease);
+ remainingCount -= spinnersToRelease;
+
+ // Next, prefer to release existing waiters
+ ushort waitersToRelease = (ushort)Math.Max((short)0, Math.Min(remainingCount, (short)(currentState.Waiters - currentState.CountForWaiters)));
+ newState.IncrCountForWaiters((ushort)waitersToRelease);// .CountForWaiters = (ushort)(newState.CountForWaiters+ waitersToRelease);
+ remainingCount -= waitersToRelease;
+
+ // Finally, release any future spinners that might come our way
+ newState.IncrementCountForSpinners((ushort)remainingCount);
+
+ // Try to commit the transaction
+ if (TryUpdateState(newState, currentState))
+ {
+ // Now we need to release the waiters we promised to release
+ if (waitersToRelease > 0)
+ m_semaphore.Release(waitersToRelease);
+
+ break;
+ }
+ }
+ }
+
+ private bool TryUpdateState(SemaphoreStateV2 newState, SemaphoreStateV2 currentState)
+ {
+ if (Interlocked.CompareExchange(ref m_state._data, newState._data, currentState._data) == currentState._data)
+ {
+ Debug.Assert(newState.CountForSpinners <= MaxWorker, "CountForSpinners is greater than MaxWorker");
+ Debug.Assert(newState.CountForSpinners >= 0, "CountForSpinners is lower than zero");
+ Debug.Assert(newState.Spinners <= MaxWorker, "Spinners is greater than MaxWorker");
+ Debug.Assert(newState.Spinners >= 0, "Spinners is lower than zero");
+ Debug.Assert(newState.CountForWaiters <= MaxWorker, "CountForWaiters is greater than MaxWorker");
+ Debug.Assert(newState.CountForWaiters >= 0, "CountForWaiters is lower than zero");
+ Debug.Assert(newState.Waiters <= MaxWorker, "Waiters is greater than MaxWorker");
+ Debug.Assert(newState.Waiters >= 0, "Waiters is lower than zero");
+ Debug.Assert(newState.CountForSpinners + newState.CountForWaiters <= MaxWorker, "CountForSpinners + CountForWaiters is greater than MaxWorker");
+
+ return true;
+ }
+
+ return false;
+ }
+
+ private SemaphoreStateV2 GetCurrentState()
+ {
+ // Volatile.Read of a long can get a partial read in x86 but the invalid
+ // state will be detected in TryUpdateState with the CompareExchange.
+
+ SemaphoreStateV2 state = new SemaphoreStateV2();
+ state._data = Volatile.Read(ref m_state._data);
+ return state;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/contrib/cluster/Akka.Cluster.Sharding/Shard.cs b/src/contrib/cluster/Akka.Cluster.Sharding/Shard.cs
index 9c306d7b305..f220e08965e 100644
--- a/src/contrib/cluster/Akka.Cluster.Sharding/Shard.cs
+++ b/src/contrib/cluster/Akka.Cluster.Sharding/Shard.cs
@@ -946,20 +946,24 @@ internal static void BaseDeliverTo(this TShard shard, string id, object
internal static IActorRef GetOrCreateEntity(this TShard shard, string id, Action onCreate = null) where TShard : IShard
{
var name = Uri.EscapeDataString(id);
- var child = shard.Context.Child(name).GetOrElse(() =>
- {
- shard.Log.Debug("Starting entity [{0}] in shard [{1}]", id, shard.ShardId);
-
- var a = shard.Context.Watch(shard.Context.ActorOf(shard.EntityProps(id), name));
- shard.IdByRef = shard.IdByRef.SetItem(a, id);
- shard.RefById = shard.RefById.SetItem(id, a);
- shard.TouchLastMessageTimestamp(id);
- shard.State = new Shard.ShardState(shard.State.Entries.Add(id));
- onCreate?.Invoke(a);
- return a;
- });
-
- return child;
+ var child = shard.Context.Child(name);
+ if (!child.IsNobody()) return child;
+ return CreateEntity(shard, id, onCreate, name);
+ }
+
+ private static IActorRef CreateEntity(TShard shard, string id,
+ Action onCreate, string name) where TShard : IShard
+ {
+ shard.Log.Debug("Starting entity [{0}] in shard [{1}]", id, shard.ShardId);
+
+ var a = shard.Context.Watch(
+ shard.Context.ActorOf(shard.EntityProps(id), name));
+ shard.IdByRef = shard.IdByRef.SetItem(a, id);
+ shard.RefById = shard.RefById.SetItem(id, a);
+ shard.TouchLastMessageTimestamp(id);
+ shard.State = new Shard.ShardState(shard.State.Entries.Add(id));
+ onCreate?.Invoke(a);
+ return a;
}
internal static int TotalBufferSize(this TShard shard) where TShard : IShard =>
diff --git a/src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt b/src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt
index d2b56896a88..c8c3861b358 100644
--- a/src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt
+++ b/src/core/Akka.API.Tests/CoreAPISpec.ApproveCore.approved.txt
@@ -177,18 +177,21 @@ namespace Akka.Actor
protected ActorPath(Akka.Actor.Address address, string name) { }
protected ActorPath(Akka.Actor.ActorPath parentPath, string name, long uid) { }
public Akka.Actor.Address Address { get; }
- public abstract System.Collections.Generic.IReadOnlyList Elements { get; }
+ public int Depth { get; }
+ public System.Collections.Generic.IReadOnlyList Elements { get; }
public string Name { get; }
- public abstract Akka.Actor.ActorPath Parent { get; }
- public abstract Akka.Actor.ActorPath Root { get; }
+ public Akka.Actor.ActorPath Parent { get; }
+ [Newtonsoft.Json.JsonIgnoreAttribute()]
+ public Akka.Actor.ActorPath Root { get; }
public long Uid { get; }
public Akka.Actor.ActorPath Child(string childName) { }
- public abstract int CompareTo(Akka.Actor.ActorPath other);
+ public int CompareTo(Akka.Actor.ActorPath other) { }
public bool Equals(Akka.Actor.ActorPath other) { }
public override bool Equals(object obj) { }
public static string FormatPathElements(System.Collections.Generic.IEnumerable pathElements) { }
public override int GetHashCode() { }
public static bool IsValidPathElement(string s) { }
+ public Akka.Actor.ActorPath ParentOf(int depth) { }
public static Akka.Actor.ActorPath Parse(string path) { }
public string ToSerializationFormat() { }
public string ToSerializationFormatWithAddress(Akka.Actor.Address address) { }
@@ -199,13 +202,17 @@ namespace Akka.Actor
public string ToStringWithoutAddress() { }
public Akka.Util.ISurrogate ToSurrogate(Akka.Actor.ActorSystem system) { }
public static bool TryParse(string path, out Akka.Actor.ActorPath actorPath) { }
+ public static bool TryParse(Akka.Actor.ActorPath basePath, string absoluteUri, out Akka.Actor.ActorPath actorPath) { }
+ public static bool TryParse(Akka.Actor.ActorPath basePath, System.ReadOnlySpan absoluteUri, out Akka.Actor.ActorPath actorPath) { }
public static bool TryParseAddress(string path, out Akka.Actor.Address address) { }
- public abstract Akka.Actor.ActorPath WithUid(long uid);
+ public static bool TryParseAddress(string path, out Akka.Actor.Address address, out System.ReadOnlySpan absoluteUri) { }
+ public static bool TryParseParts(System.ReadOnlySpan path, out System.ReadOnlySpan address, out System.ReadOnlySpan absoluteUri) { }
+ public Akka.Actor.ActorPath WithUid(long uid) { }
public static Akka.Actor.ActorPath /(Akka.Actor.ActorPath path, string name) { }
public static Akka.Actor.ActorPath /(Akka.Actor.ActorPath path, System.Collections.Generic.IEnumerable name) { }
public static bool ==(Akka.Actor.ActorPath left, Akka.Actor.ActorPath right) { }
public static bool !=(Akka.Actor.ActorPath left, Akka.Actor.ActorPath right) { }
- public class Surrogate : Akka.Util.ISurrogate, System.IEquatable, System.IEquatable
+ public sealed class Surrogate : Akka.Util.ISurrogate, System.IEquatable, System.IEquatable
{
public Surrogate(string path) { }
public string Path { get; }
@@ -408,13 +415,14 @@ namespace Akka.Actor
public static Akka.Actor.Address Parse(string address) { }
public override string ToString() { }
public Akka.Util.ISurrogate ToSurrogate(Akka.Actor.ActorSystem system) { }
+ public static bool TryParse(System.ReadOnlySpan span, out Akka.Actor.Address address) { }
public Akka.Actor.Address WithHost(string host = null) { }
public Akka.Actor.Address WithPort(System.Nullable port = null) { }
public Akka.Actor.Address WithProtocol(string protocol) { }
public Akka.Actor.Address WithSystem(string system) { }
public static bool ==(Akka.Actor.Address left, Akka.Actor.Address right) { }
public static bool !=(Akka.Actor.Address left, Akka.Actor.Address right) { }
- public class AddressSurrogate : Akka.Util.ISurrogate
+ public sealed class AddressSurrogate : Akka.Util.ISurrogate
{
public AddressSurrogate() { }
public string Host { get; set; }
@@ -497,15 +505,9 @@ namespace Akka.Actor
{
public static void CancelIfNotNull(this Akka.Actor.ICancelable cancelable) { }
}
- public class ChildActorPath : Akka.Actor.ActorPath
+ public sealed class ChildActorPath : Akka.Actor.ActorPath
{
public ChildActorPath(Akka.Actor.ActorPath parentPath, string name, long uid) { }
- public override System.Collections.Generic.IReadOnlyList Elements { get; }
- public override Akka.Actor.ActorPath Parent { get; }
- public override Akka.Actor.ActorPath Root { get; }
- public override int CompareTo(Akka.Actor.ActorPath other) { }
- public override int GetHashCode() { }
- public override Akka.Actor.ActorPath WithUid(long uid) { }
}
public sealed class CoordinatedShutdown : Akka.Actor.IExtension
{
@@ -1546,15 +1548,9 @@ namespace Akka.Actor
public void SwapUnderlying(Akka.Actor.ICell cell) { }
protected override void TellInternal(object message, Akka.Actor.IActorRef sender) { }
}
- public class RootActorPath : Akka.Actor.ActorPath
+ public sealed class RootActorPath : Akka.Actor.ActorPath
{
public RootActorPath(Akka.Actor.Address address, string name = "") { }
- public override System.Collections.Generic.IReadOnlyList Elements { get; }
- public override Akka.Actor.ActorPath Parent { get; }
- [Newtonsoft.Json.JsonIgnoreAttribute()]
- public override Akka.Actor.ActorPath Root { get; }
- public override int CompareTo(Akka.Actor.ActorPath other) { }
- public override Akka.Actor.ActorPath WithUid(long uid) { }
}
[Akka.Annotations.InternalApiAttribute()]
public class RootGuardianActorRef : Akka.Actor.LocalActorRef
diff --git a/src/core/Akka.Remote.Tests/RemotingSpec.cs b/src/core/Akka.Remote.Tests/RemotingSpec.cs
index 4c3e354222b..5c36790f4fa 100644
--- a/src/core/Akka.Remote.Tests/RemotingSpec.cs
+++ b/src/core/Akka.Remote.Tests/RemotingSpec.cs
@@ -23,6 +23,7 @@
using Xunit.Abstractions;
using Nito.AsyncEx;
using ThreadLocalRandom = Akka.Util.ThreadLocalRandom;
+using Akka.Remote.Serialization;
namespace Akka.Remote.Tests
{
@@ -176,7 +177,7 @@ public async Task Remoting_must_support_Ask()
Assert.Equal("pong", msg);
Assert.IsType>(actorRef);
}
-
+
[Fact(Skip = "Racy")]
public async Task Ask_does_not_deadlock()
{
diff --git a/src/core/Akka.Remote.Tests/Serialization/LruBoundedCacheSpec.cs b/src/core/Akka.Remote.Tests/Serialization/LruBoundedCacheSpec.cs
index 9dc9eca751a..6b17e6bd9e1 100644
--- a/src/core/Akka.Remote.Tests/Serialization/LruBoundedCacheSpec.cs
+++ b/src/core/Akka.Remote.Tests/Serialization/LruBoundedCacheSpec.cs
@@ -5,6 +5,8 @@
//
//-----------------------------------------------------------------------
+using System;
+using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Akka.Remote.Serialization;
@@ -14,22 +16,55 @@
namespace Akka.Remote.Tests.Serialization
{
+ sealed class FastHashTestComparer : IEqualityComparer
+ {
+ private readonly string _hashSeed;
+
+ public FastHashTestComparer(string hashSeed = "")
+ {
+ _hashSeed = hashSeed;
+ }
+
+ public bool Equals(string x, string y)
+ {
+ return StringComparer.Ordinal.Equals(x, y);
+ }
+
+ public int GetHashCode(string k)
+ {
+ return FastHash.OfStringFast(_hashSeed != string.Empty
+ ? _hashSeed + k + _hashSeed : k);
+ }
+ }
+
+ sealed class BrokenTestComparer : IEqualityComparer
+ {
+ public bool Equals(string x, string y)
+ {
+ return StringComparer.Ordinal.Equals(x, y);
+ }
+
+ public int GetHashCode(string k)
+ {
+ return 0;
+ }
+ }
+
public class LruBoundedCacheSpec
{
private class TestCache : LruBoundedCache {
- public TestCache(int capacity, int evictAgeThreshold, string hashSeed = "") : base(capacity, evictAgeThreshold)
+ public TestCache(int capacity, int evictAgeThreshold, IEqualityComparer comparer)
+ : base(capacity, evictAgeThreshold, comparer)
{
- _hashSeed = hashSeed;
}
- private readonly string _hashSeed;
- private int _cntr = 0;
-
- protected override int Hash(string k)
+ public TestCache(int capacity, int evictAgeThreshold, string hashSeed = "")
+ : base(capacity, evictAgeThreshold, new FastHashTestComparer(hashSeed))
{
- return FastHash.OfStringFast(_hashSeed + k + _hashSeed);
}
+ private int _cntr = 0;
+
protected override string Compute(string k)
{
var id = _cntr;
@@ -71,20 +106,17 @@ public void ExpectComputedOnly(string key, string value)
private sealed class BrokenHashFunctionTestCache : TestCache
{
- public BrokenHashFunctionTestCache(int capacity, int evictAgeThreshold, string hashSeed = "") : base(capacity, evictAgeThreshold, hashSeed)
+ public BrokenHashFunctionTestCache(int capacity, int evictAgeThreshold) :
+ base(capacity, evictAgeThreshold, new BrokenTestComparer())
{
}
- protected override int Hash(string k)
- {
- return 0;
- }
}
[Fact]
public void LruBoundedCache_must_work_in_the_happy_case()
{
- var cache = new TestCache(4,4);
+ var cache = new TestCache(4, 4);
cache.ExpectComputed("A", "A:0");
cache.ExpectComputed("B", "B:1");
@@ -97,6 +129,19 @@ public void LruBoundedCache_must_work_in_the_happy_case()
cache.ExpectCached("D", "D:3");
}
+ [Fact]
+ public void LruBoundedCache_must_handle_explict_set()
+ {
+ var cache = new TestCache(4, 4);
+
+ cache.ExpectComputed("A", "A:0");
+ cache.TrySet("A", "A:1").Should().Be(true);
+ cache.Get("A").Should().Be("A:1");
+
+ cache.TrySet("B", "B:X").Should().Be(true);
+ cache.Get("B").Should().Be("B:X");
+ }
+
[Fact]
public void LruBoundedCache_must_evict_oldest_when_full()
{
@@ -237,6 +282,8 @@ public void LruBoundedCache_must_not_cache_noncacheable_values()
cache.ExpectCached("C", "C:6");
cache.ExpectCached("D", "D:7");
cache.ExpectCached("E", "E:8");
+
+ cache.TrySet("#X", "#X:13").Should().BeFalse();
}
[Fact]
diff --git a/src/core/Akka.Remote/Akka.Remote.csproj b/src/core/Akka.Remote/Akka.Remote.csproj
index 5b1f41f9715..9c27515ce9f 100644
--- a/src/core/Akka.Remote/Akka.Remote.csproj
+++ b/src/core/Akka.Remote/Akka.Remote.csproj
@@ -12,8 +12,10 @@
+
+
$(DefineConstants);RELEASE
diff --git a/src/core/Akka.Remote/Configuration/Remote.conf b/src/core/Akka.Remote/Configuration/Remote.conf
index 246a6f1ddca..7d49db69284 100644
--- a/src/core/Akka.Remote/Configuration/Remote.conf
+++ b/src/core/Akka.Remote/Configuration/Remote.conf
@@ -581,7 +581,7 @@ akka {
default-remote-dispatcher {
executor = fork-join-executor
fork-join-executor {
- parallelism-min = 2
+ parallelism-min = 4
parallelism-factor = 0.5
parallelism-max = 16
}
diff --git a/src/core/Akka.Remote/Endpoint.cs b/src/core/Akka.Remote/Endpoint.cs
index 7ae176874c7..730bcd707c2 100644
--- a/src/core/Akka.Remote/Endpoint.cs
+++ b/src/core/Akka.Remote/Endpoint.cs
@@ -1468,7 +1468,10 @@ private bool WriteSend(EndpointManager.Send send)
}
var pdu = _codec.ConstructMessage(send.Recipient.LocalAddressToUse, send.Recipient,
- this.SerializeMessage(send.Message), send.SenderOption, send.Seq, _lastAck);
+ this.SerializeMessage(send.Message), send.SenderOption, send.Seq, _lastAck,
+ _provider
+ .RefAskCache()
+ );
_remoteMetrics.LogPayloadBytes(send.Message, pdu.Length);
diff --git a/src/core/Akka.Remote/MessageSerializer.cs b/src/core/Akka.Remote/MessageSerializer.cs
index e04d36c7e57..08989f4d0e3 100644
--- a/src/core/Akka.Remote/MessageSerializer.cs
+++ b/src/core/Akka.Remote/MessageSerializer.cs
@@ -52,10 +52,12 @@ public static SerializedMessage Serialize(ExtendedActorSystem system, Address ad
if (oldInfo == null)
Akka.Serialization.Serialization.CurrentTransportInformation =
system.Provider.SerializationInformation;
-
+
+
var serializedMsg = new SerializedMessage
{
- Message = ByteString.CopyFrom(serializer.ToBinary(message)),
+ Message = UnsafeByteOperations.UnsafeWrap(serializer.ToBinary(message)),
+ //Message = ByteString.CopyFrom(serializer.ToBinary(message)),
SerializerId = serializer.Identifier
};
diff --git a/src/core/Akka.Remote/RemoteActorRefProvider.cs b/src/core/Akka.Remote/RemoteActorRefProvider.cs
index 9014b35857f..c62fa1ed3b6 100644
--- a/src/core/Akka.Remote/RemoteActorRefProvider.cs
+++ b/src/core/Akka.Remote/RemoteActorRefProvider.cs
@@ -72,14 +72,14 @@ public interface IRemoteActorRefProvider : IActorRefProvider
/// TBD
/// TBD
/// TBD
- IInternalActorRef ResolveActorRefWithLocalAddress(string path, Address localAddress);
+ IInternalActorRef ResolveActorRefWithLocalAddress(string path, Address localAddress, bool? senderOption);
///
/// INTERNAL API: this is used by the via the public
/// method.
///
/// The path of the actor we intend to resolve.
- /// An if a match was found. Otherwise nobody.
+ /// An if a match was found. Otherwise deadletters.
IActorRef InternalResolveActorRef(string path);
///
@@ -99,6 +99,41 @@ public interface IRemoteActorRefProvider : IActorRefProvider
/// the current endpoint writer will be stopped (dropping system messages) and the address will be gated
///
void Quarantine(Address address, int? uid);
+
+ IActorRef RefAskCache();
+ ActorRefAskResolverCache RefAskCacheInst();
+ }
+
+ public class RemoteAskCacheActor : ActorBase
+ {
+ private ActorRefAskResolverCache _cache;
+
+ public RemoteAskCacheActor(ActorRefAskResolverCache cache)
+ {
+ _cache = cache;
+ }
+ protected override bool Receive(object message)
+ {
+ if (message is CacheAdd m)
+ {
+ try
+ {
+ _cache.Set(m.key, m.value);
+ }
+ catch
+ {
+ }
+
+ return true;
+ }
+ return false;
+ }
+ }
+
+ public class CacheAdd
+ {
+ public string key { get; set; }
+ public IActorRef value { get; set; }
}
///
@@ -128,7 +163,7 @@ public RemoteActorRefProvider(string systemName, Settings settings, EventStream
}
private readonly LocalActorRefProvider _local;
- private volatile Internals _internals;
+ private Internals _internals;
private ActorSystemImpl _system;
private Internals RemoteInternals
@@ -235,37 +270,46 @@ public void UnregisterTempActor(ActorPath path)
_local.UnregisterTempActor(path);
}
- private volatile IActorRef _remotingTerminator;
- private volatile IActorRef _remoteWatcher;
+ private IActorRef _remotingTerminator;
+ private IActorRef _remoteWatcher;
- private volatile ActorRefResolveThreadLocalCache _actorRefResolveThreadLocalCache;
- private volatile ActorPathThreadLocalCache _actorPathThreadLocalCache;
+ private ActorRefResolveThreadLocalCache _actorRefResolveThreadLocalCache;
+ private ActorRefResolveAskCache _actorRefResolveAskCache;
+ private ActorPathThreadLocalCache _actorPathThreadLocalCache;
+ //private ActorPathAskResolverCache _actorPathAskResolverCache;
///
/// The remote death watcher.
///
public IActorRef RemoteWatcher => _remoteWatcher;
- private volatile IActorRef _remoteDeploymentWatcher;
+ private IActorRef _remoteDeploymentWatcher;
+ private IActorRef _remoteAskHandler;
+ public IActorRef RefAskCache() => _remoteAskHandler;
+ public ActorRefAskResolverCache RefAskCacheInst() => _actorRefResolveAskCache.Cache;
///
public virtual void Init(ActorSystemImpl system)
{
_system = system;
- _local.Init(system);
-
_actorRefResolveThreadLocalCache = ActorRefResolveThreadLocalCache.For(system);
_actorPathThreadLocalCache = ActorPathThreadLocalCache.For(system);
+ _actorRefResolveAskCache = ActorRefResolveAskCache.For(system);
+ //_actorPathAskResolverCache = ActorPathAskResolverCache.For(system);
+ _local.Init(system);
_remotingTerminator =
_system.SystemActorOf(
RemoteSettings.ConfigureDispatcher(Props.Create(() => new RemotingTerminator(_local.SystemGuardian))),
"remoting-terminator");
- _internals = CreateInternals();
+ _internals = CreateInternals();
_remotingTerminator.Tell(RemoteInternals);
-
+ _remoteAskHandler = _system.SystemActorOf(
+ RemoteSettings.ConfigureDispatcher(Props.Create(() =>
+ new RemoteAskCacheActor(_actorRefResolveAskCache.Cache))),
+ "remoting-ask-cache");
Transport.Start();
_remoteWatcher = CreateRemoteWatcher(system);
_remoteDeploymentWatcher = CreateRemoteDeploymentWatcher(system);
@@ -433,9 +477,10 @@ public Deploy LookUpRemotes(IEnumerable p)
return Deploy.None;
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool HasAddress(Address address)
{
- return address.Equals(_local.RootPath.Address) || address.Equals(RootPath.Address) || Transport.Addresses.Contains(address);
+ return address.Equals(RootPath.Address) || Transport.Addresses.Contains(address);
}
///
@@ -458,21 +503,6 @@ private IInternalActorRef LocalActorOf(ActorSystemImpl system, Props props, IInt
return _local.ActorOf(system, props, supervisor, path, systemService, deploy, lookupDeploy, async);
}
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private bool TryParseCachedPath(string actorPath, out ActorPath path)
- {
- if (_actorPathThreadLocalCache != null)
- {
- path = _actorPathThreadLocalCache.Cache.GetOrCompute(actorPath);
- return path != null;
- }
- else // cache not initialized yet
- {
- return ActorPath.TryParse(actorPath, out path);
- }
- }
-
-
///
/// INTERNAL API.
///
@@ -481,22 +511,67 @@ private bool TryParseCachedPath(string actorPath, out ActorPath path)
/// TBD
/// TBD
/// TBD
- public IInternalActorRef ResolveActorRefWithLocalAddress(string path, Address localAddress)
+ public IInternalActorRef ResolveActorRefWithLocalAddress(string path, Address localAddress, bool? senderOption = null)
+ {
+ if (path is null)
+ {
+ _log.Debug("resolve of unknown path [{0}] failed", path);
+ return InternalDeadLetters;
+ }
+
+ bool isTempActor = false;
+ //bool mayBeTempActor = path.Contains("/temp/");
+ ActorPath actorPath = null;
+ if (_actorPathThreadLocalCache != null && _actorRefResolveAskCache != null)
+ {
+ if (senderOption.HasValue && senderOption.Value == true)
+ {
+ actorPath = _actorPathThreadLocalCache.Cache.GetOrCompute(path, out isTempActor);
+ }
+ else
+ {
+ actorPath = ExtractRecipientActorPath(path, out isTempActor);
+ }
+ }
+ else // cache not initialized yet
+ {
+ ActorPath.TryParse(path, out actorPath);
+ }
+
+ if (!HasAddress(actorPath.Address))
+ return CreateRemoteRef(actorPath, localAddress);
+
+ //the actor's local address was already included in the ActorPath
+
+ if (actorPath is RootActorPath)
+ return RootGuardian;
+
+ return (IInternalActorRef)ResolveActorRefOpt(path, isTempActor); // so we can use caching
+ }
+
+ private ActorPath ExtractRecipientActorPath(string path, out bool mayBeTempActor)
{
- if (TryParseCachedPath(path, out var actorPath))
+ ActorPath actorPath = null;
+ mayBeTempActor = false;
+ //if (mayBeTempActor)
{
- //the actor's local address was already included in the ActorPath
- if (HasAddress(actorPath.Address))
+ var maybeRef = _actorRefResolveAskCache.Cache.GetOrNull(path);
+ if (maybeRef != null)
{
- if (actorPath is RootActorPath)
- return RootGuardian;
- return (IInternalActorRef)ResolveActorRef(path); // so we can use caching
+ actorPath = maybeRef.Path;
+ mayBeTempActor = true;
}
+ //actorPath =
+ // _actorPathAskResolverCache.Cache.GetOrNull(path);
+ }
- return CreateRemoteRef(new RootActorPath(actorPath.Address) / actorPath.ElementsWithUid, localAddress);
+ if (actorPath == null)
+ {
+ actorPath =
+ _actorPathThreadLocalCache.Cache.GetOrCompute(path, out mayBeTempActor);
}
- _log.Debug("resolve of unknown path [{0}] failed", path);
- return InternalDeadLetters;
+
+ return actorPath;
}
@@ -531,17 +606,29 @@ protected virtual IInternalActorRef CreateRemoteRef(Props props, IInternalActorR
/// The path of the actor we are attempting to resolve.
/// A local if it exists, otherwise.
public IActorRef ResolveActorRef(string path)
+ {
+ return ResolveActorRefOpt(path, true);
+ }
+
+ private IActorRef ResolveActorRefOpt(string path, bool checkAsk)
{
if (IgnoreActorRef.IsIgnoreRefPath(path))
return IgnoreRef;
// using thread local LRU cache, which will call InternalResolveActorRef
// if the value is not cached
- if (_actorRefResolveThreadLocalCache == null)
+ if (_actorRefResolveThreadLocalCache == null || _actorRefResolveAskCache == null)
{
- return InternalResolveActorRef(path); // cache not initialized yet
+ // cache not initialized yet, should never happen
+ return InternalResolveActorRef(path);
}
- return _actorRefResolveThreadLocalCache.Cache.GetOrCompute(path);
+
+ IActorRef actorRef = null;
+ if (checkAsk)
+ {
+ actorRef = _actorRefResolveAskCache.Cache.GetOrNull(path);
+ }
+ return actorRef??_actorRefResolveThreadLocalCache.Cache.GetOrCompute(path);
}
///
@@ -592,19 +679,20 @@ public IActorRef ResolveActorRef(ActorPath actorPath)
/// The remote Address, if applicable. If not applicable null may be returned.
public Address GetExternalAddressFor(Address address)
{
- if (HasAddress(address)) { return _local.RootPath.Address; }
- if (!string.IsNullOrEmpty(address.Host) && address.Port.HasValue)
+ if (HasAddress(address))
+ return _local.RootPath.Address;
+
+ if (string.IsNullOrEmpty(address.Host) || !address.Port.HasValue)
+ return null;
+
+ try
{
- try
- {
- return Transport.LocalAddressForRemote(address);
- }
- catch
- {
- return null;
- }
+ return Transport.LocalAddressForRemote(address);
+ }
+ catch
+ {
+ return null;
}
- return null;
}
///
diff --git a/src/core/Akka.Remote/RemoteSystemDaemon.cs b/src/core/Akka.Remote/RemoteSystemDaemon.cs
index 2e5fce15820..d1dc349ccab 100644
--- a/src/core/Akka.Remote/RemoteSystemDaemon.cs
+++ b/src/core/Akka.Remote/RemoteSystemDaemon.cs
@@ -28,7 +28,7 @@ internal interface IDaemonMsg { }
///
/// INTERNAL API
///
- internal class DaemonMsgCreate : IDaemonMsg
+ internal sealed class DaemonMsgCreate : IDaemonMsg
{
///
/// Initializes a new instance of the class.
@@ -77,7 +77,7 @@ public DaemonMsgCreate(Props props, Deploy deploy, string path, IActorRef superv
///
/// It acts as the brain of the remote that responds to system remote messages and executes actions accordingly.
///
- internal class RemoteSystemDaemon : VirtualPathContainer
+ internal sealed class RemoteSystemDaemon : VirtualPathContainer
{
private readonly ActorSystemImpl _system;
private readonly Switch _terminating = new Switch(false);
diff --git a/src/core/Akka.Remote/RemoteTransport.cs b/src/core/Akka.Remote/RemoteTransport.cs
index 2798544a919..f4e1bcd0e94 100644
--- a/src/core/Akka.Remote/RemoteTransport.cs
+++ b/src/core/Akka.Remote/RemoteTransport.cs
@@ -12,6 +12,7 @@
using Akka.Actor;
using Akka.Annotations;
using Akka.Event;
+using LanguageExt;
namespace Akka.Remote
{
@@ -51,7 +52,8 @@ protected RemoteTransport(ExtendedActorSystem system, RemoteActorRefProvider pro
///
/// Addresses to be used in of refs generated for this transport.
///
- public abstract ISet Addresses { get; }
+ //public abstract ISet Addresses { get; }
+ public abstract HashSet Addresses { get; }
///
/// The default transport address of the .
diff --git a/src/core/Akka.Remote/Remoting.cs b/src/core/Akka.Remote/Remoting.cs
index 25e4eb6cdad..816b8b5b29e 100644
--- a/src/core/Akka.Remote/Remoting.cs
+++ b/src/core/Akka.Remote/Remoting.cs
@@ -16,6 +16,8 @@
using Akka.Remote.Transport;
using Akka.Util.Internal;
using Akka.Configuration;
+using LanguageExt;
+using LanguageExt.TypeClasses;
namespace Akka.Remote
{
@@ -108,18 +110,49 @@ public static RARP For(ActorSystem system)
///
internal interface IPriorityMessage { }
+ public struct AddressEq : Eq
+ {
+ public int GetHashCode(Address x)
+ {
+ return x.GetHashCode();
+ }
+
+ public bool Equals(Address x, Address y)
+ {
+ if (x != null)
+ return x.Equals(y);
+ return (y == null);
+ }
+ }
+ internal sealed class AddressEqualityComparer : EqualityComparer
+ {
+ public static readonly AddressEqualityComparer Instance =
+ new AddressEqualityComparer();
+ public override bool Equals(Address x, Address y)
+ {
+ if (x != null)
+ return x.Equals(y);
+ return (y == null);
+ }
+
+ public override int GetHashCode(Address obj)
+ {
+ return obj.GetHashCode();
+ }
+ }
///
/// INTERNAL API
///
- internal class Remoting : RemoteTransport
+ internal sealed class Remoting : RemoteTransport
{
private readonly ILoggingAdapter _log;
- private volatile IDictionary> _transportMapping;
+ private volatile IDictionary> _transportMapping;
private volatile IActorRef _endpointManager;
// This is effectively a write-once variable similar to a lazy val. The reason for not using a lazy val is exception
// handling.
- private volatile HashSet _addresses;
+ //private volatile HashSet _addresses;
+ private LanguageExt.HashSet _addresses;
// This variable has the same semantics as the addresses variable, in the sense it is written once, and emulates
// a lazy val
@@ -146,7 +179,8 @@ public Remoting(ExtendedActorSystem system, RemoteActorRefProvider provider)
///
/// TBD
///
- public override ISet Addresses
+ public override HashSet Addresses
+ //public override ISet Addresses
{
get { return _addresses; }
}
@@ -196,19 +230,26 @@ public override void Start()
var akkaProtocolTransports = addressPromise.Task.Result;
if(akkaProtocolTransports.Count==0)
throw new ConfigurationException(@"No transports enabled under ""akka.remote.enabled-transports""");
- _addresses = new HashSet(akkaProtocolTransports.Select(a => a.Address));
+
+ _addresses =
+ _addresses.AddRange(
+ akkaProtocolTransports.Select(a => a.Address));
+ //new System.Collections.Generic.HashSet(akkaProtocolTransports.Select(a => a.Address), AddressEqualityComparer.Instance);
IEnumerable> tmp =
akkaProtocolTransports.GroupBy(t => t.ProtocolTransport.SchemeIdentifier);
- _transportMapping = new Dictionary>();
+ _transportMapping = new Dictionary>();
foreach (var g in tmp)
{
- var set = new HashSet(g);
+ var set = new System.Collections.Generic.HashSet(g);
_transportMapping.Add(g.Key, set);
}
_defaultAddress = akkaProtocolTransports.Head().Address;
- _addresses = new HashSet(akkaProtocolTransports.Select(x => x.Address));
+ _addresses =
+ new HashSet().AddRange(
+ akkaProtocolTransports.Select(x => x.Address));
+ //_addresses = new System.Collections.Generic.HashSet(akkaProtocolTransports.Select(x => x.Address), AddressEqualityComparer.Instance);
_log.Info("Remoting started; listening on addresses : [{0}]", string.Join(",", _addresses.Select(x => x.ToString())));
@@ -375,7 +416,7 @@ private void NotifyError(string msg, Exception cause)
/// TBD
/// TBD
internal static Address LocalAddressForRemote(
- IDictionary> transportMapping, Address remote)
+ IDictionary> transportMapping, Address remote)
{
if (transportMapping.TryGetValue(remote.Protocol, out var transports))
{
diff --git a/src/core/Akka.Remote/Serialization/ActorPathCache.cs b/src/core/Akka.Remote/Serialization/ActorPathCache.cs
index f837bb0148d..2d2a2b30e6a 100644
--- a/src/core/Akka.Remote/Serialization/ActorPathCache.cs
+++ b/src/core/Akka.Remote/Serialization/ActorPathCache.cs
@@ -8,6 +8,8 @@
using System;
using Akka.Actor;
using System.Threading;
+using System.Collections.Generic;
+using Akka.Remote.Serialization.BitFasterBased;
namespace Akka.Remote.Serialization
{
@@ -16,10 +18,13 @@ namespace Akka.Remote.Serialization
///
internal sealed class ActorPathThreadLocalCache : ExtensionIdProvider, IExtension
{
- private readonly ThreadLocal _current = new ThreadLocal(() => new ActorPathCache());
+ //private readonly ThreadLocal _current = new ThreadLocal(() => new ActorPathCache());
- public ActorPathCache Cache => _current.Value;
+ private readonly ActorPathBitfasterCache _current =
+ new ActorPathBitfasterCache();
+ //public ActorPathCache Cache => _current.Value;
+ public ActorPathBitfasterCache Cache => _current;
public override ActorPathThreadLocalCache CreateExtension(ExtendedActorSystem system)
{
return new ActorPathThreadLocalCache();
@@ -30,26 +35,173 @@ public static ActorPathThreadLocalCache For(ActorSystem system)
return system.WithExtension();
}
}
+ internal sealed class ActorPathAskResolverCache : ExtensionIdProvider, IExtension
+ {
+ //private readonly ThreadLocal _current = new ThreadLocal(() => new ActorPathCache());
+
+ private readonly ActorPathAskCache _current =
+ new ActorPathAskCache();
+
+ //public ActorPathCache Cache => _current.Value;
+ public ActorPathAskCache Cache => _current;
+ public override ActorPathAskResolverCache CreateExtension(ExtendedActorSystem system)
+ {
+ return new ActorPathAskResolverCache();
+ }
+
+ public static ActorPathAskResolverCache For(ActorSystem system)
+ {
+ return system.WithExtension();
+ }
+ }
+ public class ActorPathAskCache
+ {
+ private readonly FastConcurrentLru _cache =
+ new FastConcurrentLru(Environment.ProcessorCount,
+ 1030, FastHashComparer.Default);
+
+ public ActorPath GetOrNull(string actorPath)
+ {
+ if (_cache.TryGet(actorPath, out ActorPath askRef))
+ {
+ return askRef;
+ }
+
+ return null;
+ }
+
+ public void Set(string actorPath, ActorPath actorPathObj)
+ {
+ _cache.TryAdd(actorPath,actorPathObj);
+ }
+
+ }
+
+ internal sealed class ActorPathBitfasterCache
+ {
+ public readonly FastConcurrentLru _cache =
+ new FastConcurrentLru(Environment.ProcessorCount,
+ 1030, FastHashComparer.Default);
+ public readonly FastConcurrentLru _rootCache =
+ new FastConcurrentLru(Environment.ProcessorCount,
+ 540, FastHashComparer.Default);
+ public ActorPath GetOrCompute(string k, out bool isTempActor)
+ {
+ //if (mayBeTempActor)
+ //{
+ // return ParsePath(k);
+ //}
+ if (_cache.TryGet(k, out ActorPath outPath))
+ {
+ isTempActor = true;
+ return outPath;
+ }
+
+ outPath = ParsePath(k);
+
+ isTempActor = k.Contains("/temp/");
+ if (outPath != null && !isTempActor)
+ {
+
+ _cache.TryAdd(k,outPath);
+ }
+ return outPath;
+ }
+
+ private ActorPath ParsePath(string k)
+ {
+ var path = k.AsSpan();
+
+ if (!ActorPath.TryParseParts(path, out var addressSpan, out var absoluteUri)
+ )
+ return null;
+
+
+ string rootPath;
+ if (absoluteUri.Length > 1 || path.Length > addressSpan.Length)
+ {
+ //path end with /
+ rootPath = path.Slice(0, addressSpan.Length + 1).ToString();
+ }
+ else
+ {
+ //todo replace with string.create
+ Span buffer = addressSpan.Length < 1024
+ ? stackalloc char[addressSpan.Length + 1]
+ : new char[addressSpan.Length + 1];
+ path.Slice(0, addressSpan.Length).CopyTo(buffer);
+ buffer[buffer.Length - 1] = '/';
+ rootPath = buffer.ToString();
+ }
+
+ //try lookup root in cache
+ if (!_rootCache.TryGet(rootPath, out var actorPath))
+ {
+ if (!Address.TryParse(addressSpan, out var address))
+ return null;
+
+ actorPath = new RootActorPath(address);
+ _rootCache.TryAdd(rootPath, actorPath);
+ }
+
+ if (!ActorPath.TryParse(actorPath, absoluteUri, out actorPath))//, out bool mayBeTemp))
+ return null;
+
+ return actorPath;
+ }
+ }
///
/// INTERNAL API
///
internal sealed class ActorPathCache : LruBoundedCache
{
- public ActorPathCache(int capacity = 1024, int evictAgeThreshold = 600) : base(capacity, evictAgeThreshold)
+ public ActorPathCache(int capacity = 1024, int evictAgeThreshold = 600)
+ : base(capacity, evictAgeThreshold, FastHashComparer.Default)
{
}
- protected override int Hash(string k)
- {
- return FastHash.OfStringFast(k);
- }
-
protected override ActorPath Compute(string k)
{
- if (ActorPath.TryParse(k, out var actorPath))
- return actorPath;
- return null;
+ ActorPath actorPath;
+
+ var path = k.AsSpan();
+
+ if (!ActorPath.TryParseParts(path, out var addressSpan, out var absoluteUri))
+ return null;
+
+
+ string rootPath;
+ if(absoluteUri.Length > 1 || path.Length > addressSpan.Length)
+ {
+ //path end with /
+ rootPath = path.Slice(0, addressSpan.Length + 1).ToString();
+ }
+ else
+ {
+ //todo replace with string.create
+ Span buffer = addressSpan.Length < 1024
+ ? stackalloc char[addressSpan.Length + 1]
+ : new char[addressSpan.Length + 1];
+ path.Slice(0, addressSpan.Length).CopyTo(buffer);
+ buffer[buffer.Length - 1] = '/';
+ rootPath = buffer.ToString();
+ }
+
+ //try lookup root in cache
+ if (!TryGet(rootPath, out actorPath))
+ {
+ if (!Address.TryParse(addressSpan, out var address))
+ return null;
+
+ actorPath = new RootActorPath(address);
+ TrySet(rootPath, actorPath);
+ }
+
+ if (!ActorPath.TryParse(actorPath, absoluteUri, out actorPath))//, out bool isTemp))
+ return null;
+
+ return actorPath;
}
protected override bool IsCacheable(ActorPath v)
diff --git a/src/core/Akka.Remote/Serialization/ActorRefResolveCache.cs b/src/core/Akka.Remote/Serialization/ActorRefResolveCache.cs
index 2bd7de5b453..28116df2894 100644
--- a/src/core/Akka.Remote/Serialization/ActorRefResolveCache.cs
+++ b/src/core/Akka.Remote/Serialization/ActorRefResolveCache.cs
@@ -5,8 +5,11 @@
//
//-----------------------------------------------------------------------
+using System;
+using System.Collections.Generic;
using System.Threading;
using Akka.Actor;
+using Akka.Remote.Serialization.BitFasterBased;
using Akka.Util.Internal;
namespace Akka.Remote.Serialization
@@ -23,7 +26,8 @@ public ActorRefResolveThreadLocalCache() { }
public ActorRefResolveThreadLocalCache(IRemoteActorRefProvider provider)
{
_provider = provider;
- _current = new ThreadLocal(() => new ActorRefResolveCache(_provider));
+ //_current = new ThreadLocal(() => new ActorRefResolveCache(_provider));
+ _current = new ActorRefResolveBitfasterCache(_provider);
}
public override ActorRefResolveThreadLocalCache CreateExtension(ExtendedActorSystem system)
@@ -31,15 +35,98 @@ public override ActorRefResolveThreadLocalCache CreateExtension(ExtendedActorSys
return new ActorRefResolveThreadLocalCache((IRemoteActorRefProvider)system.Provider);
}
- private readonly ThreadLocal _current;
+ //private readonly ThreadLocal _current;
+ private readonly ActorRefResolveBitfasterCache _current;
- public ActorRefResolveCache Cache => _current.Value;
+ //public ActorRefResolveCache Cache => _current.Value;
+ public ActorRefResolveBitfasterCache Cache => _current;
public static ActorRefResolveThreadLocalCache For(ActorSystem system)
{
return system.WithExtension();
}
}
+
+ ///
+ /// INTERNAL API
+ ///
+ internal sealed class ActorRefResolveAskCache : ExtensionIdProvider, IExtension
+ {
+ private readonly IRemoteActorRefProvider _provider;
+
+ public ActorRefResolveAskCache()
+ {
+ //_current = new ThreadLocal(() => new ActorRefResolveCache(_provider));
+ _current = new ActorRefAskResolverCache();
+ }
+
+ public override ActorRefResolveAskCache CreateExtension(ExtendedActorSystem system)
+ {
+ return new ActorRefResolveAskCache();
+ }
+
+ //private readonly ThreadLocal _current;
+ private readonly ActorRefAskResolverCache _current;
+
+ //public ActorRefResolveCache Cache => _current.Value;
+ public ActorRefAskResolverCache Cache => _current;
+
+ public static ActorRefResolveAskCache For(ActorSystem system)
+ {
+ return system.WithExtension();
+ }
+ }
+
+ public class ActorRefAskResolverCache
+ {
+ private readonly FastConcurrentLru _cache =
+ new FastConcurrentLru(Environment.ProcessorCount,
+ 1030, FastHashComparer.Default);
+
+ public IActorRef GetOrNull(string actorPath)
+ {
+ if (_cache.TryGet(actorPath, out IActorRef askRef))
+ {
+ return askRef;
+ }
+
+ return null;
+ }
+
+ public void Set(string actorPath, IActorRef actorRef)
+ {
+ _cache.TryAdd(actorPath,actorRef);
+ }
+
+ }
+
+ public class ActorRefResolveBitfasterCache
+ {
+ private readonly IRemoteActorRefProvider _provider;
+
+ private readonly FastConcurrentLru _cache =
+ new FastConcurrentLru(Environment.ProcessorCount,
+ 1030, FastHashComparer.Default);
+ public ActorRefResolveBitfasterCache(IRemoteActorRefProvider provider)
+ {
+ _provider = provider;
+ }
+
+ public IActorRef GetOrCompute(string k)
+ {
+ if (_cache.TryGet(k, out IActorRef outRef))
+ {
+ return outRef;
+ }
+ outRef= _provider.InternalResolveActorRef(k);
+ if (!(outRef is MinimalActorRef && !(outRef is FunctionRef)))
+ {
+ _cache.TryAdd(k, outRef);
+ }
+
+ return outRef;
+ }
+ }
///
/// INTERNAL API
@@ -48,7 +135,8 @@ internal sealed class ActorRefResolveCache : LruBoundedCache
{
private readonly IRemoteActorRefProvider _provider;
- public ActorRefResolveCache(IRemoteActorRefProvider provider, int capacity = 1024, int evictAgeThreshold = 600) : base(capacity, evictAgeThreshold)
+ public ActorRefResolveCache(IRemoteActorRefProvider provider, int capacity = 1024, int evictAgeThreshold = 600)
+ : base(capacity, evictAgeThreshold, FastHashComparer.Default)
{
_provider = provider;
}
@@ -58,11 +146,6 @@ protected override IActorRef Compute(string k)
return _provider.InternalResolveActorRef(k);
}
- protected override int Hash(string k)
- {
- return FastHash.OfStringFast(k);
- }
-
protected override bool IsCacheable(IActorRef v)
{
// don't cache any FutureActorRefs, et al
diff --git a/src/core/Akka.Remote/Serialization/AddressCache.cs b/src/core/Akka.Remote/Serialization/AddressCache.cs
index cfacfe58b32..a80e4ce49eb 100644
--- a/src/core/Akka.Remote/Serialization/AddressCache.cs
+++ b/src/core/Akka.Remote/Serialization/AddressCache.cs
@@ -41,15 +41,11 @@ public static AddressThreadLocalCache For(ActorSystem system)
///
internal sealed class AddressCache : LruBoundedCache
{
- public AddressCache(int capacity = 1024, int evictAgeThreshold = 600) : base(capacity, evictAgeThreshold)
+ public AddressCache(int capacity = 1024, int evictAgeThreshold = 600)
+ : base(capacity, evictAgeThreshold, FastHashComparer.Default)
{
}
- protected override int Hash(string k)
- {
- return FastHash.OfStringFast(k);
- }
-
protected override Address Compute(string k)
{
Address addr;
diff --git a/src/core/Akka.Remote/Serialization/BitFasterBased/LruWithOptimizedRemove.cs b/src/core/Akka.Remote/Serialization/BitFasterBased/LruWithOptimizedRemove.cs
new file mode 100644
index 00000000000..b0909c08de1
--- /dev/null
+++ b/src/core/Akka.Remote/Serialization/BitFasterBased/LruWithOptimizedRemove.cs
@@ -0,0 +1,679 @@
+// //-----------------------------------------------------------------------
+// //
+// // Copyright (C) 2009-2021 Lightbend Inc.
+// // Copyright (C) 2013-2021 .NET Foundation
+// //
+// //-----------------------------------------------------------------------
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using BitFaster.Caching;
+using BitFaster.Caching.Lru;
+
+namespace Akka.Remote.Serialization.BitFasterBased
+{
+ public interface IPolicy where I : LruItem
+ {
+ I CreateItem(K key, V value);
+
+ void Touch(I item);
+
+ bool ShouldDiscard(I item);
+
+ ItemDestination RouteHot(I item);
+
+ ItemDestination RouteWarm(I item);
+
+ ItemDestination RouteCold(I item);
+ }
+ ///
+ /// Discards the least recently used items first.
+ ///
+ public readonly struct LruPolicy : IPolicy>
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public LruItem CreateItem(K key, V value)
+ {
+ return new LruItem(key, value);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Touch(LruItem item)
+ {
+ item.SetAccessed();
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool ShouldDiscard(LruItem item)
+ {
+ return false;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ItemDestination RouteHot(LruItem item)
+ {
+ if (item.WasAccessed)
+ {
+ return ItemDestination.Warm;
+ }
+
+ return ItemDestination.Cold;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ItemDestination RouteWarm(LruItem item)
+ {
+ if (item.WasAccessed)
+ {
+ return ItemDestination.Warm;
+ }
+
+ return ItemDestination.Cold;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ItemDestination RouteCold(LruItem item)
+ {
+ if (item.WasAccessed)
+ {
+ return ItemDestination.Warm;
+ }
+
+ return ItemDestination.Remove;
+ }
+ }
+
+ [Flags]
+ public enum LruItemStatus
+ {
+ WasRemoved = 1,
+ WasAccessed = 2,
+ ShouldToss = 4
+ }
+ public class LruItem
+ {
+ private volatile LruItemStatus _status;
+ //private volatile bool wasAccessed;
+ //private volatile bool wasRemoved;
+
+ public LruItem(K k, V v)
+ {
+ this.Key = k;
+ this.Value = v;
+ }
+
+ public readonly K Key;
+
+ public V Value { get; set; }
+
+ public bool WasAccessed
+ {
+ get => (this._status & LruItemStatus.WasAccessed) == LruItemStatus.WasAccessed;
+ //set => this._status = this._status & LruItemStatus.WasAccessed;
+ }
+
+ public void SetAccessed()
+ {
+ this._status = this._status & LruItemStatus.WasAccessed;
+ }
+
+ public void SetUnaccessed()
+ {
+ this._status = (this._status & (LruItemStatus)int.MaxValue -
+ (int)LruItemStatus.WasAccessed);
+ }
+
+ public bool WasRemoved
+ {
+ get => (this._status & LruItemStatus.WasRemoved) == LruItemStatus.WasRemoved;
+
+ }
+
+ public void SetRemoved()
+ {
+ this._status = this._status & LruItemStatus.WasRemoved;
+ }
+
+ public bool ShouldFastDiscard
+ {
+ get => (this._status & LruItemStatus.ShouldToss) ==
+ LruItemStatus.ShouldToss;
+ set => this._status = this._status & LruItemStatus.ShouldToss;
+ }
+ }
+ ///
+ public sealed class FastConcurrentLru : TemplateConcurrentLru
+ {
+ ///
+ /// Initializes a new instance of the FastConcurrentLru class with the specified capacity that has the default
+ /// concurrency level, and uses the default comparer for the key type.
+ ///
+ /// The maximum number of elements that the FastConcurrentLru can contain.
+ public FastConcurrentLru(int capacity)
+ : base(Environment.ProcessorCount, capacity, EqualityComparer.Default, new LruPolicy(), new NullHitCounter())
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the FastConcurrentLru class that has the specified concurrency level, has the
+ /// specified initial capacity, and uses the specified IEqualityComparer.
+ ///
+ /// The estimated number of threads that will update the FastConcurrentLru concurrently.
+ /// The maximum number of elements that the FastConcurrentLru can contain.
+ /// The IEqualityComparer implementation to use when comparing keys.
+ public FastConcurrentLru(int concurrencyLevel, int capacity, IEqualityComparer comparer)
+ : base(concurrencyLevel, capacity, comparer, new LruPolicy(), new NullHitCounter())
+ {
+ }
+ }
+ ///
+ /// Pseudo LRU implementation where LRU list is composed of 3 segments: hot, warm and cold. Cost of maintaining
+ /// segments is amortized across requests. Items are only cycled when capacity is exceeded. Pure read does
+ /// not cycle items if all segments are within capacity constraints.
+ /// There are no global locks. On cache miss, a new item is added. Tail items in each segment are dequeued,
+ /// examined, and are either enqueued or discarded.
+ /// This scheme of hot, warm and cold is based on the implementation used in MemCached described online here:
+ /// https://memcached.org/blog/modern-lru/
+ ///
+ ///
+ /// Each segment has a capacity. When segment capacity is exceeded, items are moved as follows:
+ /// 1. New items are added to hot, WasAccessed = false
+ /// 2. When items are accessed, update WasAccessed = true
+ /// 3. When items are moved WasAccessed is set to false.
+ /// 4. When hot is full, hot tail is moved to either Warm or Cold depending on WasAccessed.
+ /// 5. When warm is full, warm tail is moved to warm head or cold depending on WasAccessed.
+ /// 6. When cold is full, cold tail is moved to warm head or removed from dictionary on depending on WasAccessed.
+ ///
+ public class TemplateConcurrentLru : ICache
+ where H : struct, IHitCounter
+ {
+ private readonly ConcurrentDictionary> dictionary;
+
+ private readonly ConcurrentQueue> hotQueue;
+ private readonly ConcurrentQueue> warmQueue;
+ private readonly ConcurrentQueue> coldQueue;
+
+ // maintain count outside ConcurrentQueue, since ConcurrentQueue.Count holds a global lock
+ private int hotCount;
+ private int warmCount;
+ private int coldCount;
+
+ private readonly int hotCapacity;
+ private readonly int warmCapacity;
+ private readonly int coldCapacity;
+
+ private readonly LruPolicy policy;
+
+ // Since H is a struct, making it readonly will force the runtime to make defensive copies
+ // if mutate methods are called. Therefore, field must be mutable to maintain count.
+ protected H hitCounter;
+
+ public TemplateConcurrentLru(
+ int concurrencyLevel,
+ int capacity,
+ IEqualityComparer comparer,
+ LruPolicy itemPolicy,
+ H hitCounter)
+ {
+ if (capacity < 3)
+ {
+ throw new ArgumentOutOfRangeException("Capacity must be greater than or equal to 3.");
+ }
+
+ if (comparer == null)
+ {
+ throw new ArgumentNullException(nameof(comparer));
+ }
+
+ var queueCapacity = ComputeQueueCapacity(capacity);
+ this.hotCapacity = queueCapacity.hot;
+ this.warmCapacity = queueCapacity.warm;
+ this.coldCapacity = queueCapacity.cold;
+
+ this.hotQueue = new ConcurrentQueue>();
+ this.warmQueue = new ConcurrentQueue>();
+ this.coldQueue = new ConcurrentQueue>();
+
+ int dictionaryCapacity = this.hotCapacity + this.warmCapacity + this.coldCapacity + 1;
+
+ this.dictionary = new ConcurrentDictionary>(concurrencyLevel, dictionaryCapacity, comparer);
+ this.policy = itemPolicy;
+ this.hitCounter = hitCounter;
+ }
+
+ // No lock count: https://arbel.net/2013/02/03/best-practices-for-using-concurrentdictionary/
+ public int Count => this.dictionary.Skip(0).Count();
+
+ public int HotCount => this.hotCount;
+
+ public int WarmCount => this.warmCount;
+
+ public int ColdCount => this.coldCount;
+
+ ///
+ public bool TryGet(K key, out V value)
+ {
+ LruItem item;
+ if (dictionary.TryGetValue(key, out item))
+ {
+ return GetOrDiscard(item, out value);
+ }
+
+ value = default(V);
+ this.hitCounter.IncrementMiss();
+ return false;
+ }
+
+ public bool TryPullLazy(K key, out V value)
+ {
+ if (this.dictionary.TryGetValue(key, out var existing))
+ {
+ bool retVal = GetOrDiscard(existing, out value);
+ //existing.WasAccessed = false;
+ existing.SetRemoved();
+ existing.ShouldFastDiscard = true;
+ // serialize dispose (common case dispose not thread safe)
+ //if (existing.Value is IDisposable)
+ //{
+ // lock (existing)
+ // {
+ // if (existing.Value is IDisposable d)
+ // {
+ // d.Dispose();
+ // }
+ // }
+ //}
+
+ return retVal;
+ }
+
+ value = default(V);
+ return false;
+ }
+
+ // AggressiveInlining forces the JIT to inline policy.ShouldDiscard(). For LRU policy
+ // the first branch is completely eliminated due to JIT time constant propogation.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private bool GetOrDiscard(LruItem item, out V value)
+ {
+ if (this.policy.ShouldDiscard(item))
+ {
+ this.Move(item, ItemDestination.Remove);
+ this.hitCounter.IncrementMiss();
+ value = default(V);
+ return false;
+ }
+
+ value = item.Value;
+ this.policy.Touch(item);
+ this.hitCounter.IncrementHit();
+ return true;
+ }
+
+ ///
+ public V GetOrAdd(K key, Func valueFactory)
+ {
+ if (this.TryGet(key, out var value))
+ {
+ return value;
+ }
+
+ // The value factory may be called concurrently for the same key, but the first write to the dictionary wins.
+ // This is identical logic in ConcurrentDictionary.GetOrAdd method.
+ var newItem = this.policy.CreateItem(key, valueFactory(key));
+
+ if (this.dictionary.TryAdd(key, newItem))
+ {
+ this.hotQueue.Enqueue(newItem);
+ Interlocked.Increment(ref hotCount);
+ Cycle();
+ return newItem.Value;
+ }
+
+ return this.GetOrAdd(key, valueFactory);
+ }
+
+ ///
+ public async Task GetOrAddAsync(K key, Func> valueFactory)
+ {
+ if (this.TryGet(key, out var value))
+ {
+ return value;
+ }
+
+ // The value factory may be called concurrently for the same key, but the first write to the dictionary wins.
+ // This is identical logic in ConcurrentDictionary.GetOrAdd method.
+ var newItem = this.policy.CreateItem(key, await valueFactory(key).ConfigureAwait(false));
+
+ if (this.dictionary.TryAdd(key, newItem))
+ {
+ this.hotQueue.Enqueue(newItem);
+ Interlocked.Increment(ref hotCount);
+ Cycle();
+ return newItem.Value;
+ }
+
+ return await this.GetOrAddAsync(key, valueFactory).ConfigureAwait(false);
+ }
+
+
+
+ ///
+ public bool TryRemove(K key)
+ {
+ if (this.dictionary.TryGetValue(key, out var existing))
+ {
+ var kvp = new KeyValuePair>(key, existing);
+
+ // hidden atomic remove
+ // https://devblogs.microsoft.com/pfxteam/little-known-gems-atomic-conditional-removals-from-concurrentdictionary/
+ if (((ICollection>>)this.dictionary).Remove(kvp))
+ {
+ // Mark as not accessed, it will later be cycled out of the queues because it can never be fetched
+ // from the dictionary. Note: Hot/Warm/Cold count will reflect the removed item until it is cycled
+ // from the queue.
+ //existing.WasAccessed = false;
+ existing.SetUnaccessed();
+ existing.SetRemoved();
+ //existing.WasRemoved = true;
+
+ // serialize dispose (common case dispose not thread safe)
+ //if (existing.Value is IDisposable)
+ //{
+ // lock (existing)
+ // {
+ // if (existing.Value is IDisposable d)
+ // {
+ // d.Dispose();
+ // }
+ // }
+ //}
+
+
+ return true;
+ }
+
+ // it existed, but we couldn't remove - this means value was replaced afer the TryGetValue (a race), try again
+ return TryRemove(key);
+ }
+
+ return false;
+ }
+
+ ///
+ ///Note: Calling this method does not affect LRU order.
+ public bool TryUpdate(K key, V value)
+ {
+ if (this.dictionary.TryGetValue(key, out var existing))
+ {
+ lock (existing)
+ {
+ if (!existing.WasRemoved)
+ {
+ V oldValue = existing.Value;
+ existing.Value = value;
+
+ //if (oldValue is IDisposable d)
+ //{
+ // d.Dispose();
+ //}
+
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ public void TryAdd(K key, V value)
+ {
+ var newItem = this.policy.CreateItem(key, value);
+ if (this.dictionary.TryAdd(key, newItem))
+ {
+ this.hotQueue.Enqueue(newItem);
+ Interlocked.Increment(ref hotCount);
+ Cycle();
+ return;
+ }
+ }
+
+ ///
+ ///Note: Updates to existing items do not affect LRU order. Added items are at the top of the LRU.
+ public void AddOrUpdate(K key, V value)
+ {
+ // first, try to update
+ if (this.TryUpdate(key, value))
+ {
+ return;
+ }
+
+ // then try add
+ var newItem = this.policy.CreateItem(key, value);
+
+ if (this.dictionary.TryAdd(key, newItem))
+ {
+ this.hotQueue.Enqueue(newItem);
+ Interlocked.Increment(ref hotCount);
+ Cycle();
+ return;
+ }
+
+ // if both update and add failed there was a race, try again
+ AddOrUpdate(key, value);
+ }
+
+ ///
+ public void Clear()
+ {
+ // take a key snapshot
+ var keys = this.dictionary.Keys.ToList();
+
+ // remove all keys in the snapshot - this correctly handles disposable values
+ foreach (var key in keys)
+ {
+ TryRemove(key);
+ }
+
+ // At this point, dictionary is empty but queues still hold references to all values.
+ // Cycle the queues to purge all refs. If any items were added during this process,
+ // it is possible they might be removed as part of CycleCold. However, the dictionary
+ // and queues will remain in a consistent state.
+ for (int i = 0; i < keys.Count; i++)
+ {
+ CycleHotUnchecked();
+ CycleWarmUnchecked();
+ CycleColdUnchecked();
+ }
+ }
+
+ private void Cycle()
+ {
+ // There will be races when queue count == queue capacity. Two threads may each dequeue items.
+ // This will prematurely free slots for the next caller. Each thread will still only cycle at most 5 items.
+ // Since TryDequeue is thread safe, only 1 thread can dequeue each item. Thus counts and queue state will always
+ // converge on correct over time.
+ CycleHot();
+
+ // Multi-threaded stress tests show that due to races, the warm and cold count can increase beyond capacity when
+ // hit rate is very high. Double cycle results in stable count under all conditions. When contention is low,
+ // secondary cycles have no effect.
+ CycleWarm();
+ CycleWarm();
+ CycleCold();
+ CycleCold();
+ }
+
+ private void CycleHot()
+ {
+ if (this.hotCount > this.hotCapacity)
+ {
+ CycleHotUnchecked();
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void CycleHotUnchecked()
+ {
+ Interlocked.Decrement(ref this.hotCount);
+
+ if (this.hotQueue.TryDequeue(out var item))
+ {
+ var where = this.policy.RouteHot(item);
+ this.Move(item, where);
+ }
+ else
+ {
+ Interlocked.Increment(ref this.hotCount);
+ }
+ }
+
+ private void CycleWarm()
+ {
+ if (this.warmCount > this.warmCapacity)
+ {
+ CycleWarmUnchecked();
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void CycleWarmUnchecked()
+ {
+ Interlocked.Decrement(ref this.warmCount);
+
+ if (this.warmQueue.TryDequeue(out var item))
+ {
+ var where = this.policy.RouteWarm(item);
+
+ // When the warm queue is full, we allow an overflow of 1 item before redirecting warm items to cold.
+ // This only happens when hit rate is high, in which case we can consider all items relatively equal in
+ // terms of which was least recently used.
+ if (where == ItemDestination.Warm && this.warmCount <= this.warmCapacity)
+ {
+ this.Move(item, where);
+ }
+ else
+ {
+ this.Move(item, ItemDestination.Cold);
+ }
+ }
+ else
+ {
+ Interlocked.Increment(ref this.warmCount);
+ }
+ }
+
+ private void CycleCold()
+ {
+ if (this.coldCount > this.coldCapacity)
+ {
+ CycleColdUnchecked();
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void CycleColdUnchecked()
+ {
+ Interlocked.Decrement(ref this.coldCount);
+
+ if (this.coldQueue.TryDequeue(out var item))
+ {
+ var where = this.policy.RouteCold(item);
+
+ if (where == ItemDestination.Warm && this.warmCount <= this.warmCapacity)
+ {
+ this.Move(item, where);
+ }
+ else
+ {
+ this.Move(item, ItemDestination.Remove);
+ }
+ }
+ else
+ {
+ Interlocked.Increment(ref this.coldCount);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void Move(LruItem item, ItemDestination where)
+ {
+ item.SetUnaccessed();
+ if (item.WasRemoved)
+ return;
+ switch (where)
+ {
+ case ItemDestination.Warm:
+ this.warmQueue.Enqueue(item);
+ Interlocked.Increment(ref this.warmCount);
+ break;
+ case ItemDestination.Cold:
+ this.coldQueue.Enqueue(item);
+ Interlocked.Increment(ref this.coldCount);
+ break;
+ case ItemDestination.Remove:
+ if (item.ShouldFastDiscard == false)
+ {
+
+ var kvp =
+ new KeyValuePair>(item.Key, item);
+
+ // hidden atomic remove
+ // https://devblogs.microsoft.com/pfxteam/little-known-gems-atomic-conditional-removals-from-concurrentdictionary/
+ if (((ICollection>>)this
+ .dictionary).Remove(kvp))
+ {
+ item.SetRemoved();
+ //item.WasRemoved = true;
+
+ //if (item.Value is IDisposable)
+ //{
+ // lock (item)
+ // {
+ // if (item.Value is IDisposable d)
+ // {
+ // d.Dispose();
+ // }
+ // }
+ //}
+ }
+
+ }
+ else
+ {
+ this.dictionary.TryRemove(item.Key, out var trash);
+ }
+
+ break;
+ }
+ }
+
+ private static (int hot, int warm, int cold) ComputeQueueCapacity(int capacity)
+ {
+ int hotCapacity = capacity / 3;
+ int warmCapacity = capacity / 3;
+ int coldCapacity = capacity / 3;
+
+ int remainder = capacity % 3;
+
+ switch (remainder)
+ {
+ case 1:
+ coldCapacity++;
+ break;
+ case 2:
+ hotCapacity++;
+ coldCapacity++;
+ break;
+ }
+
+ return (hotCapacity, warmCapacity, coldCapacity);
+ }
+ }
+}
+
diff --git a/src/core/Akka.Remote/Serialization/LruBoundedCache.cs b/src/core/Akka.Remote/Serialization/LruBoundedCache.cs
index e2a99fde2f3..62b0284e616 100644
--- a/src/core/Akka.Remote/Serialization/LruBoundedCache.cs
+++ b/src/core/Akka.Remote/Serialization/LruBoundedCache.cs
@@ -6,7 +6,11 @@
//-----------------------------------------------------------------------
using System;
+using System.Collections.Generic;
+using System.Diagnostics.Contracts;
using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
namespace Akka.Remote.Serialization
{
@@ -25,14 +29,24 @@ internal static class FastHash
/// A 32-bit pseudo-random hash value.
public static int OfString(string s)
{
- var chars = s.AsSpan();
+ return OfString(s.AsSpan());
+ }
+
+ ///
+ /// Allocatey, but safe implementation of FastHash
+ ///
+ /// The input string.
+ /// A 32-bit pseudo-random hash value.
+ public static int OfString(ReadOnlySpan s)
+ {
+ var len = s.Length;
var s0 = 391408L; // seed value 1, DON'T CHANGE
var s1 = 601258L; // seed value 2, DON'T CHANGE
unchecked
{
- for(var i = 0; i < chars.Length;i++)
+ for (var i = 0; i < len; i++)
{
- var x = s0 ^ chars[i]; // Mix character into PRNG state
+ var x = s0 ^ s[i]; // Mix character into PRNG state
var y = s1;
// Xorshift128+ round
@@ -53,6 +67,10 @@ public static int OfString(string s)
///
/// The input string.
/// A 32-bit pseudo-random hash value.
+ public static int OfStringFastn(string s)
+ {
+ return GetDjb2HashCode(s);
+ }
public static int OfStringFast(string s)
{
var len = s.Length;
@@ -82,6 +100,82 @@ public static int OfStringFast(string s)
}
}
}
+
+ [Pure]
+ public static int GetDjb2HashCode(ref char r0, int length)
+ {
+ int hash = 5381;
+ int offset = 0;
+
+ while (length >= 8)
+ {
+ // Doing a left shift by 5 and adding is equivalent to multiplying by 33.
+ // This is preferred for performance reasons, as when working with integer
+ // values most CPUs have higher latency for multiplication operations
+ // compared to a simple shift and add. For more info on this, see the
+ // details for imul, shl, add: https://gmplib.org/~tege/x86-timing.pdf.
+ hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 0).GetHashCode());
+ hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 1).GetHashCode());
+ hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 2).GetHashCode());
+ hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 3).GetHashCode());
+ hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 4).GetHashCode());
+ hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 5).GetHashCode());
+ hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 6).GetHashCode());
+ hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 7).GetHashCode());
+
+ length -= 8;
+ offset += 8;
+ }
+
+ if (length >= 4)
+ {
+ hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 0).GetHashCode());
+ hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 1).GetHashCode());
+ hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 2).GetHashCode());
+ hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset + 3).GetHashCode());
+
+ length -= 4;
+ offset += 4;
+ }
+
+ while (length > 0)
+ {
+ hash = unchecked(((hash << 5) + hash) ^ Unsafe.Add(ref r0, offset).GetHashCode());
+
+ length -= 1;
+ offset += 1;
+ }
+
+ return hash;
+ }
+ [Pure]
+ public static int GetDjb2HashCode(string s)//(ref T r0, nint length)
+ //where T : notnull
+ {
+ int length = s.Length;
+ ref char c0= ref MemoryMarshal.GetReference(s.AsSpan());
+ return GetDjb2HashCode(ref c0, length);
+ }
+
+
+ }
+
+ ///
+ /// INTERNAL API
+ ///
+ internal sealed class FastHashComparer : IEqualityComparer
+ {
+ public readonly static FastHashComparer Default = new FastHashComparer();
+
+ public bool Equals(string x, string y)
+ {
+ return StringComparer.Ordinal.Equals(x, y);
+ }
+
+ public int GetHashCode(string s)
+ {
+ return FastHash.OfStringFast(s);
+ }
}
///
@@ -103,6 +197,8 @@ public CacheStatistics(int entries, int maxProbeDistance, double averageProbeDis
public double AverageProbeDistance { get; }
}
+
+
///
/// INTERNAL API
///
@@ -117,7 +213,7 @@ public CacheStatistics(int entries, int maxProbeDistance, double averageProbeDis
/// The type of value used in the cache.
internal abstract class LruBoundedCache where TValue : class
{
- protected LruBoundedCache(int capacity, int evictAgeThreshold)
+ protected LruBoundedCache(int capacity, int evictAgeThreshold, IEqualityComparer keyComparer)
{
if (capacity <= 0)
throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity must be larger than zero.");
@@ -128,11 +224,13 @@ protected LruBoundedCache(int capacity, int evictAgeThreshold)
Capacity = capacity;
EvictAgeThreshold = evictAgeThreshold;
+ _keyComparer = keyComparer;
_mask = Capacity - 1;
_keys = new TKey[Capacity];
_values = new TValue[Capacity];
_hashes = new int[Capacity];
- _epochs = Enumerable.Repeat(_epoch - evictAgeThreshold, Capacity).ToArray();
+ _epochs = new int[Capacity];
+ _epochs.AsSpan().Fill(_epoch - evictAgeThreshold);
}
public int Capacity { get; private set; }
@@ -144,6 +242,7 @@ protected LruBoundedCache(int capacity, int evictAgeThreshold)
// Practically guarantee an overflow
private int _epoch = int.MaxValue - 1;
+ private readonly IEqualityComparer _keyComparer;
private readonly TKey[] _keys;
private readonly TValue[] _values;
private readonly int[] _hashes;
@@ -174,7 +273,7 @@ public CacheStatistics Stats
public TValue Get(TKey k)
{
- var h = Hash(k);
+ var h = _keyComparer.GetHashCode(k);
var position = h & _mask;
var probeDistance = 0;
@@ -186,18 +285,49 @@ public TValue Get(TKey k)
return null;
if (probeDistance > otherProbeDistance)
return null;
- if (_hashes[position] == h && k.Equals(_keys[position]))
+ if (_hashes[position] == h && _keyComparer.Equals(k, _keys[position]))
{
return _values[position];
}
position = (position + 1) & _mask;
- probeDistance = probeDistance + 1;
+ probeDistance++;
+ }
+ }
+
+ public bool TryGet(TKey k, out TValue value)
+ {
+ var h = _keyComparer.GetHashCode(k);
+
+ var position = h & _mask;
+ var probeDistance = 0;
+
+ while (true)
+ {
+ var otherProbeDistance = ProbeDistanceOf(position);
+ if (_values[position] == null || probeDistance > otherProbeDistance)
+ {
+ value = default;
+ return false;
+ }
+ if (_hashes[position] == h && _keyComparer.Equals(k, _keys[position]))
+ {
+ value = _values[position];
+ return true;
+ }
+ position = (position + 1) & _mask;
+ probeDistance++;
}
}
public TValue GetOrCompute(TKey k)
{
- var h = Hash(k);
+ TryGetOrCompute(k, out var value);
+ return value;
+ }
+
+ public bool TryGetOrCompute(TKey k, out TValue value)
+ {
+ var h = _keyComparer.GetHashCode(k);
unchecked { _epoch += 1; }
var position = h & _mask;
@@ -207,7 +337,7 @@ public TValue GetOrCompute(TKey k)
{
if (_values[position] == null)
{
- var value = Compute(k);
+ value = Compute(k);
if (IsCacheable(value))
{
_keys[position] = k;
@@ -215,7 +345,7 @@ public TValue GetOrCompute(TKey k)
_hashes[position] = h;
_epochs[position] = _epoch;
}
- return value;
+ return false;
}
else
{
@@ -224,21 +354,70 @@ public TValue GetOrCompute(TKey k)
// the table since because of the Robin-Hood property we would have swapped it with the current element.
if (probeDistance > otherProbeDistance)
{
- var value = Compute(k);
- if (IsCacheable(value)) Move(position, k, h, value, _epoch, probeDistance);
- return value;
+ value = Compute(k);
+ if (IsCacheable(value))
+ Move(position, k, h, value, _epoch, probeDistance);
+ return false;
}
- else if (_hashes[position] == h && k.Equals(_keys[position]))
+ else if (_hashes[position] == h && _keyComparer.Equals(k, _keys[position]))
{
// Update usage
_epochs[position] = _epoch;
- return _values[position];
+ value = _values[position];
+ return true;
}
else
{
// This is not our slot yet
position = (position + 1) & _mask;
- probeDistance = probeDistance + 1;
+ probeDistance++;
+ }
+ }
+ }
+ }
+
+ public bool TrySet(TKey key, TValue value)
+ {
+ if (!IsCacheable(value)) return false;
+
+ var h = _keyComparer.GetHashCode(key);
+ unchecked { _epoch += 1; }
+
+ var position = h & _mask;
+ var probeDistance = 0;
+
+ while (true)
+ {
+ if (_values[position] == null)
+ {
+ _keys[position] = key;
+ _values[position] = value;
+ _hashes[position] = h;
+ _epochs[position] = _epoch;
+ return true;
+ }
+ else
+ {
+ var otherProbeDistance = ProbeDistanceOf(position);
+ // If probe distance of the element we try to get is larger than the current slot's, then the element cannot be in
+ // the table since because of the Robin-Hood property we would have swapped it with the current element.
+ if (probeDistance > otherProbeDistance)
+ {
+ Move(position, key, h, value, _epoch, probeDistance);
+ return true;
+ }
+ else if (_hashes[position] == h && _keyComparer.Equals(key, _keys[position]))
+ {
+ // Update usage
+ _epochs[position] = _epoch;
+ _values[position] = value;
+ return true;
+ }
+ else
+ {
+ // This is not our slot yet
+ position = (position + 1) & _mask;
+ probeDistance++;
}
}
}
@@ -333,9 +512,6 @@ protected int ProbeDistanceOf(int idealSlot, int actualSlot)
return ((actualSlot - idealSlot) + Capacity) & _mask;
}
-
- protected abstract int Hash(TKey k);
-
protected abstract TValue Compute(TKey k);
protected abstract bool IsCacheable(TValue v);
diff --git a/src/core/Akka.Remote/Serialization/WrappedPayloadSupport.cs b/src/core/Akka.Remote/Serialization/WrappedPayloadSupport.cs
index 8d82daea92f..58c9116ecf9 100644
--- a/src/core/Akka.Remote/Serialization/WrappedPayloadSupport.cs
+++ b/src/core/Akka.Remote/Serialization/WrappedPayloadSupport.cs
@@ -28,8 +28,8 @@ public Proto.Msg.Payload PayloadToProto(object payload)
var payloadProto = new Proto.Msg.Payload();
var serializer = _system.Serialization.FindSerializerFor(payload);
-
- payloadProto.Message = ByteString.CopyFrom(serializer.ToBinary(payload));
+ payloadProto.Message = UnsafeByteOperations.UnsafeWrap(serializer.ToBinary(payload));
+ //payloadProto.Message = ByteString.CopyFrom(serializer.ToBinary(payload));
payloadProto.SerializerId = serializer.Identifier;
// get manifest
diff --git a/src/core/Akka.Remote/Transport/AkkaPduCodec.cs b/src/core/Akka.Remote/Transport/AkkaPduCodec.cs
index bde6e0baf27..136dd402a94 100644
--- a/src/core/Akka.Remote/Transport/AkkaPduCodec.cs
+++ b/src/core/Akka.Remote/Transport/AkkaPduCodec.cs
@@ -12,6 +12,7 @@
using System.Runtime.Serialization;
using Akka.Remote.Serialization;
using Akka.Remote.Serialization.Proto.Msg;
+using MessagePack;
using SerializedMessage = Akka.Remote.Serialization.Proto.Msg.Payload;
namespace Akka.Remote.Transport
@@ -202,12 +203,15 @@ public AckAndMessage(Ack ackOption, Message messageOption)
internal abstract class AkkaPduCodec
{
protected readonly ActorSystem System;
- protected readonly AddressThreadLocalCache AddressCache;
-
+ protected readonly ActorPathThreadLocalCache ActorPathCache;
+ protected readonly ActorRefResolveAskCache AskRefCache;
+ //protected readonly ActorPathAskResolverCache AskPathCache;
protected AkkaPduCodec(ActorSystem system)
{
System = system;
- AddressCache = AddressThreadLocalCache.For(system);
+ ActorPathCache = ActorPathThreadLocalCache.For(system);
+ AskRefCache = ActorRefResolveAskCache.For(system);
+ //AskPathCache = ActorPathAskResolverCache.For(system);
}
///
@@ -286,9 +290,15 @@ public virtual ByteString EncodePdu(IAkkaPdu pdu)
/// TBD
/// TBD
/// TBD
+ ///
/// TBD
- public abstract ByteString ConstructMessage(Address localAddress, IActorRef recipient,
- SerializedMessage serializedMessage, IActorRef senderOption = null, SeqNo seqOption = null, Ack ackOption = null);
+ public abstract ByteString ConstructMessage(Address localAddress,
+ IActorRef recipient,
+ SerializedMessage serializedMessage, IActorRef senderOption = null,
+ SeqNo seqOption = null, Ack ackOption = null,
+ //IRemoteActorRefProvider provider = null
+ IActorRef refAskCache = null
+ );
///
/// TBD
@@ -301,7 +311,7 @@ public abstract ByteString ConstructMessage(Address localAddress, IActorRef reci
///
/// TBD
///
- internal class AkkaPduProtobuffCodec : AkkaPduCodec
+ internal class AkkaPduMessagePackCodec : AkkaPduCodec
{
///
/// TBD
@@ -400,6 +410,33 @@ public override ByteString ConstructHeartbeat()
///
private const ulong SeqUndefined = ulong.MaxValue;
+
+ public class MsgPackAckAndEnvelope
+ {
+ public MsgPackAck Ack;
+ public MsgPackEnvelope Envelope;
+ }
+
+ public class MsgPackAck
+ {
+ public ulong CumulativeAck;
+ public ulong[] Nacks;
+ }
+
+ public class MsgPackEnvelope
+ {
+ public ulong Seq;
+ public string Sender;
+ public string Recipient;
+ public MsgPackPayload Payload;
+ }
+
+ public class MsgPackPayload
+ {
+ public ByteString Message { get; set; }
+ public int SerializerId { get; set; }
+ public ByteString MessageManifest { get; set; }
+ }
///
/// TBD
///
@@ -409,7 +446,9 @@ public override ByteString ConstructHeartbeat()
/// TBD
public override AckAndMessage DecodeMessage(ByteString raw, IRemoteActorRefProvider provider, Address localAddress)
{
- var ackAndEnvelope = AckAndEnvelopeContainer.Parser.ParseFrom(raw);
+ var ackAndEnvelope = MessagePackSerializer.Deserialize(raw.Memory,
+ MessagePackSerializerOptions.Standard);
+ //var ackAndEnvelope = AckAndEnvelopeContainer.Parser.ParseFrom(raw);
Ack ackOption = null;
@@ -425,23 +464,337 @@ public override AckAndMessage DecodeMessage(ByteString raw, IRemoteActorRefProvi
var envelopeContainer = ackAndEnvelope.Envelope;
if (envelopeContainer != null)
{
- var recipient = provider.ResolveActorRefWithLocalAddress(envelopeContainer.Recipient.Path, localAddress);
- Address recipientAddress;
- if (AddressCache != null)
+ var recipient = provider.ResolveActorRefWithLocalAddress(envelopeContainer.Recipient, localAddress, false);
+
+ //todo get parsed address from provider
+ var recipientAddress = recipient.Path.Address;// ActorPathCache.Cache.GetOrCompute(envelopeContainer.Recipient.Path).Address;
+
+ var serializedMessage = envelopeContainer.Payload;
+ IActorRef senderOption = null;
+ if (envelopeContainer.Sender != null)
+ senderOption = provider.ResolveActorRefWithLocalAddress(envelopeContainer.Sender, localAddress, true);
+
+ SeqNo seqOption = null;
+ if (envelopeContainer.Seq != SeqUndefined)
{
- recipientAddress = AddressCache.Cache.GetOrCompute(envelopeContainer.Recipient.Path);
+ unchecked
+ {
+ seqOption = new SeqNo((long)envelopeContainer.Seq); //proto takes a ulong
+ }
}
- else
+
+ messageOption = new Message(recipient, recipientAddress,
+ new SerializedMessage()
+ {
+ Message =
+
+ serializedMessage.Message,
+ MessageManifest =serializedMessage
+ .MessageManifest,
+ SerializerId = serializedMessage.SerializerId
+ }, senderOption, seqOption);
+ }
+ }
+
+
+ return new AckAndMessage(ackOption, messageOption);
+ }
+
+ private MsgPackAck AckBuilder(Ack ack)
+ {
+ var acki = new MsgPackAck();
+ acki.CumulativeAck = (ulong)ack.CumulativeAck.RawValue;
+ acki.Nacks = (from nack in ack.Nacks select (ulong)nack.RawValue).ToArray();
+
+ return acki;
+ }
+
+ ///
+ /// TBD
+ ///
+ /// TBD
+ /// TBD
+ /// TBD
+ /// TBD
+ /// TBD
+ /// TBD
+ ///
+ /// TBD
+ public override ByteString ConstructMessage(Address localAddress,
+ IActorRef recipient, SerializedMessage serializedMessage,
+ IActorRef senderOption = null, SeqNo seqOption = null,
+ Ack ackOption = null,
+ //IRemoteActorRefProvider provider = null
+ IActorRef refAskCache = null
+ )
+ {
+ var ackAndEnvelope = new MsgPackAckAndEnvelope();
+ //var ackAndEnvelope = new AckAndEnvelopeContainer();
+ var envelope =
+ //new RemoteEnvelope()
+ new MsgPackEnvelope()
+ { Recipient = SerializeActorRef(recipient.Path.Address, recipient).Path };
+ if (senderOption != null && senderOption.Path != null)
+ {
+ envelope.Sender = SerializeActorRef(localAddress, senderOption).Path;
+ if (senderOption is FutureActorRef)
+ {
+ //provider?.RefAskCacheInst().Set(envelope.Sender.Path, senderOption);
+ refAskCache.Tell(new CacheAdd(){key = envelope.Sender, value = senderOption});
+ //AskPathCache.Cache.Set(envelope.Sender.Path, senderOption.Path);
+ }
+ }
+
+ if (seqOption != null) { envelope.Seq = (ulong)seqOption.RawValue; } else envelope.Seq = SeqUndefined;
+ if (ackOption != null) { ackAndEnvelope.Ack = AckBuilder(ackOption); }
+
+ envelope.Payload = new MsgPackPayload()
+ {
+ Message = serializedMessage.Message,
+ MessageManifest = serializedMessage.MessageManifest,
+ SerializerId = serializedMessage.SerializerId
+ };
+ //envelope.Message = serializedMessage;
+ ackAndEnvelope.Envelope = envelope;
+
+ return UnsafeByteOperations.UnsafeWrap(
+ MessagePackSerializer.Serialize(ackAndEnvelope));
+ }
+
+ ///
+ /// TBD
+ ///
+ /// TBD
+ /// TBD
+ public override ByteString ConstructPureAck(Ack ack)
+ {
+ return new AckAndEnvelopeContainer() { Ack = AckBuilder(ack) }.ToByteString();
+ }
+
+#region Internal methods
+ private IAkkaPdu DecodeControlPdu(AkkaControlMessage controlPdu)
+ {
+ switch (controlPdu.CommandType)
+ {
+ case CommandType.Associate:
+ var handshakeInfo = controlPdu.HandshakeInfo;
+ if (handshakeInfo != null) // HasHandshakeInfo
{
- ActorPath.TryParseAddress(envelopeContainer.Recipient.Path, out recipientAddress);
+ return new Associate(new HandshakeInfo(DecodeAddress(handshakeInfo.Origin), (int)handshakeInfo.Uid));
}
+ break;
+ case CommandType.Disassociate:
+ return new Disassociate(DisassociateInfo.Unknown);
+ case CommandType.DisassociateQuarantined:
+ return new Disassociate(DisassociateInfo.Quarantined);
+ case CommandType.DisassociateShuttingDown:
+ return new Disassociate(DisassociateInfo.Shutdown);
+ case CommandType.Heartbeat:
+ return new Heartbeat();
+ }
+
+ throw new PduCodecException($"Decoding of control PDU failed, invalid format, unexpected {controlPdu}");
+ }
+
+
+
+ private ByteString DISASSOCIATE
+ {
+ get { return ConstructControlMessagePdu(CommandType.Disassociate); }
+ }
+
+ private ByteString DISASSOCIATE_SHUTTING_DOWN
+ {
+ get { return ConstructControlMessagePdu(CommandType.DisassociateShuttingDown); }
+ }
+
+ private ByteString DISASSOCIATE_QUARANTINED
+ {
+ get { return ConstructControlMessagePdu(CommandType.DisassociateQuarantined); }
+ }
+
+ private static ByteString ConstructControlMessagePdu(CommandType code, AkkaHandshakeInfo handshakeInfo = null)
+ {
+ var controlMessage = new AkkaControlMessage() { CommandType = code };
+ if (handshakeInfo != null)
+ {
+ controlMessage.HandshakeInfo = handshakeInfo;
+ }
+
+ return new AkkaProtocolMessage() { Instruction = controlMessage }.ToByteString();
+ }
+
+ private static Address DecodeAddress(AddressData origin)
+ {
+ return new Address(origin.Protocol, origin.System, origin.Hostname, (int)origin.Port);
+ }
+
+ private static ActorRefData SerializeActorRef(Address defaultAddress, IActorRef actorRef)
+ {
+ return new ActorRefData()
+ {
+ Path = (!string.IsNullOrEmpty(actorRef.Path.Address.Host))
+ ? actorRef.Path.ToSerializationFormat()
+ : actorRef.Path.ToSerializationFormatWithDefaultAddress(defaultAddress)
+ };
+ }
+
+ private static AddressData SerializeAddress(Address address)
+ {
+ if (string.IsNullOrEmpty(address.Host) || !address.Port.HasValue)
+ throw new ArgumentException($"Address {address} could not be serialized: host or port missing");
+ return new AddressData()
+ {
+ Hostname = address.Host,
+ Port = (uint)address.Port.Value,
+ System = address.System,
+ Protocol = address.Protocol
+ };
+ }
+
+#endregion
+
+ public AkkaPduMessagePackCodec(ActorSystem system) : base(system)
+ {
+ }
+ }
+ ///
+ /// TBD
+ ///
+ internal class AkkaPduProtobuffCodec : AkkaPduCodec
+ {
+ ///
+ /// TBD
+ ///
+ /// TBD
+ ///
+ /// This exception is thrown when the Akka PDU in the specified byte string,
+ /// , meets one of the following conditions:
+ ///
+ /// - The PDU is neither a message or a control message.
+ /// - The PDU is a control message with an invalid format.
+ ///
+ ///
+ /// TBD
+ public override IAkkaPdu DecodePdu(ByteString raw)
+ {
+ try
+ {
+ var pdu = AkkaProtocolMessage.Parser.ParseFrom(raw);
+ if (pdu.Instruction != null) return DecodeControlPdu(pdu.Instruction);
+ else if (!pdu.Payload.IsEmpty) return new Payload(pdu.Payload); // TODO HasPayload
+ else throw new PduCodecException("Error decoding Akka PDU: Neither message nor control message were contained");
+ }
+ catch (InvalidProtocolBufferException ex)
+ {
+ throw new PduCodecException("Decoding PDU failed", ex);
+ }
+ }
+
+ ///
+ /// TBD
+ ///
+ /// TBD
+ /// TBD
+ public override ByteString ConstructPayload(ByteString payload)
+ {
+ return new AkkaProtocolMessage() { Payload = payload }.ToByteString();
+ }
+
+ ///
+ /// TBD
+ ///
+ /// TBD
+ ///
+ /// This exception is thrown when the specified contains an invalid address.
+ ///
+ /// TBD
+ public override ByteString ConstructAssociate(HandshakeInfo info)
+ {
+ var handshakeInfo = new AkkaHandshakeInfo()
+ {
+ Origin = SerializeAddress(info.Origin),
+ Uid = (ulong)info.Uid
+ };
+
+ return ConstructControlMessagePdu(CommandType.Associate, handshakeInfo);
+ }
+
+ ///
+ /// TBD
+ ///
+ /// TBD
+ /// TBD
+ public override ByteString ConstructDisassociate(DisassociateInfo reason)
+ {
+ switch (reason)
+ {
+ case DisassociateInfo.Quarantined:
+ return DISASSOCIATE_QUARANTINED;
+ case DisassociateInfo.Shutdown:
+ return DISASSOCIATE_SHUTTING_DOWN;
+ case DisassociateInfo.Unknown:
+ default:
+ return DISASSOCIATE;
+ }
+ }
+
+ /*
+ * Since there's never any ActorSystem-specific information coded directly
+ * into the heartbeat messages themselves (i.e. no handshake info,) there's no harm in caching in the
+ * same heartbeat byte buffer and re-using it.
+ */
+ private static readonly ByteString HeartbeatPdu = ConstructControlMessagePdu(CommandType.Heartbeat);
+
+ ///
+ /// Creates a new Heartbeat message instance.
+ ///
+ /// The Heartbeat message.
+ public override ByteString ConstructHeartbeat()
+ {
+ return HeartbeatPdu;
+ }
+
+ ///
+ /// Indicated RemoteEnvelope.Seq is not defined (order is irrelevant)
+ ///
+ private const ulong SeqUndefined = ulong.MaxValue;
+
+ ///
+ /// TBD
+ ///
+ /// TBD
+ /// TBD
+ /// TBD
+ /// TBD
+ public override AckAndMessage DecodeMessage(ByteString raw, IRemoteActorRefProvider provider, Address localAddress)
+ {
+ var ackAndEnvelope = AckAndEnvelopeContainer.Parser.ParseFrom(raw);
+
+ Ack ackOption = null;
+
+ if (ackAndEnvelope.Ack != null)
+ {
+ ackOption = new Ack(new SeqNo((long)ackAndEnvelope.Ack.CumulativeAck), ackAndEnvelope.Ack.Nacks.Select(x => new SeqNo((long)x)));
+ }
+
+ Message messageOption = null;
+
+ if (ackAndEnvelope.Envelope != null)
+ {
+ var envelopeContainer = ackAndEnvelope.Envelope;
+ if (envelopeContainer != null)
+ {
+ var recipient = provider.ResolveActorRefWithLocalAddress(envelopeContainer.Recipient.Path, localAddress, false);
+
+ //todo get parsed address from provider
+ var recipientAddress = recipient.Path.Address;// ActorPathCache.Cache.GetOrCompute(envelopeContainer.Recipient.Path).Address;
var serializedMessage = envelopeContainer.Message;
IActorRef senderOption = null;
if (envelopeContainer.Sender != null)
- {
- senderOption = provider.ResolveActorRefWithLocalAddress(envelopeContainer.Sender.Path, localAddress);
- }
+ senderOption = provider.ResolveActorRefWithLocalAddress(envelopeContainer.Sender.Path, localAddress, true);
+
SeqNo seqOption = null;
if (envelopeContainer.Seq != SeqUndefined)
{
@@ -450,6 +803,7 @@ public override AckAndMessage DecodeMessage(ByteString raw, IRemoteActorRefProvi
seqOption = new SeqNo((long)envelopeContainer.Seq); //proto takes a ulong
}
}
+
messageOption = new Message(recipient, recipientAddress, serializedMessage, senderOption, seqOption);
}
}
@@ -476,13 +830,29 @@ private AcknowledgementInfo AckBuilder(Ack ack)
/// TBD
/// TBD
/// TBD
+ ///
/// TBD
- public override ByteString ConstructMessage(Address localAddress, IActorRef recipient, SerializedMessage serializedMessage,
- IActorRef senderOption = null, SeqNo seqOption = null, Ack ackOption = null)
+ public override ByteString ConstructMessage(Address localAddress,
+ IActorRef recipient, SerializedMessage serializedMessage,
+ IActorRef senderOption = null, SeqNo seqOption = null,
+ Ack ackOption = null,
+ //IRemoteActorRefProvider provider = null
+ IActorRef refAskCache = null
+ )
{
var ackAndEnvelope = new AckAndEnvelopeContainer();
var envelope = new RemoteEnvelope() { Recipient = SerializeActorRef(recipient.Path.Address, recipient) };
- if (senderOption != null && senderOption.Path != null) { envelope.Sender = SerializeActorRef(localAddress, senderOption); }
+ if (senderOption != null && senderOption.Path != null)
+ {
+ envelope.Sender = SerializeActorRef(localAddress, senderOption);
+ if (senderOption is FutureActorRef)
+ {
+ //provider?.RefAskCacheInst().Set(envelope.Sender.Path, senderOption);
+ refAskCache.Tell(new CacheAdd(){key = envelope.Sender.Path, value = senderOption});
+ //AskPathCache.Cache.Set(envelope.Sender.Path, senderOption.Path);
+ }
+ }
+
if (seqOption != null) { envelope.Seq = (ulong)seqOption.RawValue; } else envelope.Seq = SeqUndefined;
if (ackOption != null) { ackAndEnvelope.Ack = AckBuilder(ackOption); }
envelope.Message = serializedMessage;
@@ -565,7 +935,7 @@ private static ActorRefData SerializeActorRef(Address defaultAddress, IActorRef
{
Path = (!string.IsNullOrEmpty(actorRef.Path.Address.Host))
? actorRef.Path.ToSerializationFormat()
- : actorRef.Path.ToSerializationFormatWithAddress(defaultAddress)
+ : actorRef.Path.ToSerializationFormatWithDefaultAddress(defaultAddress)
};
}
diff --git a/src/core/Akka.Remote/Transport/DotNetty/DotNettyTransport.cs b/src/core/Akka.Remote/Transport/DotNetty/DotNettyTransport.cs
index ff2d79eca1c..7bc73a6ba8e 100644
--- a/src/core/Akka.Remote/Transport/DotNetty/DotNettyTransport.cs
+++ b/src/core/Akka.Remote/Transport/DotNetty/DotNettyTransport.cs
@@ -343,10 +343,23 @@ private void SetClientPipeline(IChannel channel, Address remoteAddress)
{
var certificate = Settings.Ssl.Certificate;
var host = certificate.GetNameInfo(X509NameType.DnsName, false);
-
- var tlsHandler = Settings.Ssl.SuppressValidation
- ? new TlsHandler(stream => new SslStream(stream, true, (sender, cert, chain, errors) => true), new ClientTlsSettings(host))
- : TlsHandler.Client(host, certificate);
+ TlsHandler tlsHandler = null;
+ if (Settings.Ssl.EnableSecondarySSL)
+ {
+ var secondCert = Settings.Ssl.SecondarySSLCert;
+ tlsHandler = new TlsHandler(new ClientTlsSettings(host,
+ new List()
+ {
+ certificate, secondCert
+ }));
+ }
+ else
+ {
+ tlsHandler = Settings.Ssl.SuppressValidation
+ ? new TlsHandler(stream => new SslStream(stream, true, (sender, cert, chain, errors) => true), new ClientTlsSettings(host))
+ : TlsHandler.Client(host, certificate);
+
+ }
channel.Pipeline.AddFirst("TlsHandler", tlsHandler);
}
diff --git a/src/core/Akka.Remote/Transport/DotNetty/DotNettyTransportSettings.cs b/src/core/Akka.Remote/Transport/DotNetty/DotNettyTransportSettings.cs
index 5b28bad7e66..5405adeb4ab 100644
--- a/src/core/Akka.Remote/Transport/DotNetty/DotNettyTransportSettings.cs
+++ b/src/core/Akka.Remote/Transport/DotNetty/DotNettyTransportSettings.cs
@@ -9,6 +9,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Net;
+using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using Akka.Actor;
using Akka.Configuration;
@@ -289,6 +290,9 @@ internal enum TransportMode
internal sealed class SslSettings
{
public static readonly SslSettings Empty = new SslSettings();
+ public readonly X509Certificate2 SecondarySSLCert;
+ public readonly bool EnableSecondarySSL;
+
public static SslSettings Create(Config config)
{
if (config.IsNullOrEmpty())
@@ -296,9 +300,9 @@ public static SslSettings Create(Config config)
if (config.GetBoolean("certificate.use-thumprint-over-file", false))
{
- return new SslSettings(config.GetString("certificate.thumbprint", null),
+ return new SslSettings(new StoreSslSettings(config.GetString("certificate.thumbprint", null),
config.GetString("certificate.store-name", null),
- ParseStoreLocationName(config.GetString("certificate.store-location", null)),
+ ParseStoreLocationName(config.GetString("certificate.store-location", null))),
config.GetBoolean("suppress-validation", false));
}
@@ -308,9 +312,9 @@ public static SslSettings Create(Config config)
var flags = flagsRaw.Aggregate(X509KeyStorageFlags.DefaultKeySet, (flag, str) => flag | ParseKeyStorageFlag(str));
return new SslSettings(
- certificatePath: config.GetString("certificate.path", null),
+ new FileSslSettings(certificatePath: config.GetString("certificate.path", null),
certificatePassword: config.GetString("certificate.password", null),
- flags: flags,
+ flags: flags),
suppressValidation: config.GetBoolean("suppress-validation", false));
}
@@ -356,31 +360,68 @@ public SslSettings()
SuppressValidation = false;
}
- public SslSettings(string certificateThumbprint, string storeName, StoreLocation storeLocation, bool suppressValidation)
+
+ public SslSettings(SSLCertGenerator settings, SSLCertGenerator secondarySettings, bool suppressValidation)
+ {
+ Certificate = settings.Generate();
+ SecondarySSLCert = secondarySettings?.Generate();
+ SuppressValidation = suppressValidation;
+ }
+
+ public abstract class SSLCertGenerator
+ {
+ public abstract X509Certificate2 Generate();
+ }
+
+ public class StoreSslSettings : SSLCertGenerator
{
- using (var store = new X509Store(storeName, storeLocation))
+ public StoreSslSettings(string certificateThumbprint,
+ string storeName, StoreLocation storeLocation)
{
- store.Open(OpenFlags.ReadOnly);
+ this.certificateThumbprint = certificateThumbprint;
+ this.storeName = storeName;
+ this.StoreLocation = storeLocation;
+ }
+ public readonly string certificateThumbprint;
+ public readonly string storeName;
+ public readonly StoreLocation storeLocation;
- var find = store.Certificates.Find(X509FindType.FindByThumbprint, certificateThumbprint, !suppressValidation);
- if (find.Count == 0)
+ public override X509Certificate2 Generate()
+ {
+ using (var store = new X509Store(storeName, storeLocation))
{
- throw new ArgumentException(
- "Could not find Valid certificate for thumbprint (by default it can be found under `akka.remote.dot-netty.tcp.ssl.certificate.thumpbrint`. Also check akka.remote.dot-netty.tcp.ssl.certificate.store-name and akka.remote.dot-netty.tcp.ssl.certificate.store-location)");
- }
+ store.Open(OpenFlags.ReadOnly);
+
+ var find = store.Certificates.Find(X509FindType.FindByThumbprint, certificateThumbprint, !suppressValidation);
+ if (find.Count == 0)
+ {
+ throw new ArgumentException(
+ "Could not find Valid certificate for thumbprint (by default it can be found under `akka.remote.dot-netty.tcp.ssl.certificate.thumpbrint`. Also check akka.remote.dot-netty.tcp.ssl.certificate.store-name and akka.remote.dot-netty.tcp.ssl.certificate.store-location)");
+ }
- Certificate = find[0];
- SuppressValidation = suppressValidation;
+ return find[0];
+ }
}
}
-
- public SslSettings(string certificatePath, string certificatePassword, X509KeyStorageFlags flags, bool suppressValidation)
+ public class FileSslSettings : SSLCertGenerator
{
- if (string.IsNullOrEmpty(certificatePath))
- throw new ArgumentNullException(nameof(certificatePath), "Path to SSL certificate was not found (by default it can be found under `akka.remote.dot-netty.tcp.ssl.certificate.path`)");
+ public FileSslSettings(string certificatePath, string certificatePassword, X509KeyStorageFlags flags)
+ {
+ CertificatePath = certificatePath;
+ CertificatePassword = certificatePassword;
+ Flags = flags;
+ }
- Certificate = new X509Certificate2(certificatePath, certificatePassword, flags);
- SuppressValidation = suppressValidation;
+ public override X509Certificate2 Generate()
+ {
+ if (string.IsNullOrEmpty(CertificatePath))
+ throw new ArgumentNullException(nameof(CertificatePath), "Path to SSL certificate was not found (by default it can be found under `akka.remote.dot-netty.tcp.ssl.certificate.path`)");
+ return new X509Certificate2(CertificatePath, CertificatePassword, Flags);
+
+ }
+ public string CertificatePath { get; }
+ public string CertificatePassword { get; }
+ public X509KeyStorageFlags Flags { get; }
}
}
}
diff --git a/src/core/Akka.Tests/Actor/ActorPathSpec.cs b/src/core/Akka.Tests/Actor/ActorPathSpec.cs
index acd081c6494..b22f2eedc12 100644
--- a/src/core/Akka.Tests/Actor/ActorPathSpec.cs
+++ b/src/core/Akka.Tests/Actor/ActorPathSpec.cs
@@ -7,11 +7,10 @@
using System;
using System.Linq;
-using System.Net;
+using System.Text;
using Akka.Actor;
using Akka.TestKit;
using Xunit;
-using Xunit.Extensions;
namespace Akka.Tests.Actor
{
@@ -27,7 +26,7 @@ public void SupportsParsingItsStringRep()
private ActorPath ActorPathParse(string path)
{
ActorPath actorPath;
- if(ActorPath.TryParse(path, out actorPath))
+ if (ActorPath.TryParse(path, out actorPath))
return actorPath;
throw new UriFormatException();
}
@@ -44,12 +43,12 @@ public void ActorPath_Parse_HandlesCasing_ForLocal()
// as well as "http") for the sake of robustness but should only produce lowercase scheme names
// for consistency." rfc3986
Assert.True(actorPath.Address.Protocol.Equals("akka", StringComparison.Ordinal), "protocol should be lowercase");
-
+
//In Akka, at least the system name is case-sensitive, see http://doc.akka.io/docs/akka/current/additional/faq.html#what-is-the-name-of-a-remote-actor
- Assert.True(actorPath.Address.System.Equals("sYstEm", StringComparison.Ordinal), "system");
+ Assert.True(actorPath.Address.System.Equals("sYstEm", StringComparison.Ordinal), "system");
var elements = actorPath.Elements.ToList();
- elements.Count.ShouldBe(2,"number of elements in path");
+ elements.Count.ShouldBe(2, "number of elements in path");
Assert.True("pAth1".Equals(elements[0], StringComparison.Ordinal), "first path element");
Assert.True("pAth2".Equals(elements[1], StringComparison.Ordinal), "second path element");
Assert.Equal("akka://sYstEm/pAth1/pAth2", actorPath.ToString());
@@ -100,15 +99,55 @@ public void Supports_parsing_remote_FQDN_paths()
parsed.ToString().ShouldBe(remote);
}
+ [Fact]
+ public void Supports_rebase_a_path()
+ {
+ var path = "akka://sys@host:1234/";
+ ActorPath.TryParse(path, out var root).ShouldBe(true);
+ root.ToString().ShouldBe(path);
+
+ ActorPath.TryParse(root, "/", out var newPath).ShouldBe(true);
+ newPath.ShouldBe(root);
+
+ var uri1 = "/abc/def";
+ ActorPath.TryParse(root, uri1, out newPath).ShouldBe(true);
+ newPath.ToStringWithAddress().ShouldBe($"{path}{uri1.Substring(1)}");
+ newPath.ParentOf(-2).ShouldBe(root);
+
+ var uri2 = "/def";
+ ActorPath.TryParse(newPath, uri2, out newPath).ShouldBe(true);
+ newPath.ToStringWithAddress().ShouldBe($"{path}{uri1.Substring(1)}{uri2}");
+ newPath.ParentOf(-3).ShouldBe(root);
+ }
+
[Fact]
public void Return_false_upon_malformed_path()
{
- ActorPath ignored;
- ActorPath.TryParse("", out ignored).ShouldBe(false);
- ActorPath.TryParse("://hallo", out ignored).ShouldBe(false);
- ActorPath.TryParse("s://dd@:12", out ignored).ShouldBe(false);
- ActorPath.TryParse("s://dd@h:hd", out ignored).ShouldBe(false);
- ActorPath.TryParse("a://l:1/b", out ignored).ShouldBe(false);
+ ActorPath.TryParse("", out _).ShouldBe(false);
+ ActorPath.TryParse("://hallo", out _).ShouldBe(false);
+ ActorPath.TryParse("s://dd@:12", out _).ShouldBe(false);
+ ActorPath.TryParse("s://dd@h:hd", out _).ShouldBe(false);
+ ActorPath.TryParse("a://l:1/b", out _).ShouldBe(false);
+ ActorPath.TryParse("akka:/", out _).ShouldBe(false);
+ }
+
+ [Fact]
+ public void Supports_jumbo_actor_name_length()
+ {
+ var prefix = "akka://sys@host.domain.com:1234/some/ref/";
+ var nameSize = 10 * 1024 * 1024; //10MB
+
+ var sb = new StringBuilder(nameSize + prefix.Length);
+ sb.Append(prefix);
+ sb.Append('a', nameSize); //10MB
+ var path = sb.ToString();
+
+ ActorPath.TryParse(path, out var actorPath).ShouldBe(true);
+ actorPath.Name.Length.ShouldBe(nameSize);
+ actorPath.Name.All(n => n == 'a').ShouldBe(true);
+
+ var result = actorPath.ToStringWithAddress();
+ result.ShouldBe(path);
}
[Fact]
@@ -196,7 +235,7 @@ public void Paths_with_different_addresses_and_same_elements_should_not_be_equal
{
ActorPath path1 = null;
ActorPath path2 = null;
- ActorPath.TryParse("akka.tcp://remotesystem@localhost:8080/user",out path1);
+ ActorPath.TryParse("akka.tcp://remotesystem@localhost:8080/user", out path1);
ActorPath.TryParse("akka://remotesystem/user", out path2);
Assert.NotEqual(path2, path1);
@@ -238,7 +277,7 @@ public void Validate_element_parts(string element, bool matches)
public void Validate_that_url_encoded_values_are_valid_element_parts(string element)
{
var urlEncode = System.Net.WebUtility.UrlEncode(element);
- global::System.Diagnostics.Debug.WriteLine("Encoded \"{0}\" to \"{1}\"", element, urlEncode) ;
+ global::System.Diagnostics.Debug.WriteLine("Encoded \"{0}\" to \"{1}\"", element, urlEncode);
ActorPath.IsValidPathElement(urlEncode).ShouldBeTrue();
}
}
diff --git a/src/core/Akka/Actor/ActorCell.cs b/src/core/Akka/Actor/ActorCell.cs
index d8eabb6c149..00f6739745f 100644
--- a/src/core/Akka/Actor/ActorCell.cs
+++ b/src/core/Akka/Actor/ActorCell.cs
@@ -382,6 +382,25 @@ public void UseThreadContext(Action action)
InternalCurrentActorCellKeeper.Current = tmp;
}
}
+
+ ///
+ /// TBD
+ ///
+ /// TBD
+ public void UseThreadContext(Action action, T state)
+ {
+ var tmp = InternalCurrentActorCellKeeper.Current;
+ InternalCurrentActorCellKeeper.Current = this;
+ try
+ {
+ action(state);
+ }
+ finally
+ {
+ //ensure we set back the old context
+ InternalCurrentActorCellKeeper.Current = tmp;
+ }
+ }
///
/// TBD
diff --git a/src/core/Akka/Actor/ActorPath.cs b/src/core/Akka/Actor/ActorPath.cs
index 8491dd1e886..0ad5b0327c7 100644
--- a/src/core/Akka/Actor/ActorPath.cs
+++ b/src/core/Akka/Actor/ActorPath.cs
@@ -11,7 +11,6 @@
using System.Linq;
using Akka.Util;
using Newtonsoft.Json;
-using static System.String;
namespace Akka.Actor
{
@@ -36,7 +35,7 @@ public abstract class ActorPath : IEquatable, IComparable,
/// This class represents a surrogate of an .
/// Its main use is to help during the serialization process.
///
- public class Surrogate : ISurrogate, IEquatable, IEquatable
+ public sealed class Surrogate : ISurrogate, IEquatable, IEquatable
{
///
/// Initializes a new instance of the class.
@@ -59,12 +58,8 @@ public Surrogate(string path)
/// The encapsulated by this surrogate.
public ISurrogated FromSurrogate(ActorSystem system)
{
- if (TryParse(Path, out var path))
- {
- return path;
- }
-
- return null;
+ TryParse(Path, out var path);
+ return path;
}
#region Equality
@@ -72,25 +67,23 @@ public ISurrogated FromSurrogate(ActorSystem system)
///
public bool Equals(Surrogate other)
{
- if (ReferenceEquals(null, other)) return false;
- if (ReferenceEquals(this, other)) return true;
- return string.Equals(Path, other.Path);
+ if (other is null) return false;
+ return ReferenceEquals(this, other) || StringComparer.Ordinal.Equals(Path, other.Path);
}
///
public bool Equals(ActorPath other)
{
- if (other == null) return false;
- return Equals(other.ToSurrogate(null)); //TODO: not so sure if this is OK
+ if (other is null) return false;
+ return StringComparer.Ordinal.Equals(Path, other.ToSerializationFormat());
}
///
public override bool Equals(object obj)
{
- if (ReferenceEquals(null, obj)) return false;
+ if (obj is null) return false;
if (ReferenceEquals(this, obj)) return true;
- var actorPath = obj as ActorPath;
- if (actorPath != null) return Equals(actorPath);
+ if (obj is ActorPath actorPath) return Equals(actorPath);
return Equals(obj as Surrogate);
}
@@ -117,11 +110,7 @@ public override int GetHashCode()
/// TBD
public static bool IsValidPathElement(string s)
{
- if (IsNullOrEmpty(s))
- {
- return false;
- }
- return !s.StartsWith("$") && Validate(s);
+ return !string.IsNullOrEmpty(s) && !s.StartsWith("$") && Validate(s);
}
private static bool IsValidChar(char c) => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
@@ -132,17 +121,17 @@ private static bool IsHexChar(char c) => (c >= 'a' && c <= 'f') || (c >= 'A' &&
private static bool Validate(string chars)
{
- int len = chars.Length;
+ var len = chars.Length;
var pos = 0;
while (pos < len)
{
if (IsValidChar(chars[pos]))
{
- pos = pos + 1;
+ pos += 1;
}
else if (chars[pos] == '%' && pos + 2 < len && IsHexChar(chars[pos + 1]) && IsHexChar(chars[pos + 2]))
{
- pos = pos + 3;
+ pos += 3;
}
else
{
@@ -152,43 +141,92 @@ private static bool Validate(string chars)
return true;
}
+ private readonly Address _address;
+ private readonly ActorPath _parent;
+ private readonly int _depth;
+
+ private readonly string _name;
+ private readonly long _uid;
+
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class as root.
///
/// The address.
/// The name.
protected ActorPath(Address address, string name)
{
- Name = name;
- Address = address;
+ _address = address;
+ _parent = null;
+ _depth = 0;
+ _name = name;
+ _uid = ActorCell.UndefinedUid;
}
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class as child.
///
- /// The parent path.
+ /// The parentPath.
/// The name.
/// The uid.
protected ActorPath(ActorPath parentPath, string name, long uid)
{
- Address = parentPath.Address;
- Uid = uid;
- Name = name;
+ _parent = parentPath;
+ _address = parentPath._address;
+ _depth = parentPath._depth + 1;
+ _name = name;
+ _uid = uid;
}
+ ///
+ /// Gets the name.
+ ///
+ /// The name.
+ public string Name => _name;
+
+ ///
+ /// The Address under which this path can be reached; walks up the tree to
+ /// the RootActorPath.
+ ///
+ /// The address.
+ public Address Address => _address;
+
///
/// Gets the uid.
///
/// The uid.
- public long Uid { get; }
+ public long Uid => _uid;
- internal static readonly string[] EmptyElements = { };
+ ///
+ /// The path of the parent to this actor.
+ ///
+ public ActorPath Parent => _parent;
+
+ ///
+ /// The the depth of the actor.
+ ///
+ public int Depth => _depth;
///
/// Gets the elements.
///
/// The elements.
- public abstract IReadOnlyList Elements { get; }
+ public IReadOnlyList Elements
+ {
+ get
+ {
+ if (_depth == 0)
+ return ImmutableArray.Empty;
+ var b = ImmutableArray.CreateBuilder(_depth);
+ b.Count = _depth;
+ var p = this;
+ for (var i = 0; i < _depth; i++)
+ {
+ b[_depth - i - 1] = p._name;
+ p = p._parent;
+ }
+ return b.MoveToImmutable();
+ }
+ }
///
/// INTERNAL API.
@@ -202,70 +240,106 @@ internal IReadOnlyList ElementsWithUid
{
get
{
- if (this is RootActorPath) return EmptyElements;
- var elements = (List)Elements;
- elements[elements.Count - 1] = AppendUidFragment(Name);
- return elements;
+ if (_depth == 0)
+ return ImmutableArray.Empty;
+
+ var b = ImmutableArray.CreateBuilder(_depth);
+ b.Count = _depth;
+ var p = this;
+ for (var i = 0; i < _depth; i++)
+ {
+ b[_depth - i - 1] = i > 0 ? p._name : AppendUidFragment(p._name);
+ p = p._parent;
+ }
+ return b.MoveToImmutable();
}
}
- ///
- /// Gets the name.
- ///
- /// The name.
- public string Name { get; }
-
- ///
- /// The Address under which this path can be reached; walks up the tree to
- /// the RootActorPath.
- ///
- /// The address.
- public Address Address { get; }
-
///
/// The root actor path.
///
- public abstract ActorPath Root { get; }
-
- ///
- /// The path of the parent to this actor.
- ///
- public abstract ActorPath Parent { get; }
+ [JsonIgnore]
+ public ActorPath Root => ParentOf(0);
///
public bool Equals(ActorPath other)
{
- if (other == null)
+ if (other is null)
return false;
+ if (ReferenceEquals(this, other))
+ return true;
+ if (_depth != other._depth)
+ {
+ return false;
+ }
if (!Address.Equals(other.Address))
return false;
- ActorPath a = this;
- ActorPath b = other;
- for (; ; )
+ var a = this;
+ var b = other;
+ while (true)
{
if (ReferenceEquals(a, b))
return true;
- else if (a == null || b == null)
+ else if (a is null || b is null)
return false;
- else if (a.Name != b.Name)
+ else if (a._name != b._name)
return false;
- a = a.Parent;
- b = b.Parent;
+ a = a._parent;
+ b = b._parent;
}
}
///
- public abstract int CompareTo(ActorPath other);
+ public int CompareTo(ActorPath other)
+ {
+ if (_depth == 0)
+ {
+ if (other is null || other._depth > 0) return 1;
+ return StringComparer.Ordinal.Compare(ToString(), other.ToString());
+ }
+ return InternalCompareTo(this, other);
+ }
+
+ private int InternalCompareTo(ActorPath left, ActorPath right)
+ {
+ if (ReferenceEquals(left, right))
+ return 0;
+ if (right is null)
+ return 1;
+ if (left is null)
+ return -1;
+
+ if (left._depth == 0)
+ return left.CompareTo(right);
+
+ if (right._depth == 0)
+ return -right.CompareTo(left);
+
+ var nameCompareResult = StringComparer.Ordinal.Compare(left._name, right._name);
+ if (nameCompareResult != 0)
+ return nameCompareResult;
+
+ return InternalCompareTo(left._parent, right._parent);
+ }
///
- /// Withes the uid.
+ /// Creates a copy of the given ActorPath and applies a new Uid
///
/// The uid.
/// ActorPath.
- public abstract ActorPath WithUid(long uid);
+ public ActorPath WithUid(long uid)
+ {
+ if (_depth == 0)
+ {
+ if (uid != 0) throw new NotSupportedException("RootActorPath must have undefined Uid");
+ return this;
+ }
+
+ return uid != _uid ? new ChildActorPath(_parent, Name, uid) : this;
+ }
///
/// Creates a new with the specified parent
@@ -290,14 +364,35 @@ public bool Equals(ActorPath other)
public static ActorPath operator /(ActorPath path, IEnumerable name)
{
var a = path;
- foreach (string element in name)
+ foreach (var element in name)
{
if (!string.IsNullOrEmpty(element))
- a = a / element;
+ a /= element;
}
return a;
}
+ ///
+ /// Returns a parent of depth
+ /// 0: Root, 1: Guardian, ..., -1: Parent, -2: GrandParent
+ ///
+ /// The parent depth, negative depth for reverse lookup
+ public ActorPath ParentOf(int depth)
+ {
+ var current = this;
+ if (depth >= 0)
+ {
+ while (current._depth > depth)
+ current = current._parent;
+ }
+ else
+ {
+ for (var i = depth; i < 0 && current._depth > 0; i++)
+ current = current._parent;
+ }
+ return current;
+ }
+
///
/// Creates an from the specified .
///
@@ -308,12 +403,9 @@ public bool Equals(ActorPath other)
/// A newly created
public static ActorPath Parse(string path)
{
- ActorPath actorPath;
- if (TryParse(path, out actorPath))
- {
- return actorPath;
- }
- throw new UriFormatException($"Can not parse an ActorPath: {path}");
+ return TryParse(path, out var actorPath)
+ ? actorPath
+ : throw new UriFormatException($"Can not parse an ActorPath: {path}");
}
///
@@ -325,42 +417,78 @@ public static ActorPath Parse(string path)
/// TBD
public static bool TryParse(string path, out ActorPath actorPath)
{
- actorPath = null;
+ if (!TryParseAddress(path, out var address, out var absoluteUri))
+ {
+ actorPath = null;
+ return false;
+ }
- if (!TryParseAddress(path, out var address, out var absoluteUri)) return false;
- var spanified = absoluteUri;
+ return TryParse(new RootActorPath(address), absoluteUri, out actorPath);//, out bool isTemp);
+ }
- // check for Uri fragment here
- var nextSlash = 0;
+ ///
+ /// Tries to parse the uri, which should be a uri not containing protocol.
+ /// For example "/user/my-actor"
+ ///
+ /// the base path, normaly a root path
+ /// TBD
+ /// TBD
+ /// TBD
+ public static bool TryParse(ActorPath basePath, string absoluteUri, out ActorPath actorPath)
+ {
+ return TryParse(basePath, absoluteUri.AsSpan(), out actorPath); //, out bool isTemp);
+ }
- actorPath = new RootActorPath(address);
+ ///
+ /// Tries to parse the uri, which should be a uri not containing protocol.
+ /// For example "/user/my-actor"
+ ///
+ /// the base path, normaly a root path
+ /// TBD
+ /// TBD
+ /// TBD
+ public static bool TryParse(ActorPath basePath, ReadOnlySpan absoluteUri, out ActorPath actorPath
+ )
+ //, out bool isTemp)
+ {
+ actorPath = basePath;
+ //isTemp = false;
+ // check for Uri fragment here
+ int nextSlash;
do
{
- nextSlash = spanified.IndexOf('/');
+ nextSlash = absoluteUri.IndexOf('/');
if (nextSlash > 0)
{
- actorPath /= spanified.Slice(0, nextSlash).ToString();
+ //var name = absoluteUri.Slice(0, nextSlash).ToString();
+ actorPath = new ChildActorPath(actorPath, absoluteUri.Slice(0, nextSlash).ToString(), ActorCell.UndefinedUid);
}
- else if (nextSlash < 0 && spanified.Length > 0) // final segment
+ else if (nextSlash < 0 && absoluteUri.Length > 0) // final segment
{
- var fragLoc = spanified.IndexOf('#');
+ //if (string.Equals(actorPath._name, "temp",
+ // StringComparison.OrdinalIgnoreCase))
+ //{
+ // isTemp = true;
+ //}
+ var fragLoc = absoluteUri.IndexOf('#');
if (fragLoc > -1)
{
- var fragment = spanified.Slice(fragLoc+1);
- var fragValue = SpanHacks.Parse(fragment);
- spanified = spanified.Slice(0, fragLoc);
- actorPath = new ChildActorPath(actorPath, spanified.ToString(), fragValue);
+ //var fragment = absoluteUri.Slice(fragLoc + 1);
+ var fragValue = SpanHacks.Parse(absoluteUri.Slice(fragLoc + 1));
+ absoluteUri = absoluteUri.Slice(0, fragLoc);
+ actorPath = new ChildActorPath(actorPath, absoluteUri.ToString(), fragValue);
}
else
{
- actorPath /= spanified.ToString();
+ actorPath = new ChildActorPath(actorPath, absoluteUri.ToString(), ActorCell.UndefinedUid);
}
-
+
}
- spanified = spanified.Slice(nextSlash + 1);
- } while (nextSlash >= 0);
+ absoluteUri = absoluteUri.Slice(nextSlash + 1);
+ }
+ while (nextSlash >= 0);
return true;
}
@@ -383,153 +511,103 @@ public static bool TryParseAddress(string path, out Address address)
/// If true, the parsed . Otherwise null.
/// A containing the path following the address.
/// true if the could be parsed, false otherwise.
- private static bool TryParseAddress(string path, out Address address, out ReadOnlySpan absoluteUri)
+ public static bool TryParseAddress(string path, out Address address, out ReadOnlySpan absoluteUri)
{
- address = null;
-
- var spanified = path.AsSpan();
- absoluteUri = spanified;
-
- var firstColonPos = spanified.IndexOf(':');
+ address = default;
- if (firstColonPos == -1) // not an absolute Uri
+ if (!TryParseParts(path.AsSpan(), out var addressSpan, out absoluteUri))
return false;
- var fullScheme = SpanHacks.ToLowerInvariant(spanified.Slice(0, firstColonPos));
- if (!fullScheme.StartsWith("akka"))
+ if (!Address.TryParse(addressSpan, out address))
return false;
- spanified = spanified.Slice(firstColonPos + 1);
- if (spanified.Length < 2 || !(spanified[0] == '/' && spanified[1] == '/'))
- return false;
-
- spanified = spanified.Slice(2); // move past the double //
- var firstAtPos = spanified.IndexOf('@');
- var sysName = string.Empty;
-
- if (firstAtPos == -1)
- { // dealing with an absolute local Uri
- var nextSlash = spanified.IndexOf('/');
-
- if (nextSlash == -1)
- {
- sysName = spanified.ToString();
- absoluteUri = "/".AsSpan(); // RELY ON THE JIT
- }
- else
- {
- sysName = spanified.Slice(0, nextSlash).ToString();
- absoluteUri = spanified.Slice(nextSlash);
- }
-
- address = new Address(fullScheme, sysName);
- return true;
- }
-
- // dealing with a remote Uri
- sysName = spanified.Slice(0, firstAtPos).ToString();
- spanified = spanified.Slice(firstAtPos + 1);
-
- /*
- * Need to check for:
- * - IPV4 / hostnames
- * - IPV6 (must be surrounded by '[]') according to spec.
- */
- var host = string.Empty;
-
- // check for IPV6 first
- var openBracket = spanified.IndexOf('[');
- var closeBracket = spanified.IndexOf(']');
- if (openBracket > -1 && closeBracket > openBracket)
- {
- // found an IPV6 address
- host = spanified.Slice(openBracket, closeBracket - openBracket + 1).ToString();
- spanified = spanified.Slice(closeBracket + 1); // advance past the address
-
- // need to check for trailing colon
- var secondColonPos = spanified.IndexOf(':');
- if (secondColonPos == -1)
- return false;
+ return true;
+ }
- spanified = spanified.Slice(secondColonPos + 1);
- }
- else
+ ///
+ /// Attempts to parse an from a stringified .
+ ///
+ /// The string representation of the .
+ /// A containing the address part.
+ /// A containing the path following the address.
+ /// true if the path parts could be parsed, false otherwise.
+ public static bool TryParseParts(ReadOnlySpan path, out ReadOnlySpan address, out ReadOnlySpan absoluteUri)
+ {
+ var firstAtPos = path.IndexOf(':');
+ if (firstAtPos < 4 || 255 < firstAtPos
+ || path.Length strPort;
- if (actorPathSlash == -1)
+
+ //if (path.Slice(firstAtPos + 1).StartsWith("//".AsSpan()) == false)
+ //{
+ // //missing double slash
+ // address = default;
+ // absoluteUri = path;
+ // return false;
+ //}
+
+ var nextSlash = path.Slice(firstAtPos + 3).IndexOf('/');
+ if (nextSlash == -1)
{
- strPort = spanified;
+ address = path;
+ absoluteUri = "/".AsSpan(); // RELY ON THE JIT
}
else
{
- strPort = spanified.Slice(0, actorPathSlash);
- }
-
- if (SpanHacks.TryParse(strPort, out var port))
- {
- address = new Address(fullScheme, sysName, host, port);
-
- // need to compute the absolute path after the Address
- if (actorPathSlash == -1)
- {
- absoluteUri = "/".AsSpan();
- }
- else
- {
- absoluteUri = spanified.Slice(actorPathSlash);
- }
-
- return true;
+ address = path.Slice(0, firstAtPos + 3 + nextSlash);
+ absoluteUri = path.Slice(address.Length);
}
- return false;
+ return true;
}
///
/// Joins this instance.
///
+ /// the address or empty
/// System.String.
- private string Join()
+ private string Join(ReadOnlySpan prefix)
{
- if (this is RootActorPath)
- return "/";
-
- // Resolve length of final string
- var totalLength = 0;
- var p = this;
- while (!(p is RootActorPath))
+ if (_depth == 0)
{
- totalLength += p.Name.Length + 1;
- p = p.Parent;
+ Span buffer = prefix.Length < 1024 ? stackalloc char[prefix.Length + 1] : new char[prefix.Length + 1];
+ prefix.CopyTo(buffer);
+ buffer[buffer.Length - 1] = '/';
+ return buffer.ToString(); //todo use string.Create() when available
}
-
- // Concatenate segments (in reverse order) into buffer with '/' prefixes
- char[] buffer = new char[totalLength];
- int offset = buffer.Length;
- p = this;
- while (!(p is RootActorPath))
+ else
{
- offset -= p.Name.Length + 1;
- buffer[offset] = '/';
+ // Resolve length of final string
+ var totalLength = prefix.Length;
+ var p = this;
+ while (p._depth > 0)
+ {
+ totalLength += p._name.Length + 1;
+ p = p._parent;
+ }
- p.Name.CopyTo(0, buffer, offset + 1, p.Name.Length);
+ // Concatenate segments (in reverse order) into buffer with '/' prefixes
+ Span buffer = totalLength < 1024 ? stackalloc char[totalLength] : new char[totalLength];
+ prefix.CopyTo(buffer);
- p = p.Parent;
+ var offset = buffer.Length;
+ p = this;
+ while (p._depth > 0)
+ {
+ var name = p._name.AsSpan();
+ offset -= name.Length + 1;
+ buffer[offset] = '/';
+ name.CopyTo(buffer.Slice(offset + 1, name.Length));
+ p = p._parent;
+ }
+ return buffer.ToString(); //todo use string.Create() when available
}
-
- return new string(buffer);
}
///
@@ -540,13 +618,13 @@ private string Join()
/// System.String.
public string ToStringWithoutAddress()
{
- return Join();
+ return Join(ReadOnlySpan.Empty);
}
///
public override string ToString()
{
- return $"{Address}{Join()}";
+ return Join(_address.ToString().AsSpan());
}
///
@@ -555,10 +633,7 @@ public override string ToString()
/// TBD
public string ToStringWithUid()
{
- var uid = Uid;
- if (uid == ActorCell.UndefinedUid)
- return ToStringWithAddress();
- return ToStringWithAddress() + "#" + uid;
+ return _uid != ActorCell.UndefinedUid ? $"{ToStringWithAddress()}#{_uid}" : ToStringWithAddress();
}
///
@@ -571,15 +646,14 @@ public ActorPath Child(string childName)
return this / childName;
}
- ///
public override int GetHashCode()
{
unchecked
{
var hash = 17;
hash = (hash * 23) ^ Address.GetHashCode();
- foreach (var e in Elements)
- hash = (hash * 23) ^ e.GetHashCode();
+ for (var p = this; !(p is null); p = p._parent)
+ hash = (hash * 23) ^ p._name.GetHashCode();
return hash;
}
}
@@ -587,8 +661,7 @@ public override int GetHashCode()
///
public override bool Equals(object obj)
{
- var other = obj as ActorPath;
- return Equals(other);
+ return Equals(obj as ActorPath);
}
///
@@ -599,7 +672,7 @@ public override bool Equals(object obj)
/// true if both actor paths are equal; otherwise false
public static bool operator ==(ActorPath left, ActorPath right)
{
- return Equals(left, right);
+ return left?.Equals(right) ?? right is null;
}
///
@@ -610,7 +683,7 @@ public override bool Equals(object obj)
/// true if both actor paths are not equal; otherwise false
public static bool operator !=(ActorPath left, ActorPath right)
{
- return !Equals(left, right);
+ return !(left == right);
}
///
@@ -619,9 +692,17 @@ public override bool Equals(object obj)
/// System.String.
public string ToStringWithAddress()
{
- return ToStringWithAddress(Address);
+ if (lazyToStringWithAddress == null)
+ {
+ var addr = ToStringWithAddress(_address);
+ lazyToStringWithAddress = addr;
+ }
+ return lazyToStringWithAddress;
+ //return ToStringWithAddress(_address);
}
+ private string lazyToStringWithAddress;
+
///
/// TBD
///
@@ -648,12 +729,24 @@ public string ToSerializationFormatWithAddress(Address address)
return result;
}
- private string AppendUidFragment(string withAddress)
+ private string lazyDefaultAddress;
+ public string ToSerializationFormatWithDefaultAddress(Address address)
{
- if (Uid == ActorCell.UndefinedUid)
- return withAddress;
+ if (lazyDefaultAddress == null)
+ {
+ lazyDefaultAddress = ToSerializationFormatWithAddress(address);
+ }
+ return lazyDefaultAddress;
+ }
- return String.Concat(withAddress, "#", Uid.ToString());
+ private string AppendUidFragment(string withAddress)
+ {
+ return _uid != ActorCell.UndefinedUid
+ ?
+ //string.Concat(withAddress,"#",Uid.ToString())
+ string.Concat(withAddress, "#", Uid.ToString())
+ //$"{withAddress}#{_uid}"
+ : withAddress;
}
///
@@ -670,10 +763,10 @@ public string ToStringWithAddress(Address address)
// we never change address for IgnoreActorRef
return ToString();
}
- if (Address.Host != null && Address.Port.HasValue)
- return $"{Address}{Join()}";
+ if (_address.Host != null && _address.Port.HasValue)
+ return Join(_address.ToString().AsSpan());
- return $"{address}{Join()}";
+ return Join(address.ToString().AsSpan());
}
///
@@ -683,7 +776,7 @@ public string ToStringWithAddress(Address address)
/// TBD
public static string FormatPathElements(IEnumerable pathElements)
{
- return String.Join("/", pathElements);
+ return string.Join("/", pathElements);
}
///
@@ -700,7 +793,7 @@ public ISurrogate ToSurrogate(ActorSystem system)
///
/// Actor paths for root guardians, such as "/user" and "/system"
///
- public class RootActorPath : ActorPath
+ public sealed class RootActorPath : ActorPath
{
///
/// Initializes a new instance of the class.
@@ -712,39 +805,13 @@ public RootActorPath(Address address, string name = "")
{
}
- ///
- public override ActorPath Parent => null;
-
- public override IReadOnlyList Elements => EmptyElements;
-
- ///
- [JsonIgnore]
- public override ActorPath Root => this;
-
- ///
- public override ActorPath WithUid(long uid)
- {
- if (uid == 0)
- return this;
- throw new NotSupportedException("RootActorPath must have undefined Uid");
- }
-
- ///
- public override int CompareTo(ActorPath other)
- {
- if (other is ChildActorPath) return 1;
- return Compare(ToString(), other.ToString(), StringComparison.Ordinal);
- }
}
///
/// Actor paths for child actors, which is to say any non-guardian actor.
///
- public class ChildActorPath : ActorPath
+ public sealed class ChildActorPath : ActorPath
{
- private readonly string _name;
- private readonly ActorPath _parent;
-
///
/// Initializes a new instance of the class.
///
@@ -754,87 +821,6 @@ public class ChildActorPath : ActorPath
public ChildActorPath(ActorPath parentPath, string name, long uid)
: base(parentPath, name, uid)
{
- _name = name;
- _parent = parentPath;
- }
-
- ///
- public override ActorPath Parent => _parent;
-
- public override IReadOnlyList Elements
- {
- get
- {
- ActorPath p = this;
- var acc = new Stack();
- while (true)
- {
- if (p is RootActorPath)
- return acc.ToList();
- acc.Push(p.Name);
- p = p.Parent;
- }
- }
- }
-
- ///
- public override ActorPath Root
- {
- get
- {
- var current = _parent;
- while (current is ChildActorPath child)
- {
- current = child._parent;
- }
- return current.Root;
- }
- }
-
- ///
- /// Creates a copy of the given ActorPath and applies a new Uid
- ///
- /// The uid.
- /// ActorPath.
- public override ActorPath WithUid(long uid)
- {
- if (uid == Uid)
- return this;
- return new ChildActorPath(_parent, _name, uid);
- }
-
- ///
- public override int GetHashCode()
- {
- unchecked
- {
- var hash = 17;
- hash = (hash * 23) ^ Address.GetHashCode();
- for (ActorPath p = this; p != null; p = p.Parent)
- hash = (hash * 23) ^ p.Name.GetHashCode();
- return hash;
- }
- }
-
- ///
- public override int CompareTo(ActorPath other)
- {
- return InternalCompareTo(this, other);
- }
-
- private int InternalCompareTo(ActorPath left, ActorPath right)
- {
- if (ReferenceEquals(left, right)) return 0;
- var leftRoot = left as RootActorPath;
- if (leftRoot != null)
- return leftRoot.CompareTo(right);
- var rightRoot = right as RootActorPath;
- if (rightRoot != null)
- return -rightRoot.CompareTo(left);
- var nameCompareResult = Compare(left.Name, right.Name, StringComparison.Ordinal);
- if (nameCompareResult != 0)
- return nameCompareResult;
- return InternalCompareTo(left.Parent, right.Parent);
}
}
}
diff --git a/src/core/Akka/Actor/ActorRef.cs b/src/core/Akka/Actor/ActorRef.cs
index 7208047ff44..266c66f6a9e 100644
--- a/src/core/Akka/Actor/ActorRef.cs
+++ b/src/core/Akka/Actor/ActorRef.cs
@@ -64,12 +64,17 @@ public interface IRepointableRef : IActorRefScope
bool IsStarted { get; }
}
+ public abstract class FutureActorRef : MinimalActorRef
+ {
+
+ }
+
///
/// INTERNAL API.
///
/// ActorRef implementation used for one-off tasks.
///
- public sealed class FutureActorRef : MinimalActorRef
+ public sealed class FutureActorRef : FutureActorRef
{
private readonly TaskCompletionSource _result;
private readonly ActorPath _path;
@@ -509,9 +514,16 @@ public override IInternalActorRef Parent
///
public override IActorRef GetChild(IReadOnlyList name)
{
- if (name.All(x => string.IsNullOrEmpty(x)))
- return this;
- return ActorRefs.Nobody;
+ foreach (var s in name)
+ {
+ if (string.IsNullOrEmpty(s) == false)
+ return ActorRefs.Nobody;
+ }
+
+ return this;
+ //if (name.All(x => string.IsNullOrEmpty(x)))
+ // return this;
+ //return ActorRefs.Nobody;
}
///
diff --git a/src/core/Akka/Actor/ActorRefFactoryShared.cs b/src/core/Akka/Actor/ActorRefFactoryShared.cs
index 032b263a3dc..067f62586aa 100644
--- a/src/core/Akka/Actor/ActorRefFactoryShared.cs
+++ b/src/core/Akka/Actor/ActorRefFactoryShared.cs
@@ -64,8 +64,7 @@ public static ActorSelection ActorSelection(string path, ActorSystem system, IAc
if(Uri.IsWellFormedUriString(path, UriKind.Absolute))
{
- ActorPath actorPath;
- if(!ActorPath.TryParse(path, out actorPath))
+ if(!ActorPath.TryParse(path, out var actorPath))
return new ActorSelection(provider.DeadLetters, "");
var actorRef = provider.RootGuardianAt(actorPath.Address);
diff --git a/src/core/Akka/Actor/ActorRefProvider.cs b/src/core/Akka/Actor/ActorRefProvider.cs
index aa6e21adb95..efd4c77ef26 100644
--- a/src/core/Akka/Actor/ActorRefProvider.cs
+++ b/src/core/Akka/Actor/ActorRefProvider.cs
@@ -417,9 +417,9 @@ public void Init(ActorSystemImpl system)
/// TBD
public IActorRef ResolveActorRef(string path)
{
- ActorPath actorPath;
- if (ActorPath.TryParse(path, out actorPath) && actorPath.Address == _rootPath.Address)
+ if (ActorPath.TryParse(path, out var actorPath) && actorPath.Address == _rootPath.Address)
return ResolveActorRef(_rootGuardian, actorPath.Elements);
+
_log.Debug("Resolve of unknown path [{0}] failed. Invalid format.", path);
return _deadLetters;
}
diff --git a/src/core/Akka/Actor/ActorSelection.cs b/src/core/Akka/Actor/ActorSelection.cs
index 218b3c2838f..a13dcca96e5 100644
--- a/src/core/Akka/Actor/ActorSelection.cs
+++ b/src/core/Akka/Actor/ActorSelection.cs
@@ -63,7 +63,7 @@ public ActorSelection(IActorRef anchor, SelectionPathElement[] path)
/// The anchor.
/// The path.
public ActorSelection(IActorRef anchor, string path)
- : this(anchor, path == "" ? new string[] { } : path.Split('/'))
+ : this(anchor, path == "" ? Array.Empty() : path.Split('/'))
{
}
@@ -77,8 +77,8 @@ public ActorSelection(IActorRef anchor, IEnumerable elements)
Anchor = anchor;
var list = new List();
- var count = elements.Count(); // shouldn't have a multiple enumeration issue\
- var i = 0;
+ var hasDoubleWildcard = false;
+
foreach (var s in elements)
{
switch (s)
@@ -86,10 +86,9 @@ public ActorSelection(IActorRef anchor, IEnumerable elements)
case null:
case "":
break;
- case "**":
- if (i < count-1)
- throw new IllegalActorNameException("Double wildcard can only appear at the last path entry");
+ case "**":
list.Add(SelectChildRecursive.Instance);
+ hasDoubleWildcard = true;
break;
case string e when e.Contains("?") || e.Contains("*"):
list.Add(new SelectChildPattern(e));
@@ -101,10 +100,11 @@ public ActorSelection(IActorRef anchor, IEnumerable elements)
list.Add(new SelectChildName(s));
break;
}
-
- i++;
}
+ if(hasDoubleWildcard && list[list.Count-1] != SelectChildRecursive.Instance)
+ throw new IllegalActorNameException("Double wildcard can only appear at the last path entry");
+
Path = list.ToArray();
}
@@ -164,10 +164,7 @@ private async Task InnerResolveOne(TimeSpan timeout, CancellationToke
try
{
var identity = await this.Ask(new Identify(null), timeout, ct).ConfigureAwait(false);
- if (identity.Subject == null)
- throw new ActorNotFoundException("subject was null");
-
- return identity.Subject;
+ return identity.Subject ?? throw new ActorNotFoundException("subject was null");
}
catch (Exception ex)
{
diff --git a/src/core/Akka/Actor/Address.cs b/src/core/Akka/Actor/Address.cs
index 81398855cc6..54ec23bfa22 100644
--- a/src/core/Akka/Actor/Address.cs
+++ b/src/core/Akka/Actor/Address.cs
@@ -65,6 +65,7 @@ public int Compare(Address x, Address y)
private readonly int? _port;
private readonly string _system;
private readonly string _protocol;
+ private int _hashCode;
///
/// TBD
@@ -160,14 +161,19 @@ public bool Equals(Address other)
///
public override int GetHashCode()
{
- unchecked
+ if (_hashCode == 0)
{
- var hashCode = (Host != null ? Host.GetHashCode() : 0);
- hashCode = (hashCode * 397) ^ Port.GetHashCode();
- hashCode = (hashCode * 397) ^ (System != null ? System.GetHashCode() : 0);
- hashCode = (hashCode * 397) ^ (Protocol != null ? Protocol.GetHashCode() : 0);
- return hashCode;
+ unchecked
+ {
+ var hashCode = (Host != null ? Host.GetHashCode() : 0);
+ hashCode = (hashCode * 397) ^ Port.GetHashCode();
+ hashCode = (hashCode * 397) ^ (System != null ? System.GetHashCode() : 0);
+ hashCode = (hashCode * 397) ^ (Protocol != null ? Protocol.GetHashCode() : 0);
+ _hashCode = hashCode;
+ }
}
+
+ return _hashCode;
}
int IComparable.CompareTo(object obj)
@@ -246,7 +252,7 @@ public Address WithPort(int? port = null)
/// true if both addresses are equal; otherwise false
public static bool operator ==(Address left, Address right)
{
- return left?.Equals(right) ?? ReferenceEquals(right, null);
+ return left?.Equals(right) ?? right is null;
}
///
@@ -299,11 +305,102 @@ public static Address Parse(string address)
}
}
+ ///
+ /// Parses a new from a given string
+ ///
+ /// The span of address to parse
+ /// If true, the parsed . Otherwise null.
+ /// true if the could be parsed, false otherwise.
+ public static bool TryParse(ReadOnlySpan span, out Address address)
+ {
+ address = default;
+
+ var firstColonPos = span.IndexOf(':');
+
+ if (firstColonPos == -1) // not an absolute Uri
+ return false;
+
+ if (firstColonPos < 4 || 255 < firstColonPos)
+ {
+ //invalid scheme length
+ return false;
+ }
+
+ Span fullScheme = stackalloc char[firstColonPos];
+ span.Slice(0, firstColonPos).ToLowerInvariant(fullScheme);
+ if (!fullScheme.StartsWith("akka".AsSpan()))
+ {
+ //invalid scheme
+ return false;
+ }
+
+ span = span.Slice(firstColonPos + 1);
+ if (span.StartsWith("//".AsSpan()) == false)
+ return false;
+
+ span = span.Slice(2); // move past the double //
+ var firstAtPos = span.IndexOf('@');
+
+
+ if (firstAtPos == -1)
+ {
+ // dealing with an absolute local Uri
+ address = new Address(fullScheme.ToString(), span.ToString());
+ return true;
+ }
+ // dealing with a remote Uri
+ string sysName = span.Slice(0, firstAtPos).ToString();
+ span = span.Slice(firstAtPos + 1);
+
+ /*
+ * Need to check for:
+ * - IPV4 / hostnames
+ * - IPV6 (must be surrounded by '[]') according to spec.
+ */
+ string host;
+
+ // check for IPV6 first
+ var openBracket = span.IndexOf('[');
+ var closeBracket = span.IndexOf(']');
+ if (openBracket > -1 && closeBracket > openBracket)
+ {
+ // found an IPV6 address
+ host = span.Slice(openBracket, closeBracket - openBracket + 1).ToString();
+ span = span.Slice(closeBracket + 1); // advance past the address
+
+ // need to check for trailing colon
+ var secondColonPos = span.IndexOf(':');
+ if (secondColonPos == -1)
+ return false;
+
+ span = span.Slice(secondColonPos + 1);
+ }
+ else
+ {
+ var secondColonPos = span.IndexOf(':');
+ if (secondColonPos == -1)
+ return false;
+
+ host = span.Slice(0, secondColonPos).ToString();
+
+ // move past the host
+ span = span.Slice(secondColonPos + 1);
+ }
+
+ if (SpanHacks.TryParse(span, out var port) && port >= 0)
+ {
+ address = new Address(fullScheme.ToString(), sysName, host, port);
+ return true;
+ }
+
+ return false;
+ }
+
///
/// This class represents a surrogate of an .
/// Its main use is to help during the serialization process.
///
- public class AddressSurrogate : ISurrogate
+ public sealed class AddressSurrogate : ISurrogate
{
///
/// TBD
diff --git a/src/core/Akka/Actor/Futures.cs b/src/core/Akka/Actor/Futures.cs
index c49cc8b74e4..fbb84507bfb 100644
--- a/src/core/Akka/Actor/Futures.cs
+++ b/src/core/Akka/Actor/Futures.cs
@@ -178,14 +178,14 @@ public static Task Ask(this ICanTell self, Func message
/// Provider used for Ask pattern implementation
internal static IActorRefProvider ResolveProvider(ICanTell self)
{
- if (self is ActorSelection)
- return ResolveProvider(self.AsInstanceOf().Anchor);
+ if (self is ActorSelection selection)
+ return ResolveProvider(selection.Anchor);
- if (self is IInternalActorRef)
- return self.AsInstanceOf().Provider;
+ if (self is IInternalActorRef actorRef)
+ return actorRef.Provider;
- if (ActorCell.Current != null)
- return InternalCurrentActorCellKeeper.Current.SystemImpl.Provider;
+ if (ActorCell.Current is ActorCell cell)
+ return cell.SystemImpl.Provider;
return null;
}
diff --git a/src/core/Akka/Akka.csproj b/src/core/Akka/Akka.csproj
index 2f0d47d7804..4fcb3478bb7 100644
--- a/src/core/Akka/Akka.csproj
+++ b/src/core/Akka/Akka.csproj
@@ -4,10 +4,10 @@
Akka
Akka.NET is a port of the popular Java/Scala framework Akka to .NET
- $(NetStandardLibVersion)
$(AkkaPackageTags)
true
7.2
+ netstandard2.0
@@ -15,6 +15,7 @@
+
diff --git a/src/core/Akka/Dispatch/Dispatchers.cs b/src/core/Akka/Dispatch/Dispatchers.cs
index 19ae34f9c10..37a9ce33b68 100644
--- a/src/core/Akka/Dispatch/Dispatchers.cs
+++ b/src/core/Akka/Dispatch/Dispatchers.cs
@@ -261,8 +261,13 @@ public ForkJoinExecutor(string id, DedicatedThreadPoolSettings poolSettings) : b
public override void Execute(IRunnable run)
{
if (Volatile.Read(ref _shuttingDown) == 1)
- throw new RejectedExecutionException("ForkJoinExecutor is shutting down");
- _dedicatedThreadPool.QueueUserWorkItem(run.Run);
+ ThrowShutdownHelper();
+ _dedicatedThreadPool.QueueUserWorkItem(r=>r.Run(), run);
+ }
+
+ private static void ThrowShutdownHelper()
+ {
+ throw new RejectedExecutionException("ForkJoinExecutor is shutting down");
}
///
diff --git a/src/core/Akka/Dispatch/Mailbox.cs b/src/core/Akka/Dispatch/Mailbox.cs
index e253187c76c..f77b5379f01 100644
--- a/src/core/Akka/Dispatch/Mailbox.cs
+++ b/src/core/Akka/Dispatch/Mailbox.cs
@@ -352,11 +352,11 @@ public void Run()
{
if (!IsClosed()) // Volatile read, needed here
{
- Actor.UseThreadContext(() =>
+ Actor.UseThreadContext((state) =>
{
- ProcessAllSystemMessages(); // First, deal with any system messages
- ProcessMailbox(); // Then deal with messages
- });
+ state.ProcessAllSystemMessages(); // First, deal with any system messages
+ state.ProcessMailbox(); // Then deal with messages
+ },this);
}
}
finally
diff --git a/src/core/Akka/Helios.Concurrency.DedicatedThreadPool.cs b/src/core/Akka/Helios.Concurrency.DedicatedThreadPool.cs
index 497e27af097..b35d4ef3e76 100644
--- a/src/core/Akka/Helios.Concurrency.DedicatedThreadPool.cs
+++ b/src/core/Akka/Helios.Concurrency.DedicatedThreadPool.cs
@@ -14,9 +14,12 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
+using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
+using Akka.Dispatch;
+using Akka.Util;
namespace Helios.Concurrency
{
@@ -96,7 +99,7 @@ public DedicatedThreadPoolSettings(
///
/// TaskScheduler for working with a instance
///
- internal class DedicatedThreadPoolTaskScheduler : TaskScheduler
+ internal class DedicatedThreadPoolTaskScheduler : TaskScheduler, IRunnable
{
// Indicates whether the current thread is processing work items.
[ThreadStatic]
@@ -227,37 +230,70 @@ private void ReleaseWorker()
private void RequestWorker()
{
- _pool.QueueUserWorkItem(() =>
+ _pool.QueueUserWorkItem((t) =>
{
- // this thread is now available for inlining
- _currentThreadIsRunningTasks = true;
- try
+ t.Run();
+ //// this thread is now available for inlining
+ //_currentThreadIsRunningTasks = true;
+ //try
+ //{
+ // // Process all available items in the queue.
+ // while (true)
+ // {
+ // Task item;
+ // lock (_tasks)
+ // {
+ // // done processing
+ // if (_tasks.Count == 0)
+ // {
+ // ReleaseWorker();
+ // break;
+ // }
+ //
+ // // Get the next item from the queue
+ // item = _tasks.First.Value;
+ // _tasks.RemoveFirst();
+ // }
+ //
+ // // Execute the task we pulled out of the queue
+ // TryExecuteTask(item);
+ // }
+ //}
+ //// We're done processing items on the current thread
+ //finally { _currentThreadIsRunningTasks = false; }
+ },this);
+ }
+
+ void IRunnable.Run()
+ {
+ // this thread is now available for inlining
+ _currentThreadIsRunningTasks = true;
+ try
+ {
+ // Process all available items in the queue.
+ while (true)
{
- // Process all available items in the queue.
- while (true)
+ Task item;
+ lock (_tasks)
{
- Task item;
- lock (_tasks)
+ // done processing
+ if (_tasks.Count == 0)
{
- // done processing
- if (_tasks.Count == 0)
- {
- ReleaseWorker();
- break;
- }
-
- // Get the next item from the queue
- item = _tasks.First.Value;
- _tasks.RemoveFirst();
+ ReleaseWorker();
+ break;
}
- // Execute the task we pulled out of the queue
- TryExecuteTask(item);
+ // Get the next item from the queue
+ item = _tasks.First.Value;
+ _tasks.RemoveFirst();
}
+
+ // Execute the task we pulled out of the queue
+ TryExecuteTask(item);
}
- // We're done processing items on the current thread
- finally { _currentThreadIsRunningTasks = false; }
- });
+ }
+ // We're done processing items on the current thread
+ finally { _currentThreadIsRunningTasks = false; }
}
}
@@ -299,12 +335,17 @@ public DedicatedThreadPool(DedicatedThreadPoolSettings settings)
/// This exception is thrown if the given item is undefined.
///
/// TBD
- public bool QueueUserWorkItem(Action work)
+ public bool QueueUserWorkItem(Action work, IRunnable state)
{
if (work == null)
- throw new ArgumentNullException(nameof(work), "Work item cannot be null.");
+ ThrowNullWorkHelper(work);
+
+ return _workQueue.TryAdd(work,state);
+ }
- return _workQueue.TryAdd(work);
+ private static void ThrowNullWorkHelper(Action work)
+ {
+ throw new ArgumentNullException(nameof(work), "Work item cannot be null.");
}
///
@@ -365,11 +406,11 @@ private void RunThread()
{
try
{
- foreach (var action in _pool._workQueue.GetConsumingEnumerable())
+ foreach (var (action,state) in _pool._workQueue.GetConsumingEnumerable())
{
try
{
- action();
+ action(state);
}
catch (Exception ex)
{
@@ -393,8 +434,11 @@ private class ThreadPoolWorkQueue
private static readonly int ProcessorCount = Environment.ProcessorCount;
private const int CompletedState = 1;
- private readonly ConcurrentQueue _queue = new ConcurrentQueue();
- private readonly UnfairSemaphore _semaphore = new UnfairSemaphore();
+ private readonly ConcurrentQueue<(Action act, IRunnable state)>
+ _queue = new ConcurrentQueue<(Action act, IRunnable state)>();
+ //private readonly ConcurrentQueue _queue = new ConcurrentQueue();
+ //private readonly UnfairSemaphore _semaphore = new UnfairSemaphore();
+ private readonly UnfairSemaphoreV2 _semaphore = new UnfairSemaphoreV2();
private int _outstandingRequests;
private int _isAddingCompleted;
@@ -403,7 +447,8 @@ public bool IsAddingCompleted
get { return Volatile.Read(ref _isAddingCompleted) == CompletedState; }
}
- public bool TryAdd(Action work)
+ //public bool TryAdd(Action work)
+ public bool TryAdd(Action work, IRunnable state)
{
// If TryAdd returns true, it's guaranteed the work item will be executed.
// If it returns false, it's also guaranteed the work item won't be executed.
@@ -411,17 +456,17 @@ public bool TryAdd(Action work)
if (IsAddingCompleted)
return false;
- _queue.Enqueue(work);
+ _queue.Enqueue((work,state));
EnsureThreadRequested();
return true;
}
- public IEnumerable GetConsumingEnumerable()
+ public IEnumerable<(Action action, IRunnable state)> GetConsumingEnumerable()
{
while (true)
{
- Action work;
+ (Actionaction,IRunnable state) work;
if (_queue.TryDequeue(out work))
{
yield return work;
@@ -738,6 +783,366 @@ private SemaphoreState GetCurrentState()
return state;
}
}
+
+ [StructLayout(LayoutKind.Sequential)]
+ private sealed class UnfairSemaphoreV2
+ {
+ public const int MaxWorker = 0x7FFF;
+
+ private static readonly int ProcessorCount = Environment.ProcessorCount;
+
+ // We track everything we care about in a single 64-bit struct to allow us to
+ // do CompareExchanges on this for atomic updates.
+ private struct SemaphoreStateV2
+ {
+ private const byte CurrentSpinnerCountShift = 0;
+ private const byte CountForSpinnerCountShift = 16;
+ private const byte WaiterCountShift = 32;
+ private const byte CountForWaiterCountShift = 48;
+
+ //Ugh. So, Older versions of .NET,
+ //for whatever reason, don't have
+ //Interlocked compareexchange for ULong.
+ public long _data;
+
+ private SemaphoreStateV2(ulong data)
+ {
+ unchecked
+ {
+ _data = (long)data;
+ }
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void AddSpinners(ushort value)
+ {
+ Debug.Assert(value <= uint.MaxValue - Spinners);
+ unchecked
+ {
+ _data += (long)value << CurrentSpinnerCountShift;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void DecrSpinners(ushort value)
+ {
+ Debug.Assert(value >= ushort.MinValue + Spinners);
+ unchecked
+ {
+ _data -= (long)value << CurrentSpinnerCountShift;
+ }
+ }
+
+ private uint GetUInt32Value(byte shift) => (uint)(_data >> shift);
+ private void SetUInt32Value(uint value, byte shift)
+ {
+ unchecked
+ {
+ _data = (_data & ~((long)uint.MaxValue << shift)) | ((long)value << shift);
+ }
+
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private ushort GetUInt16Value(byte shift) => (ushort)(_data >> shift);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void SetUInt16Value(ushort value, byte shift)
+ {
+ unchecked
+ {
+ _data = (_data & ~((long)ushort.MaxValue << shift)) | ((long)value << shift);
+ }
+
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private byte GetByteValue(byte shift) => (byte)(_data >> shift);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void SetByteValue(byte value, byte shift)
+ {
+ unchecked
+ {
+ _data = (_data & ~((long)byte.MaxValue << shift)) | ((long)value << shift);
+ }
+
+ }
+
+
+ //how many threads are currently spin-waiting for this semaphore?
+ public ushort Spinners
+ {
+ get { return GetUInt16Value(CurrentSpinnerCountShift); }
+ //set{SetUInt16Value(value,CurrentSpinnerCountShift);}
+
+ }
+
+ //how much of the semaphore's count is available to spinners?
+ //[FieldOffset(2)]
+ public ushort CountForSpinners
+ {
+ get { return GetUInt16Value(CountForSpinnerCountShift); }
+ //set{SetUInt16Value(value,CountForSpinnerCountShift);}
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void IncrementCountForSpinners(ushort count)
+ {
+ Debug.Assert(CountForSpinners+count < ushort.MaxValue);
+ unchecked
+ {
+ _data += (long)count << CountForSpinnerCountShift;
+ }
+
+ }
+
+ public void DecrementCountForSpinners()
+ {
+ Debug.Assert(CountForSpinners != 0);
+ unchecked
+ {
+ _data -= (long)1 << CountForSpinnerCountShift;
+ }
+
+ }
+
+
+ //how many threads are blocked in the OS waiting for this semaphore?
+ public ushort Waiters
+ {
+ get { return GetUInt16Value(WaiterCountShift); }
+ //set{SetUInt16Value(value,WaiterCountShift);}
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void AddWaiters(ushort value)
+ {
+ Debug.Assert(value <= uint.MaxValue - Waiters);
+ unchecked
+ {
+ _data += (long)value << WaiterCountShift;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void DecrWaiters(ushort value)
+ {
+ Debug.Assert(value >= ushort.MinValue + Waiters);
+ unchecked
+ {
+ _data -= (long)value << WaiterCountShift;
+ }
+ }
+ //how much count is available to waiters?
+ public ushort CountForWaiters
+ {
+ get { return GetUInt16Value(CountForWaiterCountShift); }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void IncrCountForWaiters(ushort value)
+ {
+ Debug.Assert(value <= ushort.MaxValue + CountForWaiters);
+ unchecked
+ {
+ _data += (long)value << CountForWaiterCountShift;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void DecrCountForWaiters(ushort value)
+ {
+ Debug.Assert(value >= ushort.MinValue + CountForWaiters);
+ unchecked
+ {
+ _data -= (long)value << CountForWaiterCountShift;
+ }
+ }
+ }
+
+ [StructLayout(LayoutKind.Explicit, Size = 64)]
+ private struct CacheLinePadding
+ { }
+
+ private readonly Semaphore m_semaphore;
+
+ // padding to ensure we get our own cache line
+#pragma warning disable 169
+ private readonly CacheLinePadding m_padding1;
+ private SemaphoreStateV2 m_state;
+ private readonly CacheLinePadding m_padding2;
+#pragma warning restore 169
+
+ public UnfairSemaphoreV2()
+ {
+ m_semaphore = new Semaphore(0, short.MaxValue);
+ }
+
+ public bool Wait()
+ {
+ return Wait(Timeout.InfiniteTimeSpan);
+ }
+
+ public bool Wait(TimeSpan timeout)
+ {
+ while (true)
+ {
+ SemaphoreStateV2 currentCounts = GetCurrentState();
+ SemaphoreStateV2 newCounts = currentCounts;
+
+ // First, just try to grab some count.
+ if (currentCounts.CountForSpinners > 0)
+ {
+ newCounts.DecrementCountForSpinners();
+ if (TryUpdateState(newCounts, currentCounts))
+ return true;
+ }
+ else
+ {
+ // No count available, become a spinner
+ newCounts.AddSpinners(1);
+ if (TryUpdateState(newCounts, currentCounts))
+ break;
+ }
+ }
+
+ //
+ // Now we're a spinner.
+ //
+ int numSpins = 0;
+ const int spinLimitPerProcessor = 50;
+ while (true)
+ {
+ SemaphoreStateV2 currentCounts = GetCurrentState();
+ SemaphoreStateV2 newCounts = currentCounts;
+
+ if (currentCounts.CountForSpinners > 0)
+ {
+ newCounts.DecrementCountForSpinners();
+ newCounts.DecrSpinners(1);
+ if (TryUpdateState(newCounts, currentCounts))
+ return true;
+ }
+ else
+ {
+ double spinnersPerProcessor = (double)currentCounts.Spinners / ProcessorCount;
+ int spinLimit = (int)((spinLimitPerProcessor / spinnersPerProcessor) + 0.5);
+ if (numSpins >= spinLimit)
+ {
+ newCounts.DecrSpinners(1);
+ newCounts.AddWaiters(1);
+ if (TryUpdateState(newCounts, currentCounts))
+ break;
+ }
+ else
+ {
+ //
+ // We yield to other threads using Thread.Sleep(0) rather than the more traditional Thread.Yield().
+ // This is because Thread.Yield() does not yield to threads currently scheduled to run on other
+ // processors. On a 4-core machine, for example, this means that Thread.Yield() is only ~25% likely
+ // to yield to the correct thread in some scenarios.
+ // Thread.Sleep(0) has the disadvantage of not yielding to lower-priority threads. However, this is ok because
+ // once we've called this a few times we'll become a "waiter" and wait on the Semaphore, and that will
+ // yield to anything that is runnable.
+ //
+ Thread.Sleep(0);
+ numSpins++;
+ }
+ }
+ }
+
+ //
+ // Now we're a waiter
+ //
+ bool waitSucceeded = m_semaphore.WaitOne(timeout);
+
+ while (true)
+ {
+ SemaphoreStateV2 currentCounts = GetCurrentState();
+ SemaphoreStateV2 newCounts = currentCounts;
+
+ newCounts.DecrWaiters(1);
+
+ if (waitSucceeded)
+ newCounts.DecrCountForWaiters(1);
+
+ if (TryUpdateState(newCounts, currentCounts))
+ return waitSucceeded;
+ }
+ }
+
+ public void Release()
+ {
+ Release(1);
+ }
+
+ public void Release(short count)
+ {
+ while (true)
+ {
+ SemaphoreStateV2 currentState = GetCurrentState();
+ SemaphoreStateV2 newState = currentState;
+
+ ushort remainingCount = (ushort)count;
+
+ // First, prefer to release existing spinners,
+ // because a) they're hot, and b) we don't need a kernel
+ // transition to release them.
+
+ ushort spinnersToRelease = (ushort)Math.Max((short)0, Math.Min(remainingCount, (short)(currentState.Spinners - currentState.CountForSpinners)));
+ newState.IncrementCountForSpinners((ushort)spinnersToRelease);// .CountForSpinners = (ushort)(newState.CountForSpinners + spinnersToRelease);
+ remainingCount -= spinnersToRelease;
+
+ // Next, prefer to release existing waiters
+ ushort waitersToRelease = (ushort)Math.Max((short)0, Math.Min(remainingCount, (short)(currentState.Waiters - currentState.CountForWaiters)));
+ newState.IncrCountForWaiters((ushort)waitersToRelease);// .CountForWaiters = (ushort)(newState.CountForWaiters+ waitersToRelease);
+ remainingCount -= waitersToRelease;
+
+ // Finally, release any future spinners that might come our way
+ newState.IncrementCountForSpinners((ushort)remainingCount);
+
+ // Try to commit the transaction
+ if (TryUpdateState(newState, currentState))
+ {
+ // Now we need to release the waiters we promised to release
+ if (waitersToRelease > 0)
+ m_semaphore.Release(waitersToRelease);
+
+ break;
+ }
+ }
+ }
+
+ private bool TryUpdateState(SemaphoreStateV2 newState, SemaphoreStateV2 currentState)
+ {
+ if (Interlocked.CompareExchange(ref m_state._data, newState._data, currentState._data) == currentState._data)
+ {
+ Debug.Assert(newState.CountForSpinners <= MaxWorker, "CountForSpinners is greater than MaxWorker");
+ Debug.Assert(newState.CountForSpinners >= 0, "CountForSpinners is lower than zero");
+ Debug.Assert(newState.Spinners <= MaxWorker, "Spinners is greater than MaxWorker");
+ Debug.Assert(newState.Spinners >= 0, "Spinners is lower than zero");
+ Debug.Assert(newState.CountForWaiters <= MaxWorker, "CountForWaiters is greater than MaxWorker");
+ Debug.Assert(newState.CountForWaiters >= 0, "CountForWaiters is lower than zero");
+ Debug.Assert(newState.Waiters <= MaxWorker, "Waiters is greater than MaxWorker");
+ Debug.Assert(newState.Waiters >= 0, "Waiters is lower than zero");
+ Debug.Assert(newState.CountForSpinners + newState.CountForWaiters <= MaxWorker, "CountForSpinners + CountForWaiters is greater than MaxWorker");
+
+ return true;
+ }
+
+ return false;
+ }
+
+ private SemaphoreStateV2 GetCurrentState()
+ {
+ // Volatile.Read of a long can get a partial read in x86 but the invalid
+ // state will be detected in TryUpdateState with the CompareExchange.
+
+ SemaphoreStateV2 state = new SemaphoreStateV2();
+ state._data = Volatile.Read(ref m_state._data);
+ return state;
+ }
+ }
#endregion
}
diff --git a/src/core/Akka/Serialization/NewtonSoftJsonSerializer.cs b/src/core/Akka/Serialization/NewtonSoftJsonSerializer.cs
index 274c873a4c5..7b35b6189d3 100644
--- a/src/core/Akka/Serialization/NewtonSoftJsonSerializer.cs
+++ b/src/core/Akka/Serialization/NewtonSoftJsonSerializer.cs
@@ -7,6 +7,7 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Reflection;
@@ -159,6 +160,7 @@ public NewtonSoftJsonSerializer(ExtendedActorSystem system, NewtonSoftJsonSerial
if (system != null)
{
+ _serializer.Deserialize()
var settingsSetup = system.Settings.Setup.Get()
.GetOrElse(NewtonSoftJsonSerializerSetup.Create(s => {}));
diff --git a/src/core/Akka/Serialization/Serialization.cs b/src/core/Akka/Serialization/Serialization.cs
index 41a2d708d00..eabf8f7c21f 100644
--- a/src/core/Akka/Serialization/Serialization.cs
+++ b/src/core/Akka/Serialization/Serialization.cs
@@ -19,6 +19,8 @@
using Akka.Util.Internal;
using Akka.Util.Reflection;
using Akka.Configuration;
+using LanguageExt;
+using LanguageExt.TypeClasses;
namespace Akka.Serialization
{
@@ -142,8 +144,44 @@ public static T WithTransport(ActorSystem system, Address address, Func ac
private readonly Serializer _nullSerializer;
- private readonly ConcurrentDictionary _serializerMap = new ConcurrentDictionary();
- private readonly Dictionary _serializersById = new Dictionary();
+ private class TypeEqualityComparer : IEqualityComparer
+ {
+ public static readonly TypeEqualityComparer Default =
+ new TypeEqualityComparer();
+
+ public bool Equals(Type x, Type y)
+ {
+ return x == y;
+ }
+
+ public int GetHashCode(Type obj)
+ {
+ return obj.GetHashCode();
+ }
+ }
+ private struct TypeEq : Eq
+ {
+ public int GetHashCode(Type x)
+ {
+ if (x is null)
+ {
+ return 0;
+ }
+ return x.GetHashCode();
+ }
+
+ public bool Equals(Type x, Type y)
+ {
+ return (x == y) ;
+ }
+ }
+
+ //private HashMap _serializerMap =
+ // new HashMap();
+ private readonly ConcurrentDictionary _serializerMap = new ConcurrentDictionary(TypeEqualityComparer.Default);
+
+ private readonly Serializer[] _serializersById = new Serializer[1024];
+ //private readonly Dictionary _serializersById = new Dictionary();
private readonly Dictionary _serializersByName = new Dictionary();
private readonly ImmutableHashSet _serializerDetails;
@@ -326,7 +364,8 @@ private Serializer GetSerializerByName(string name)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddSerializer(Serializer serializer)
{
- _serializersById.Add(serializer.Identifier, serializer);
+ _serializersById[serializer.Identifier + 255] = serializer;
+ //_serializersById.Add(serializer.Identifier, serializer);
}
///
@@ -337,7 +376,8 @@ public void AddSerializer(Serializer serializer)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddSerializer(string name, Serializer serializer)
{
- _serializersById.Add(serializer.Identifier, serializer);
+ _serializersById[serializer.Identifier + 255] = serializer;
+ //_serializersById.Add(serializer.Identifier, serializer);
_serializersByName.Add(name, serializer);
}
@@ -350,6 +390,7 @@ public void AddSerializer(string name, Serializer serializer)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddSerializationMap(Type type, Serializer serializer)
{
+ //_serializerMap = _serializerMap.AddOrUpdate(type, serializer);
_serializerMap[type] = serializer;
}
@@ -377,7 +418,9 @@ public object Deserialize(byte[] bytes, int serializerId, Type type)
{
return WithTransport(() =>
{
- if (!_serializersById.TryGetValue(serializerId, out var serializer))
+ var serializer = _serializersById[serializerId + 255];
+ if (serializer == null)
+ //if (!_serializersById.TryGetValue(serializerId, out var serializer))
throw new SerializationException(
$"Cannot find serializer with id [{serializerId}] (class [{type?.Name}]). The most probable reason" +
" is that the configuration entry 'akka.actor.serializers' is not in sync between the two systems.");
@@ -399,10 +442,10 @@ public object Deserialize(byte[] bytes, int serializerId, Type type)
/// The resulting object
public object Deserialize(byte[] bytes, int serializerId, string manifest)
{
- if (!_serializersById.TryGetValue(serializerId, out var serializer))
- throw new SerializationException(
- $"Cannot find serializer with id [{serializerId}] (manifest [{manifest}]). The most probable reason" +
- " is that the configuration entry 'akka.actor.serializers' is not in sync between the two systems.");
+ var serializer = _serializersById[serializerId + 255];
+ if (serializer == null)
+ //if (!_serializersById.TryGetValue(serializerId, out var serializer))
+ ThrowCannotFindSerializerHelper(serializerId, manifest);
// not using `withTransportInformation { () =>` because deserializeByteBuffer is supposed to be the
// possibility for allocation free serialization
@@ -435,6 +478,14 @@ public object Deserialize(byte[] bytes, int serializerId, string manifest)
}
}
+ private static void ThrowCannotFindSerializerHelper(int serializerId,
+ string manifest)
+ {
+ throw new SerializationException(
+ $"Cannot find serializer with id [{serializerId}] (manifest [{manifest}]). The most probable reason" +
+ " is that the configuration entry 'akka.actor.serializers' is not in sync between the two systems.");
+ }
+
///
/// Returns the Serializer configured for the given object, returns the NullSerializer if it's null.
///
@@ -464,6 +515,8 @@ public Serializer FindSerializerFor(object obj, string defaultSerializerName = n
/// The serializer configured for the given object type
public Serializer FindSerializerForType(Type objectType, string defaultSerializerName = null)
{
+ //var fullMatchSerializer = _serializerMap.Find(objectType).IfNoneUnsafe((Serializer)null);
+ //if (fullMatchSerializer != null)
if (_serializerMap.TryGetValue(objectType, out var fullMatchSerializer))
return fullMatchSerializer;
@@ -486,6 +539,8 @@ public Serializer FindSerializerForType(Type objectType, string defaultSerialize
// do a final check for the "object" serializer
if (serializer == null)
+ //serializer = _serializerMap.Find(_objectType)
+ // .IfNoneUnsafe((Serializer)null);
_serializerMap.TryGetValue(_objectType, out serializer);
if (serializer == null)
@@ -557,7 +612,8 @@ public static string SerializedActorPath(IActorRef actorRef)
internal Serializer GetSerializerById(int serializerId)
{
- return _serializersById[serializerId];
+ return _serializersById[serializerId+255];
+ //return _serializersById[serializerId];
}
}
}
diff --git a/src/core/Akka/Util/FastLazy.cs b/src/core/Akka/Util/FastLazy.cs
index 3b234f65f36..4f28eb1fcf8 100644
--- a/src/core/Akka/Util/FastLazy.cs
+++ b/src/core/Akka/Util/FastLazy.cs
@@ -67,19 +67,25 @@ public T Value
{
if (IsValueCreatedInternal())
return _createdValue;
- if (!IsValueCreationInProgress())
- {
- Volatile.Write(ref _creating, 1);
- _createdValue = _producer();
- Volatile.Write(ref _created, 1);
- }
- else
- {
- SpinWait.SpinUntil(IsValueCreatedInternal);
- }
- return _createdValue;
+ return ReturnCreation();
}
}
+
+ private T ReturnCreation()
+ {
+ if (!IsValueCreationInProgress())
+ {
+ Volatile.Write(ref _creating, 1);
+ _createdValue = _producer();
+ Volatile.Write(ref _created, 1);
+ }
+ else
+ {
+ SpinWait.SpinUntil(IsValueCreatedInternal);
+ }
+
+ return _createdValue;
+ }
}
diff --git a/src/core/Akka/Util/Internal/Collections/ListSlice.cs b/src/core/Akka/Util/Internal/Collections/ListSlice.cs
index 5614b6a05b7..c70197642a7 100644
--- a/src/core/Akka/Util/Internal/Collections/ListSlice.cs
+++ b/src/core/Akka/Util/Internal/Collections/ListSlice.cs
@@ -89,11 +89,7 @@ public void Dispose()
public ListSlice(IReadOnlyList array)
{
-
- if (array == null)
- throw new ArgumentNullException(nameof(array));
-
- _array = array;
+ _array = array ?? throw new ArgumentNullException(nameof(array));
Offset = 0;
Count = array.Count;
}
diff --git a/src/core/Akka/Util/Reflection/TypeCache.cs b/src/core/Akka/Util/Reflection/TypeCache.cs
index a9715891f44..f871d420eae 100644
--- a/src/core/Akka/Util/Reflection/TypeCache.cs
+++ b/src/core/Akka/Util/Reflection/TypeCache.cs
@@ -32,7 +32,7 @@ public static class TypeCache
/// TBD
public static Type GetType(string typeName)
{
- return TypeMap.GetOrAdd(typeName, GetTypeInternal);
+ return TypeMap.GetOrAdd(typeName,(val)=>Type.GetType(val, true));
}
private static Type GetTypeInternal(string typeName)
diff --git a/src/core/Akka/Util/SpanHacks.cs b/src/core/Akka/Util/SpanHacks.cs
index 08464610126..61a6a3506bf 100644
--- a/src/core/Akka/Util/SpanHacks.cs
+++ b/src/core/Akka/Util/SpanHacks.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Runtime.InteropServices;
using System.Text;
namespace Akka.Util
@@ -31,6 +32,26 @@ public static int Parse(ReadOnlySpan str)
throw new FormatException($"[{str.ToString()}] is now a valid numeric format");
}
+ private static readonly char[] numStrings = new[]
+ {
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
+ };
+ public static string ToString(long number)
+ {
+ return number.ToString();
+ Span charArr = stackalloc char[19];
+ int length = 1;
+ long curr = 0;
+ long theNum = number;
+ do
+ {
+ curr = theNum % 10;
+ charArr[19 - length] = numStrings[theNum % 10];
+ theNum = theNum / 10;
+ } while (curr != 0);
+
+ }
+
///
/// Parses an integer from a string.
///
diff --git a/src/examples/AspNetCore/Samples.Akka.AspNetCore/Startup.cs b/src/examples/AspNetCore/Samples.Akka.AspNetCore/Startup.cs
index e76a2feb220..77c84bb8add 100644
--- a/src/examples/AspNetCore/Samples.Akka.AspNetCore/Startup.cs
+++ b/src/examples/AspNetCore/Samples.Akka.AspNetCore/Startup.cs
@@ -20,6 +20,77 @@
namespace Samples.Akka.AspNetCore
{
+ public interface IStartupTaskTokenContext : IStartupTaskContext
+ {
+ public string[] tokenChecks { get; }
+ }
+ public interface IStartupTaskContext
+ {
+
+ public bool IsComplete { get; }
+ public int RetryAfterSeconds { get; }
+ }
+
+ public sealed class
+ PathFilteringStartupTasksFilterMiddleWare : BaseStartupTasksFilterMiddleware
+ where T : IStartupTaskTokenContext
+ {
+ public PathFilteringStartupTasksFilterMiddleWare(T context, RequestDelegate next) : base(context, next)
+ {
+ }
+ public override bool shouldCheck(HttpContext context)
+ {
+ if (context.Request.Path.HasValue && hasPath(context.Request.Path))
+ {
+ return true;
+ }
+
+ return false;
+ }
+ public bool hasPath(string path)
+ {
+ var tokens = _context.tokenChecks;
+ if (tokens != null)
+ {
+ foreach (var token in tokens)
+ {
+ if (path.Contains(token, StringComparison.InvariantCultureIgnoreCase))
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+ public abstract class BaseStartupTasksFilterMiddleware where T: IStartupTaskContext
+ {
+ protected readonly T _context;
+ protected readonly RequestDelegate _next;
+
+ public BaseStartupTasksFilterMiddleware(T context, RequestDelegate next)
+ {
+ _context = context;
+ _next = next;
+ }
+
+ public abstract bool shouldCheck(HttpContext context);
+
+
+ public async Task Invoke(HttpContext httpContext)
+ {
+ if (shouldCheck(httpContext) == false || _context.IsComplete)
+ {
+ await _next(httpContext);
+ }
+ else
+ {
+ var response = httpContext.Response;
+ response.StatusCode = 503;
+ response.Headers["Retry-After"] = _context.RetryAfterSeconds.ToString();
+ await response.WriteAsync("Service Unavailable");
+ }
+ }
+ }
+
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.