When I was first introduced to Akka.net one of my preliminary thoughts was this framework needed IOC support. So I decided to pitch in but before I wrote any code I started doing research into the what it would take to get this done.
Initial Research
The first thing that I found was a Java implementation using the Spring IOC container. From this implementation I began the process of translating the Java into C# so I could understand better how the whole thing worked.
That research led me to open up two discussions on the Google group with concerns that I had based on a great post I read from Mark Seemann who incidentally wrote an excellent book called Dependency Injection in .NET. The basic gist to the discussion was that the interface IndirectActorProducer was missing a method to make working with an IOC container more DI friendly.
So instead I trying to solve that issue I decided to move forward making my contribution using that interface as the basis for coding a DI plugin.
Bug Report
Just recently as I was moving forward with my Event Store with Cassandra and Akka @aaronontheweb pointed out a bug report filed against my contribution relating to the for mentioned issues I brought up in the discussion group but did not pursue a solution for.
So since I have the contribution complete based on the old interface I decided to see if I could come up with a solution to this issue. As a result of my research into the issue and chatting over on Gitter I was able to enlisted the help of @stefansedich to rewrite the AutoFac portion of the code. Which is great since my IOC container of choice is Castle Windsor and I really want this contribution to be first rate so I am very thank full to him for his help.
Proposal
To make this work a few things will need to change with Akka.net to make this work properly. The current IndirectActorProducer
interface is going to need a slight change with the addition of a new release method.
public interface IndirectActorProducer : IRelease
{
/// <summary>
/// This factory method must produce a fresh actor instance upon each
/// invocation. It is not permitted to return the same instance more than
/// once.
/// </summary>
/// <returns>A fresh actor instance.</returns>
ActorBase Produce();
/// <summary>
/// This method is used by [[Props]] to determine the type of actor which will
/// be created. The returned type is not used to produce the actor.
/// </summary>
Type ActorType { get; }
/// <summary>
/// New Method to release the actor produced by this factory's
/// implementation
/// </summary>
void Release(ActorBase actor);
}
The next part is that framework is going to need to call the release method when the Actor is no longer needed by the system. The ActorCell calls the method FinishTerminate
and it's from this method we need to call Release. In order to be able to call release the Props object which is injected with the IndirectActorProducer
is going to need a need method to pass through the request from the ActorCell
to the IndirectActorProducer
. To accomplish this I propose either a new IReleaseInterface
with the release method and implemented by Props or a new internal Release method be used.
Implementation A
public interface IRelease
{
void Release(ActorBase actor);
}
public class Props : IRelease
{
//Rest of implementation here
void Release(ActorBase actor)
{
if (this.producer != null) this.producer.Release(actor);
actor = null;
}
}
private void FinishTerminate()
{
// The following order is crucial for things to work properly. Only change this if you're very confident and lucky.
//
// Please note that if a parent is also a watcher then ChildTerminated and Terminated must be processed in this
// specific order.
var a = _actor;
try
{
if (a != null)
{
a.AroundPostStop();
//if the actor uses a stash, we must Unstash all messages.
//If the user do not want this behavior, the stash should be cleared in PostStop
//either by calling ClearStash or by calling UnstashAll.
UnstashAllActorMessages(a);
}
}
catch (Exception x)
{
HandleNonFatalOrInterruptedException(
() => Publish(new Error(x, _self.Path.ToString(), ActorType, x.Message)));
}
finally
{
try
//TODO: Akka Jvm: this is done in a call to dispatcher.detach()
{
//TODO: Akka Jvm: this is done in a call to MessageDispatcher.detach()
{
var mailbox = Mailbox;
var deadLetterMailbox = System.Mailboxes.DeadLetterMailbox;
SwapMailbox(deadLetterMailbox);
mailbox.BecomeClosed();
mailbox.CleanUp();
}
}
finally
{
try { Parent.Tell(new DeathWatchNotification(_self, existenceConfirmed: true, addressTerminated: false)); }
finally
{
try { TellWatchersWeDied(); }
finally
{
try { UnwatchWatchedActors(a); }
finally
{
if (System.Settings.DebugLifecycle)
Publish(new Debug(_self.Path.ToString(), ActorType, "Stopped"));
ClearActor(a);
ClearActorCell();
Release(a);
_actor = null;
}
}
}
}
}
}
private void Release(ActorBase actor)
{
(IRelease)_props.Release(actor);
}
Implementation B
public interface IRelease
{
void Release(ActorBase actor);
}
public class Props
{
//Rest of implementation here
internal void Release(ActorBase actor)
{
if (this.producer != null) this.producer.Release(actor);
actor = null;
}
}
private void FinishTerminate()
{
// The following order is crucial for things to work properly. Only change this if you're very confident and lucky.
//
// Please note that if a parent is also a watcher then ChildTerminated and Terminated must be processed in this
// specific order.
var a = _actor;
try
{
if (a != null)
{
a.AroundPostStop();
//if the actor uses a stash, we must Unstash all messages.
//If the user do not want this behavior, the stash should be cleared in PostStop
//either by calling ClearStash or by calling UnstashAll.
UnstashAllActorMessages(a);
}
}
catch (Exception x)
{
HandleNonFatalOrInterruptedException(
() => Publish(new Error(x, _self.Path.ToString(), ActorType, x.Message)));
}
finally
{
try
//TODO: Akka Jvm: this is done in a call to dispatcher.detach()
{
//TODO: Akka Jvm: this is done in a call to MessageDispatcher.detach()
{
var mailbox = Mailbox;
var deadLetterMailbox = System.Mailboxes.DeadLetterMailbox;
SwapMailbox(deadLetterMailbox);
mailbox.BecomeClosed();
mailbox.CleanUp();
}
}
finally
{
try { Parent.Tell(new DeathWatchNotification(_self, existenceConfirmed: true, addressTerminated: false)); }
finally
{
try { TellWatchersWeDied(); }
finally
{
try { UnwatchWatchedActors(a); }
finally
{
if (System.Settings.DebugLifecycle)
Publish(new Debug(_self.Path.ToString(), ActorType, "Stopped"));
ClearActor(a);
ClearActorCell();
Release(a);
_actor = null;
}
}
}
}
}
}
private void Release(ActorBase actor)
{
_props.Release(actor);
}
Either solution will provide the desired result. Since this is just a proposal I would be interested to see what others think. Please feel free to comment either on my blog or at https://gitter.im/akkadotnet/akka.net.