Skip to content

Commit

Permalink
Added a way to shutdown actors by name/ref
Browse files Browse the repository at this point in the history
  • Loading branch information
andresgutierrez committed Oct 16, 2023
1 parent fc795df commit 5c0300c
Show file tree
Hide file tree
Showing 10 changed files with 468 additions and 39 deletions.
29 changes: 29 additions & 0 deletions Nixie.Tests/Actors/ShutdownActor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@

namespace Nixie.Tests.Actors;

public class ShutdownActor : IActor<string>
{
private int receivedMessages;

public ShutdownActor(IActorContext<ShutdownActor, string> _)
{

}

public int GetMessages()
{
return receivedMessages;
}

public void IncrMessage()
{
receivedMessages++;
}

public async Task Receive(string message)
{
await Task.Yield();

IncrMessage();
}
}
35 changes: 35 additions & 0 deletions Nixie.Tests/Actors/ShutdownInsideActor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@

namespace Nixie.Tests.Actors;

public class ShutdownInsideActor : IActor<string>
{
private int receivedMessages;

private readonly IActorContext<ShutdownInsideActor, string> context;

public ShutdownInsideActor(IActorContext<ShutdownInsideActor, string> context)
{
this.context = context;
}

public int GetMessages()
{
return receivedMessages;
}

public void IncrMessage()
{
receivedMessages++;
}

public async Task Receive(string message)
{
await Task.Yield();

if (message == "shutdown")
context.ActorSystem.Shutdown(context.Self);
else
IncrMessage();
}

}
20 changes: 10 additions & 10 deletions Nixie.Tests/TestSend.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public class TestSend
[Fact]
public async Task TestSendMessageToSingleActor()
{
ActorSystem asx = new();
using ActorSystem asx = new();

IActorRef<ReplyActor, string, string> actor = asx.Spawn<ReplyActor, string, string>();

Expand All @@ -22,7 +22,7 @@ public async Task TestSendMessageToSingleActor()
[Fact]
public async Task TestSendMultipleMessageToSingleActor()
{
ActorSystem asx = new();
using ActorSystem asx = new();

IActorRef<ReplyActor, string, string> actor = asx.Spawn<ReplyActor, string, string>();

Expand All @@ -38,7 +38,7 @@ public async Task TestSendMultipleMessageToSingleActor()
[Fact]
public async Task TestSendMessageToSingleActorNoResponse()
{
ActorSystem asx = new();
using ActorSystem asx = new();

IActorRef<FireAndForgetActor, string> actor = asx.Spawn<FireAndForgetActor, string>();

Expand All @@ -52,7 +52,7 @@ public async Task TestSendMessageToSingleActorNoResponse()
[Fact]
public async Task TestSendMultipleMessageToSingleActorNoResponse()
{
ActorSystem asx = new();
using ActorSystem asx = new();

IActorRef<FireAndForgetActor, string> actor = asx.Spawn<FireAndForgetActor, string>("TestSendMultipleMessageToSingleActorNoResponse");

Expand All @@ -68,7 +68,7 @@ public async Task TestSendMultipleMessageToSingleActorNoResponse()
[Fact]
public async Task TestSendMultipleMessageToSingleActorNoResponse2()
{
ActorSystem asx = new();
using ActorSystem asx = new();

IActorRef<FireAndForgetActor, string> actor = asx.Spawn<FireAndForgetActor, string>("TestSendMultipleMessageToSingleActorNoResponse");

Expand All @@ -83,7 +83,7 @@ public async Task TestSendMultipleMessageToSingleActorNoResponse2()
[Fact]
public async Task TestSendMultipleMessageToSingleActorNoResponseSlow()
{
ActorSystem asx = new();
using ActorSystem asx = new();

IActorRef<FireAndForgetSlowActor, string> actor = asx.Spawn<FireAndForgetSlowActor, string>("TestSendMultipleMessageToSingleActorNoResponseSlow");

Expand All @@ -99,7 +99,7 @@ public async Task TestSendMultipleMessageToSingleActorNoResponseSlow()
[Fact]
public async Task TestSendMultipleMessageToSingleActorNoResponseSlow2()
{
ActorSystem asx = new();
using ActorSystem asx = new();

IActorRef<FireAndForgetSlowActor, string> actor = asx.Spawn<FireAndForgetSlowActor, string>("TestSendMultipleMessageToSingleActorNoResponseSlow");

Expand All @@ -114,7 +114,7 @@ public async Task TestSendMultipleMessageToSingleActorNoResponseSlow2()
[Fact]
public async Task TestCreateMultipleActorsAndSendOneMessage()
{
ActorSystem asx = new();
using ActorSystem asx = new();

IActorRef<ReplyActor, string, string>[] actorRefs = new IActorRef<ReplyActor, string, string>[10];

Expand All @@ -133,7 +133,7 @@ public async Task TestCreateMultipleActorsAndSendOneMessage()
[Fact]
public async Task TestCreateMultipleActorsAndSendOneMessage2()
{
ActorSystem asx = new();
using ActorSystem asx = new();

IActorRef<ReplyActor, string, string>[] actorRefs = new IActorRef<ReplyActor, string, string>[100];

Expand All @@ -152,7 +152,7 @@ public async Task TestCreateMultipleActorsAndSendOneMessage2()
[Fact]
public async Task TestSendMessageToFaultyActor()
{
ActorSystem asx = new();
using ActorSystem asx = new();

IActorRef<FaultyActor, FaultyMessage> actor = asx.Spawn<FaultyActor, FaultyMessage>();

Expand Down
134 changes: 134 additions & 0 deletions Nixie.Tests/TestShutdown.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@

using Nixie.Tests.Actors;

namespace Nixie.Tests;

public sealed class TestShutdown
{
[Fact]
public async Task TestSpawnFireAndForgetActorAndShutdownByName()
{
using ActorSystem asx = new();

IActorRef<ShutdownActor, string> actor = asx.Spawn<ShutdownActor, string>("my-actor");

Assert.IsAssignableFrom<ShutdownActor>(actor.Runner.Actor);

actor.Send("TestSpawnFireAndForgetActorAndShutdownByName");

await asx.Wait();

Assert.True(asx.Shutdown<ShutdownActor, string>("my-actor"));

await asx.Wait();

Assert.Equal(1, ((ShutdownActor)actor.Runner.Actor!).GetMessages());

IActorRef<ShutdownActor, string>? actor2 = asx.Get<ShutdownActor, string>("my-actor");
Assert.Null(actor2);
}

[Fact]
public async Task TestSpawnFireAndForgetActorAndShutdownByName2()
{
using ActorSystem asx = new();

IActorRef<ShutdownActor, string> actor = asx.Spawn<ShutdownActor, string>("my-actor");

Assert.IsAssignableFrom<ShutdownActor>(actor.Runner.Actor);

actor.Send("TestSpawnFireAndForgetActorAndShutdownByName2");
actor.Send("TestSpawnFireAndForgetActorAndShutdownByName2");
actor.Send("TestSpawnFireAndForgetActorAndShutdownByName2");

await asx.Wait();

Assert.True(asx.Shutdown<ShutdownActor, string>("my-actor"));

actor.Send("TestSpawnFireAndForgetActorAndShutdownByName2");
actor.Send("TestSpawnFireAndForgetActorAndShutdownByName2");
actor.Send("TestSpawnFireAndForgetActorAndShutdownByName2");

await asx.Wait();

Assert.Equal(3, ((ShutdownActor)actor.Runner.Actor!).GetMessages());

IActorRef<ShutdownActor, string>? actor2 = asx.Get<ShutdownActor, string>("my-actor");
Assert.Null(actor2);
}

[Fact]
public async Task TestSpawnFireAndForgetActorAndShutdownByRef()
{
using ActorSystem asx = new();

IActorRef<ShutdownActor, string> actor = asx.Spawn<ShutdownActor, string>("my-actor");

Assert.IsAssignableFrom<ShutdownActor>(actor.Runner.Actor);

actor.Send("TestSpawnFireAndForgetActorAndShutdownByRef");

await asx.Wait();

Assert.True(asx.Shutdown(actor));

await asx.Wait();

Assert.Equal(1, ((ShutdownActor)actor.Runner.Actor!).GetMessages());

IActorRef<ShutdownActor, string>? actor2 = asx.Get<ShutdownActor, string>("my-actor");
Assert.Null(actor2);
}

[Fact]
public async Task TestSpawnFireAndForgetActorAndShutdownByRef2()
{
using ActorSystem asx = new();

IActorRef<ShutdownActor, string> actor = asx.Spawn<ShutdownActor, string>("my-actor");

Assert.IsAssignableFrom<ShutdownActor>(actor.Runner.Actor);

actor.Send("TestSpawnFireAndForgetActorAndShutdownByRef2");
actor.Send("TestSpawnFireAndForgetActorAndShutdownByRef2");
actor.Send("TestSpawnFireAndForgetActorAndShutdownByRef2");

await asx.Wait();

Assert.True(asx.Shutdown(actor));

actor.Send("TestSpawnFireAndForgetActorAndShutdownByRef2");
actor.Send("TestSpawnFireAndForgetActorAndShutdownByRef2");
actor.Send("TestSpawnFireAndForgetActorAndShutdownByRef2");

await asx.Wait();

Assert.Equal(3, ((ShutdownActor)actor.Runner.Actor!).GetMessages());

IActorRef<ShutdownActor, string>? actor2 = asx.Get<ShutdownActor, string>("my-actor");
Assert.Null(actor2);
}

[Fact]
public async Task TestSpawnActorAndShutdownInside()
{
using ActorSystem asx = new();

IActorRef<ShutdownInsideActor, string> actor = asx.Spawn<ShutdownInsideActor, string>("my-actor");

Assert.IsAssignableFrom<ShutdownInsideActor>(actor.Runner.Actor);

actor.Send("TestSpawnActorAndShutdownInside");

await asx.Wait();

actor.Send("shutdown");

await asx.Wait();

Assert.Equal(1, ((ShutdownInsideActor)actor.Runner.Actor!).GetMessages());

IActorRef<ShutdownInsideActor, string>? actor2 = asx.Get<ShutdownInsideActor, string>("my-actor");
Assert.Null(actor2);
}
}
62 changes: 58 additions & 4 deletions Nixie/ActorRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,15 @@ public bool HasPendingMessages()
{
Lazy<(ActorRunner<TActor, TRequest> runner, ActorRef<TActor, TRequest> actorRef)> lazyValue = actor.Value;

if (lazyValue.IsValueCreated && !lazyValue.Value.runner.Inbox.IsEmpty)
return true;
if (lazyValue.IsValueCreated)
{
ActorRunner<TActor, TRequest> runner = lazyValue.Value.runner;

if (!runner.IsShutdown && !lazyValue.Value.runner.Inbox.IsEmpty)
return true;
}
}

return false;
}

Expand All @@ -50,9 +56,15 @@ public bool IsProcessing()
{
Lazy<(ActorRunner<TActor, TRequest> runner, ActorRef<TActor, TRequest> actorRef)> lazyValue = actor.Value;

if (lazyValue.IsValueCreated && lazyValue.Value.runner.Processing)
return true;
if (lazyValue.IsValueCreated)
{
ActorRunner<TActor, TRequest> runner = lazyValue.Value.runner;

if (!runner.IsShutdown && runner.IsProcessing)
return true;
}
}

return false;
}

Expand Down Expand Up @@ -134,4 +146,46 @@ public IActorRef<TActor, TRequest> Spawn(string? name = null, params object[]? a

return null;
}

/// <summary>
/// Shutdowns an actor by its name
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public bool Shutdown(string name)
{
name = name.ToLowerInvariant();

if (actors.TryGetValue(name, out Lazy<(ActorRunner<TActor, TRequest> runner, ActorRef<TActor, TRequest> actorRef)>? actor))
{
if (actor.Value.runner.Shutdown())
{
actors.TryRemove(name, out _);
return true;
}
}

return true;
}

/// <summary>
/// Shutdowns an actor by its reference
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public bool Shutdown(IActorRef<TActor, TRequest> actorRef)
{
string name = actorRef.Runner.Name;

if (actors.TryGetValue(name, out Lazy<(ActorRunner<TActor, TRequest> runner, ActorRef<TActor, TRequest> actorRef)>? actor))
{
if (actor.Value.runner.Shutdown())
{
actors.TryRemove(name, out _);
return true;
}
}

return true;
}
}
Loading

0 comments on commit 5c0300c

Please sign in to comment.