Skocz do zawartości

AKKA.NET – Przykład obsługi błędów


DevStart Blogi

Recommended Posts

W poprzednim wpisie pokazałem, w jaki sposób możemy zaprojektować obsługę błędów. Jak widać mamy do dyspozycji sporo opcji. Z punktu widzenia AKKA.NET nie jest to jednak tak skomplikowane. Wystarczy przeładować jedną metodę i zwrócić odpowiedni obiekt.

Tak jak w poprzednim wpisie będziemy testować kod na następującym “systemie”:

controller

Dla przypomnienia nasz ApplicationUserActor wygląda następująco:

public class ApplicationUserActor : UntypedActor
    {
        private readonly string _userName;

        public ApplicationUserActor(string userName)
        {
            _userName = userName;
        }

        protected override void OnReceive(object message)
        {
            Console.WriteLine("Received by {0}: {1}", _userName, message);
        }

        protected override void PreStart()
        {
            Console.WriteLine("{0}: PreStart",_userName);

          
            base.PreStart();
        }

        protected override void PostStop()
        {
            Console.WriteLine("{0}: PostStop", _userName);
            base.PostStop();
        }

        protected override void PreRestart(Exception reason, object message)
        {
            Console.WriteLine("{0}: PreRestart", _userName);
            base.PreRestart(reason, message);
        }

        protected override void PostRestart(Exception reason)
        {
            Console.WriteLine("{0}: PostRestart", _userName);
            base.PostRestart(reason);
        }
    }

Póki co niewiele mamy tam kodu – głównie hooking, które pomogą nam w zrozumieniu propagacji błędów.
Zmodyfikujmy metodę OnReceived tak, aby zasymulować wyjątek:

        protected override void OnReceive(object message)
        {
            if (message.ToString() == "error")
                throw new ArgumentException();

            Console.WriteLine("Received by {0}: {1}", _userName, message);
        }

W celu zdefiniowania obsługi błędów wystarczy przeciążyć metodę SupervisorStrategy aktora zarządzającego. Jeśli chcemy więc obsłużyć wyjątek w ApplicationUserActor, wtedy węzeł zarządzający (rodzic) to ApplicationUserControllerActor. Kod:

        protected override SupervisorStrategy SupervisorStrategy()
        {
            return new OneForOneStrategy((exception) =>
            {
                if (exception is ArgumentException)
                    return Directive.Restart;

                return Directive.Escalate;
            });
        }

W przykładzie wybraliśmy strategię OneForOneStrategy, którą opisałem już w poprzednim wpisie. W skrócie, rodzeństwo węzła, który spowodował wyjątek, nie będzie odgrywało tutaj żadnej roli. Wystarczy, że przekażemy wyrażenie lambda, które określa co należy zrobić w zależności od typu wyjątku. W powyższym przykładzie restartujemy aktora. Tak jak napisałem w poprzednim poście, mamy cztery sposoby reakcji:

  public enum Directive
  {
    Resume,
    Restart,
    Escalate,
    Stop,
  }

W celu zaprezentowania efektu, stwórzmy dwóch aktorów i wyślijmy serię wiadomości:

            var system = ActorSystem.Create("FooHierarchySystem");
            IActorRef userControllerActor =
                system.ActorOf<ApplicationUserControllerActor>("ApplicationUserControllerActor");

            userControllerActor.Tell(new AddUser("Piotr"));
            userControllerActor.Tell(new AddUser("Pawel"));
            var actor1 = system.ActorSelection("/user/ApplicationUserControllerActor/Piotr");
            var actor2 = system.ActorSelection("/user/ApplicationUserControllerActor/Pawel");

            Console.ReadLine();
            actor1.Tell("Sample message I");
            Console.ReadLine();
            actor1.Tell("error");
            Console.ReadLine();
            actor1.Tell("Sample message II");

            Console.ReadLine();

1
Widzimy, że w momencie wystąpienia błędu, aktor został zrestartowany. Ze screenu również można zauważyć, że kolejne wiadomości zostaną przetworzone. Stan wewnętrzny został zrestartowany, ale nie kolejka wiadomości. W celu zademonstrowania, że stan wewnętrzny faktycznie jest wymazywany (ponieważ tworzona jest nowa instancja), dodajmy prywatne pole do klasy:

    public class ApplicationUserActor : UntypedActor
    {
        private readonly string _userName;
        private string _internalState;
      ...
    }

InternalState jest wyświetlany i ustawiany w OnReceive:

        protected override void OnReceive(object message)
        {
            Console.WriteLine("{0}:Internal State: {1}",_userName,_internalState);

            if (message.ToString() == "error")
                throw new ArgumentException();

            _internalState = message.ToString();

            Console.WriteLine("Received by {0}: {1}", _userName, message);
        }

Teraz widzimy, że po wystąpieniu wyjątku, InternalState będzie pusty:
2

Analogicznie, spróbujmy zmienić dyrektywę restart na resume:

        protected override SupervisorStrategy SupervisorStrategy()
        {
            return new OneForOneStrategy((exception) =>
            {
                if (exception is ArgumentException)
                    return Directive.Resume;

                return Directive.Escalate;
            });
        }

Po uruchomieniu programu, przekonamy się, że stan wewnętrzny nie jest usuwany:
3

Zmieńmy również strategię na AllForOneStrategy:

        protected override SupervisorStrategy SupervisorStrategy()
        {
            return new AllForOneStrategy((exception) =>
            {
                if (exception is ArgumentException)
                    return Directive.Restart;

                return Directive.Escalate;
            });
        }

Efekt będzie taki, że wszystkie węzły podrzędne zostaną zrestartowane:
4

Jeśli w jakimś aktorze nie zdefiniujemy strategii obsługi błędów, wtedy domyślna będzie użyta:

   protected virtual SupervisorStrategy SupervisorStrategy()
    {
      return SupervisorStrategy.DefaultStrategy;
    }

Domyślna strategia to z kolei OneForOneStrategy.
Warto również przyjrzeć się  innym przeciążeniom konstruktora, np.:

    ///
<summary>
    /// Applies the fault handling `Directive` (Resume, Restart, Stop) specified in the `Decider`
    ///                 to all children when one fails, as opposed to <see cref="T:Akka.Actor.OneForOneStrategy"/> that applies
    ///                 it only to the child actor that failed.
    /// 
    /// </summary>

    /// <param name="maxNrOfRetries">the number of times a child actor is allowed to be restarted, negative value means no limit,
    ///                 if the limit is exceeded the child actor is stopped.
    ///             </param><param name="withinTimeRange">duration of the time window for maxNrOfRetries, Duration.Inf means no window.</param><param name="localOnlyDecider">mapping from Exception to <see cref="T:Akka.Actor.Directive"/></param>
    public OneForOneStrategy(int? maxNrOfRetries, TimeSpan? withinTimeRange, Func<Exception, Directive> localOnlyDecider)
      : this(maxNrOfRetries.GetValueOrDefault(-1), (int) withinTimeRange.GetValueOrDefault(Timeout.InfiniteTimeSpan).TotalMilliseconds, localOnlyDecider, true)
    {
    }

Widzimy, że oprócz wspomnianego wyrażenia lambda, możemy określić maksymalną liczbę prób oraz przedział czasowy.

LM55rPzj4Gk

Wyświetl pełny artykuł

Link do komentarza
Udostępnij na innych stronach

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Gość
Odpowiedz...

×   Wkleiłeś zawartość bez formatowania.   Usuń formatowanie

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Utwórz nowe...