2017-07-25 2 views
1

propagieren Ich versuche, Fehlerbehandlung mit akka und zu verstehen. Zum Beispiel habe ich Eltern und Kind Schauspieler.Wie Fehler in Zukunft zu übergeordneten Akteur

Child Schauspieler haben zwei Fehlerfälle:
Fall 1) Fehler passiert, während der Nachrichtenverarbeitung
Fall 2) Fehler passiert im Inneren Zukunft

Ich muss in beiden Fällen Fehler Eltern propagieren, sondern in zweiten Fall ist es nicht passiert. Was mache ich falsch?

import akka.actor.SupervisorStrategy.{Decider, Stop} 
import akka.actor.{Actor, ActorRef, ActorSystem, OneForOneStrategy, Props, SupervisorStrategy} 
import akka.testkit.{TestKit, TestProbe} 
import org.junit.{After, Before, Test} 

import scala.concurrent.Future 
import scala.util.{Failure, Success} 


class Parent(_case: String, probe: ActorRef) extends Actor { 

    val child = context.actorOf(Props(new Child(_case)), "myLittleChild") 

    final val defaultStrategy: SupervisorStrategy = { 
    def defaultDecider: Decider = { 
     case ex: Exception => 
     probe ! ex 
     Stop 
    } 

    OneForOneStrategy()(defaultDecider) 
    } 

    override def supervisorStrategy: SupervisorStrategy = defaultStrategy 

    override def receive: Receive = { 
    case msg => unhandled(msg) 
    } 

} 

class Child(_case: String) extends Actor { 

    implicit val ec = context.dispatcher 

    override def preStart(): Unit = { 
    self ! _case 
    } 


    override def receive: Receive = { 
    case "case1" => throw new RuntimeException("fail") 
    case "case2" => Future[String] { 
     throw new RuntimeException("fail") 
    }.onComplete { 
     case Success(s) => println(s) 
     case Failure(e) => 
     throw e 
    } 
    case msg => unhandled(msg) 
    } 
} 


class TestExample { 

    protected implicit var system: ActorSystem = _ 

    @Before 
    def setup(): Unit = { 
    system = ActorSystem.create("test") 
    } 

    @After 
    def tearDown(): Unit = { 
    TestKit.shutdownActorSystem(system) 
    } 

    @Test 
    def case1(): Unit = { 
    val testProbe = TestProbe() 
    system.actorOf(Props(new Parent("case1", testProbe.ref))) 
    testProbe expectMsgClass classOf[RuntimeException] 
    } 

    @Test 
    def case2(): Unit = { 
    val testProbe = TestProbe() 
    system.actorOf(Props(new Parent("case2", testProbe.ref))) 
    testProbe expectMsgClass classOf[RuntimeException] 
    } 

} 

Antwort

0

Ihren Test zu erhalten, passieren Sie die Ausnahme an den Schauspieler schicken könnte und die Ausnahme von außerhalb des onComplete Rückruf erneut auslösen:

override def receive: Receive = { 
    case "case1" => throw new RuntimeException("fail") 
    case "case2" => 
    Future[String] { 
     throw new RuntimeException("fail") 
    }.onComplete { 
     case Success(s) => println(s) 
     case Failure(e) => 
     self ! e 
    } 
    case e: RuntimeException => throw e 
    case msg => unhandled(msg) 
} 

Wenn Sie jedoch einen Future in einem Schauspieler verwenden müssen (z. B. eine Bibliothek eines Drittanbieters, deren Methoden eine Future zurückgeben), dann gibt es bessere Möglichkeiten, Ausnahmen zu behandeln. Mit der Datenbank-API, die Sie in einem Kommentar (databaseApi.load(): Future[Rows]) erwähnt haben, könnte der übergeordnete Akteur beispielsweise eine LoadDb Nachricht an das Kind senden, und das Kind könnte Rows oder eine Fehlermeldung zurück an den Eltern senden. Das Verhalten des Kindes aussehen würde wie folgt aus:

def receive = { 
    case LoadDb => 
    val s = sender // capture the sender 
    databaseApi 
     .load 
     .onComplete { 
     case Success(rows) => 
      s ! rows 
     case Failure(e) => 
      s ! DbFailure(e) 
     } 
    case ... 
} 

Ein wichtiger Hinweis ist, dass wir eine lokale Kopie des sender Referenz, wenn das Kind eine LoadDb Nachricht empfängt, um von innen die einen Verweis auf den richtigen Sender zu haben onComplete Rückruf. Wenn wir sender innerhalb des Callbacks gerade aufgerufen haben, könnte dies zu fehlerhaften Ergebnissen führen, da sich sender bis zur Ausführung des Callbacks geändert haben könnte, wie in here erläutert. (Im Gegensatz zu sender ist self unveränderlich, so ist es sicher self innen onComplete zu verwenden.)

+0

Ich aktualisierte mit vollem Testcode. Und Fall2 schlägt fehl ... – zella

+0

@zella: Aktualisiert. – chunjef

1

Dies ist nicht die Art und Weise zwischen Eltern und Kind zu kommunizieren. Der richtige Weg ist, eine Nachricht zu definieren, die den Fehler enthält (und keine Ausnahme senden!). Dann kann der Elternteil die Nachricht entsprechend behandeln.

Auch das Konstruieren des Kind-Akteurs, den Sie in den Eltern tun, wird nicht bevorzugt, weil dies es sehr schwierig macht, die Akteure zu testen. Stattdessen sollte eine Kind-Akteur-Fabrik-Funktion in den übergeordneten Akteur als Argument übergeben werden. Dies kann dann leicht durch einen Dummy-Akteur (z. B. einen TestActorRef oder TestProbe) ersetzt werden, wenn der Elternakteur getestet wird. In ähnlicher Weise kann der Kinddarsteller isoliert getestet werden, um die richtigen Nachrichten an den Elternteil zurückzugeben.

Auch die Verwendung von "Zukunft" in einem Schauspieler wird nicht empfohlen. Der Akteur läuft bereits asynchron und behandelt nur 1 Nachricht zu der Zeit. Wenn Sie Future in actor verwenden, müssen Sie den Fall des Empfangens anderer Nachrichten behandeln, während die Zukunft noch nicht abgeschlossen ist, da der Akteur sich in einem falschen Zustand befinden könnte, bis die Zukunft abgeschlossen ist. Eine Möglichkeit, mit Future in einem Schauspieler herumzukommen, könnte sein, einen temporären Schauspieler zu verwenden, wie in der book 'Effective Akka' (Extra Pattern, Cameo Pattern) beschrieben.

Die 'Effective Akka' book ist eine gute Lesung, wenn Sie mit Akka beginnen. Es enthält einige Best Practices und Dinge, die zu vermeiden sind. Es ist ein kleines Buch, das so schnell zu lesen ist.

Update basierend auf Kommentar:
In diesem Fall haben Sie zwei Möglichkeiten:

  • da ein Schauspieler schon async Sie es in dem Schauspieler synchron machen könnte läuft entscheiden. Das würde es viel einfacher machen, aber würde blockieren müssen.
  • Andere Lösung ist die OnComplete der Zukunft zu behandeln und senden Sie entweder Erfolg oder Misserfolg Nachricht an den Elternteil (oder der Schauspieler interessiert an dem Ergebnis). Persönlich würde ich keine Ausnahmen werfen.

    Ich würde eine Kind-Schauspieler-Fabrik in den Eltern-Schauspieler übergeben und für den Kind (oder Arbeiter) Akteur entweder in den Akteur übergeben, der die Antwort will oder es vom "Absender" nehmen.

    Beachten Sie, dass Sie den 'Absender' erfassen müssen, bevor Sie die Zukunft aufrufen. Und verwenden Sie einen anderen Thread-Pool, ansonsten verwenden die Futures denselben Thread-Pool wie die Aktoren selbst. Um diesen Pool wiederzuverwenden, möchten Sie diesen auch an den untergeordneten Akteur weitergeben und ihn dann zum Testen anpassen.

Ich verstehe nicht, warum Sie den Schauspieler neu starten möchten. Es scheint, dass dies ein staatenloser Schauspieler wäre, nur ein Adapter zur DatenbankApi.

Für die Implementierung des untergeordneten Aktors können Sie das zusätzliche/Cameo-Muster verwenden. Dann sind Sie sicher, dass es keine anderen Nachrichten erhält (vergessen Sie nicht, den Schauspieler zu stoppen, wenn es fertig ist). Aber indem Sie es zu einem separaten Akteur machen, könnten Sie sich schließlich dazu entschließen, einen Pool dieser Akteure (mit einem Router) zu erstellen, um die Anzahl der gleichzeitigen db-Aktionen in den Griff zu bekommen.

+0

Danke für Erklärungen. Und was ist wenn ich bereits eine API mit Futures habe, zum Beispiel 'databaseApi.load(): Future [Rows]' und 'ChildDatabaseActor', die es verwenden. Bei einigen Datenbankfehlern möchte ich den Akteur neu starten - das ist ähnlich in meinem Beispielcode. – zella

+0

Siehe aktualisierte Antwort –

Verwandte Themen