2014-05-13 8 views
5

Ich versuche eine Hierarchie von Akka Schauspielern zu verwenden, um pro Benutzerstatus zu behandeln. Es gibt einen übergeordneten Schauspieler, die alle Kinder besitzen, und übernimmt die get-oder-erstellen in der richtigen Art und Weise (siehe a1, a2):Erhalte oder erschaffe Kind Akka Schauspieler und gewährleiste die Lebendigkeit

class UserActorRegistry extends Actor { 
    override def Receive = { 
    case [email protected] DoPerUserWork(userId, _) => 
     val perUserActor = getOrCreateUserActor(userId) 
     // perUserActor is live now, but will it receive "msg"? 
     perUserActor.forward(msg) 
    } 

    def getOrCreateUserActor(userId: UserId): ActorRef = { 
    val childName = userId.toActorName 
    context.child(childName) match { 
     case Some(child) => child 
     case None => context.actorOf(Props(classOf[UserActor], userId), childName) 
    } 
} 

Um Speicher zurückzugewinnen, die UserActors verfallen nach einer Frist von Faulheit (dh ein Timer löst den Kindschauspieler aus, um context.stop(self) anzurufen).

Mein Problem ist, dass ich denke, ich habe eine Race-Bedingung zwischen dem "getOrCreateUserActor" und dem Kind Akteur erhalten die weitergeleitete Nachricht - wenn das Kind in diesem Fenster abläuft dann wird die weitergeleitete Nachricht verloren.

Gibt es eine Möglichkeit, dass ich entweder diesen Randfall erkennen kann, oder den UserActorRegistry refaktorieren, um es auszuschließen?

+0

Es wird einfacher, wenn Sie die Liste der untergeordneten Akteure explizit in einer Karte verwalten. Anschließend können Sie den Zeitüberschreitungen für untergeordnete Akteure signalisieren, indem Sie eine Ablaufnachricht an das übergeordnete Element senden, wodurch das übergeordnete Element das Kind anhält und die Karte bereinigt. Die Karte wird synchron (aus der Perspektive des übergeordneten Schauspielers) gehalten, so dass die Wettlaufbedingung verschwindet. – tariksbl

+0

Das klingt sinnvoll. Ich muss auch die Karte verwenden, um die Kinderschauspieler nachzuschlagen, anstatt sie namentlich zu suchen, sonst könnte es Konflikte geben, in denen das Kind sterben soll, aber es existiert immer noch in Akkas Kinderkarte. Es ist verschwenderisch, eine separate Kindkarte zu pflegen, wenn Akka diese Karte schon hat. – Rich

+0

Eine separate Karte scheint überflüssig zu sein. Mehr noch, wenn ich bedenke, dass ich oft zwei Karten, die zweite Indizierung von ActorRef, aufhalte, um den ersten zu reinigen, wenn ich ein Terminated erhalte. Ja, interessant, wäre glatt, um Kind() zu verwenden. – tariksbl

Antwort

8

kann ich zwei Probleme mit Ihrem aktuellen Design sehen, die sich auf das Rennen Zustand öffnen Sie erwähnen:

1) mit der Abbruchbedingung (Timer eine Giftpille) gehen direkt an das Kind Schauspieler zu senden. Mit diesem Ansatz kann das Kind sicher auf einem separaten Thread (innerhalb des Dispatcher) beendet werden, während gleichzeitig eine Nachricht eingerichtet wurde, um zu ihm in dem UserActorRegistry Aktor (auf einem anderen Thread innerhalb des Dispatcher) weitergeleitet zu werden.

2) Mit einem PoisonPill, um das Kind zu beenden. A PoisonPill ist für einen eleganten Stop, der ermöglicht, dass andere Nachrichten in der Mailbox zuerst verarbeitet werden. In Ihrem Fall werden Sie aufgrund von Inaktivität beendet, was darauf hindeutet, dass keine anderen Nachrichten bereits im Postfach vorhanden sind. Ich sehe eine PoisonPill als falsch hier, weil in Ihrem Fall eine andere Nachricht nach der PosionPill gesendet werden und diese Nachricht würde sicherlich verloren gehen, nachdem die PoisonPill verarbeitet wird.

Also werde ich vorschlagen, dass Sie die Beendigung der inaktiven Kinder an die UserActorRegistry delegieren, anstatt es in den Kindern selbst zu tun. Wenn Sie den Zustand der Inaktivität feststellen, senden Sie eine Nachricht an die Instanz UserActorRegistry, die angibt, dass ein bestimmtes untergeordnetes Kind beendet werden muss. Wenn Sie diese Nachricht erhalten, beenden Sie dieses Kind über stop statt eine PoisonPill senden. Wenn Sie das einzelne Postfach der UserActorRegistry verwenden, das seriell verarbeitet wird, können Sie sicherstellen, dass ein untergeordnetes Objekt nicht gleichzeitig terminiert wird, während Sie ihm gerade eine Nachricht senden.

Jetzt gibt es hier eine Komplikation, mit der Sie umgehen müssen. Stoppen eines Schauspielers ist asynchron. Wenn Sie also stop auf einem Kind aufrufen, wird es möglicherweise nicht vollständig angehalten, wenn Sie eine DoPerUserWork-Nachricht verarbeiten, und könnte daher eine Nachricht senden, die verloren geht, weil sie gerade gestoppt wird. Sie können dies lösen, indem Sie einen internen Status (eine Liste) beibehalten, der Kinder darstellt, die gerade angehalten werden. Wenn Sie ein Kind anhalten, fügen Sie dessen Namen zu dieser Liste hinzu und richten Sie dann DeathWatch (über context watch child) darauf ein. Wenn Sie das Ereignis Terminated für dieses Kind erhalten, entfernen Sie seinen Namen aus der Liste der untergeordneten Kinder. Wenn Sie Arbeit für ein Kind erhalten, während sein Name in dieser Liste ist, wiederholen Sie es für die erneute Verarbeitung, möglicherweise bis zu einer maximalen Anzahl von Malen, um nicht für immer zu versuchen und erneut zu verarbeiten.

Dies ist keine perfekte Lösung; Es ist nur eine Identifizierung einiger Probleme mit Ihrem Ansatz und ein Stoß in die richtige Richtung, um einige von ihnen zu lösen. Lass mich wissen, wenn du den Code dafür sehen willst und ich werde etwas zusammenpeitschen.

bearbeiten

Als Antwort auf Ihre zweite Bemerkung. Ich glaube nicht, dass Sie in der Lage sein werden, ein Kind zu betrachten ActorRef und sehen, dass es derzeit heruntergefahren wird, also die Notwendigkeit für diese Liste von Kindern, die gerade dabei sind, herunterzufahren. Sie könnten die Nachricht DoPerUserWork so aufwerten, dass sie ein Feld numberOfAttempts: Int enthält, und dieses inkrementieren und zur erneuten Verarbeitung an self zurücksenden, wenn das Zielkind gerade geschlossen wird. Sie können dann numberOfAttempts verwenden, um zu verhindern, dass Sie für immer in die Warteschlange eingereiht werden und bei einer maximalen Anzahl von Versuchen anhalten. Wenn Sie sich bei DeathWatch nicht sicher fühlen, können Sie den Elementen in der Liste der heruntergefahrenen untergeordneten Elemente eine Komponente für die Lebensdauer hinzufügen. Du könntest dann Gegenstände beschneiden, wenn du sie siehst, wenn sie in der Liste sind, aber zu lange dort waren.

+0

Danke, sehr hilfreich. Ich benutze 'PoisonPill nicht wirklich ', aber der Kinddarsteller ruft' context.stop (self) 'nach einer Zeitüberschreitung: Ich habe die Frage aktualisiert. Entschuldigung für die Verwirrung. Der Rest Ihrer Erklärung macht noch viel Sinn. – Rich

+0

" Stoppen eines Schauspielers ist asynchron "- Das ist das Schlüsselproblem hier, denke ich. Nachdem ich' context.stop (child) 'vom Elternteil aufgerufen habe, aber das Kind erscheint immer noch über' context.child (name) 'in welchem ​​Zustand wird es sein? Unbestimmter Status? Was kann ich mit eingehenden "DoPerUserWork" -Nachrichten in diesem Zeitraum tun? Ich nehme an, dass ich sie in die Warteschlange stellen muss. Ich werde in dieser unbestimmten Zeit kein neues Kind mit dem gleichen Namen erstellen können, oder? Und eine Liste von "Kindern, die gekündigt werden" ist der einzige Weg, diesen Zombie-Staat zu entdecken? Wird diese Liste brauchen? eine Zeitüberschreitung oder kann ich mich auf DeathWatch verlassen? – Rich

+0

@Rich, ich habe mehr zur Antwort hinzugefügt, um deine zweite Frage zu beantworten. – cmbaxter