Outils pour utilisateurs

Outils du site


perspective_broker_cacheable

Perspective Broker Cacheable

Niveau Pro et intergalactique !
Cette API:

  • a entre 15 et 20 ans
  • n'est que vaguement maintenu
  • un exemple au moins ne marche pas
  • le tranfert de datas simples marche
  • le transfert d'objets complexes ne marche pas
  • les exemples ne respectent pas le PEP 8, c'est imbriqué au possible

Traduction Google de PB Copyable: Passing Complex Types améliorée par un humain ayant fait intergalactique en 1ère langue !

pb.Cacheable

Parfois, l'objet que vous souhaitez envoyer au processus distant est volumineux et lent. “grand” signifie qu'il faut beaucoup de données (stockage, bande passante réseau, traitement) pour représenter son état. “lent” signifie que l'état ne change pas très fréquemment. Il peut être plus efficace d'envoyer l'état complet une seule fois, la première fois qu'il est nécessaire, puis de n'envoyer ensuite que les différences ou les changements d'état chaque fois qu'il est modifié. La classe pb.Cacheable fournit un cadre pour implémenter cela.

pb.Cacheable est dérivé de pb.Copyable, il est donc basé sur l'idée que l'état d'un objet est capturé côté envoi, puis transformé en un nouvel objet côté réception. Ceci est étendu pour avoir un objet “publication” du côté envoi (dérivé de pb.Cacheable), associé à un objet “observation” du côté réception (dérivé de pb.RemoteCache).

Pour utiliser efficacement pb.Cacheable, vous devez isoler les modifications apportées à votre objet dans des fonctions d'accesseur (en particulier les fonctions de “définition”). Votre objet doit prendre le contrôle à chaque fois qu'un attribut est modifié.

  • Bien sûr, vous pourriez être intelligent et ajouter un crochet à __setattr__, ainsi que des sous-classes magiques annonçant les changements des types intégrés habituels, pour détecter les changements qui résultent d'opérations normales d'ensemble “=”.

Vous dérivez votre classe côté expéditeur de pb.Cacheable et vous ajoutez deux méthodes : getStateToCacheAndObserveFor et stoppedObserving. Le premier est appelé lorsqu'une référence de mise en cache distante est créée pour la première fois et récupère les données avec lesquelles le cache est rempli pour la première fois. Il fournit également un objet appelé “observateur” qui pointe vers ce cache côté récepteur. Chaque fois que l'état de l'objet est modifié, vous donnez un message à l'observateur, l'informant du changement. L'autre méthode, stoppedObserving, est appelée lorsque le cache distant disparaît, afin que vous puissiez arrêter d'envoyer des mises à jour.

  • C'est en fait un RemoteCacheObserver, mais il n'est pas très utile de le sous-classer ou de le modifier, alors traitez-le simplement comme un petit démon qui se trouve dans votre classe pb.Cacheable et vous aide à distribuer les notifications de changement. La seule chose utile à faire avec lui est d'exécuter sa méthode callRemote, qui agit comme une méthode pb.Referenceable normale du même nom.

Du côté récepteur, vous faites hériter votre classe de cache de pb.RemoteCache et implémentez le setCopyableState comme vous le feriez pour un objet pb.RemoteCopy. De plus, vous devez implémenter des méthodes pour recevoir les mises à jour envoyées à l'observateur par le pb.Cacheable: ces méthodes doivent avoir des noms qui commencent par observe_, et correspondre aux invocations callRemote du côté expéditeur tout comme les méthodes habituelles remote_* et correspondent aux appels normaux .perspective_* callRemote

La première fois qu'une référence à l'objet pb.Cacheable est envoyée à un destinataire particulier, un observateur côté expéditeur sera créé pour lui et la méthode getStateToCacheAndObserveFor sera appelée pour obtenir l'état actuel et enregistrer l'observateur. L'état renvoyé est envoyé à l'extrémité distante et transformé en une représentation locale en utilisant setCopyableState exactement comme pb.RemoteCopy, décrit ci-dessus (en fait, il hérite de cette classe).

Après cela, vos fonctions “setter” côté expéditeur doivent appeler l'observateur callRemote, ce qui entraîne observe_* l'exécution de méthodes sur le récepteur, qui sont ensuite censées mettre à jour l'état du récepteur local (en cache).

Lorsque le récepteur arrête de suivre l'objet mis en cache et que la dernière référence disparaît, l'objet pb.RemoteCache peut être libéré. Juste avant de mourir, il indique à l'expéditeur qu'il ne se soucie plus de l'objet d'origine. Lorsque ce décompte de références atteint zéro, l'observateur s'en va et l'objet pb.Cacheable peut arrêter d'annoncer chaque changement qui a lieu. La méthode stoppedObserving est utilisée pour dire au pb.Cacheable que l'Observateur est parti.

Avec les classes pb.Cacheable et pb.RemoteCache en place, liées ensemble par un appel à pb.setUnjellyableForClass, il ne reste plus qu'à passer une référence à votre pb.Cacheable sur le fil à l'extrémité distante. L'objet pb.RemoteCache correspondant sera automatiquement créé et les méthodes correspondantes seront utilisées pour maintenir l'objet esclave côté récepteur synchronisé avec l'objet maître côté émetteur.

Exemple

Voici un exemple complet, dans lequel le MasterDuckPondest contrôlé par le côté émetteur, et le SlaveDuckPondest un cache qui suit les modifications apportées au maître :

cache_classes.py
from twisted.spread import pb
class MasterDuckPond(pb.Cacheable):
    def __init__(self, ducks):
        self.observers = []
        self.ducks = ducks
    def count(self):
        print("I have [%d] ducks" % len(self.ducks))
    def addDuck(self, duck):
        self.ducks.append(duck)
        for o in self.observers:
            o.callRemote("addDuck", duck)
    def removeDuck(self, duck):
        self.ducks.remove(duck)
        for o in self.observers:
            o.callRemote("removeDuck", duck)
    def getStateToCacheAndObserveFor(self, perspective, observer):
        self.observers.append(observer)
        # you should ignore pb.Cacheable-specific state, like self.observers
        return self.ducks  # in this case, just a list of ducks
    def stoppedObserving(self, perspective, observer):
        self.observers.remove(observer)
class SlaveDuckPond(pb.RemoteCache):
    # This is a cache of a remote MasterDuckPond
    def count(self):
        return len(self.cacheducks)
    def getDucks(self):
        return self.cacheducks
    def setCopyableState(self, state):
        print(" cache - sitting, er, setting ducks")
        self.cacheducks = state
    def observe_addDuck(self, newDuck):
        print(" cache - addDuck")
        self.cacheducks.append(newDuck)
    def observe_removeDuck(self, deadDuck):
        print(" cache - removeDuck")
        self.cacheducks.remove(deadDuck)
pb.setUnjellyableForClass(MasterDuckPond, SlaveDuckPond)
cache_sender.py
from cache_classes import MasterDuckPond
from twisted.internet import reactor
from twisted.python import log
from twisted.spread import jelly, pb
class Sender:
    def __init__(self, pond):
        self.pond = pond
    def phase1(self, remote):
        self.remote = remote
        d = remote.callRemote("takePond", self.pond)
        d.addCallback(self.phase2).addErrback(log.err)
    def phase2(self, response):
        self.pond.addDuck("ugly duckling")
        self.pond.count()
        reactor.callLater(1, self.phase3)
    def phase3(self):
        d = self.remote.callRemote("checkDucks")
        d.addCallback(self.phase4).addErrback(log.err)
    def phase4(self, dummy):
        self.pond.removeDuck("one duck")
        self.pond.count()
        self.remote.callRemote("checkDucks")
        d = self.remote.callRemote("ignorePond")
        d.addCallback(self.phase5)
    def phase5(self, dummy):
        d = self.remote.callRemote("shutdown")
        d.addCallback(self.phase6)
    def phase6(self, dummy):
        reactor.stop()
def main():
    master = MasterDuckPond(["one duck", "two duck"])
    master.count()
    sender = Sender(master)
    factory = pb.PBClientFactory()
    reactor.connectTCP("localhost", 8800, factory)
    deferred = factory.getRootObject()
    deferred.addCallback(sender.phase1)
    reactor.run()
if __name__ == "__main__":
    main()
cache_receiver.py
import cache_classes
from twisted.application import internet, service
from twisted.internet import reactor
from twisted.spread import pb
class Receiver(pb.Root):
    def remote_takePond(self, pond):
        self.pond = pond
        print("got pond:", pond)  # a DuckPondCache
        self.remote_checkDucks()
    def remote_checkDucks(self):
        print("[%d] ducks: " % self.pond.count(), self.pond.getDucks())
    def remote_ignorePond(self):
        # stop watching the pond
        print("dropping pond")
        # gc causes __del__ causes 'decache' msg causes stoppedObserving
        self.pond = None
    def remote_shutdown(self):
        reactor.stop()
application = service.Application("copy_receiver")
internet.TCPServer(8800, pb.PBServerFactory(Receiver())).setServiceParent(
    service.IServiceCollection(application))
$ twistd -n -y cache_receiver.py
[-] twisted.spread.pb.PBServerFactory starting on 8800
[-] Starting factory <twisted.spread.pb.PBServerFactory instance at
0x40615acc>
[Broker,0,127.0.0.1]  cache - sitting, er, setting ducks
[Broker,0,127.0.0.1] got pond: <cache_classes.SlaveDuckPond instance at
0x406eb5ec>
[Broker,0,127.0.0.1] [2] ducks:  ['one duck', 'two duck']
[Broker,0,127.0.0.1]  cache - addDuck
[Broker,0,127.0.0.1] [3] ducks:  ['one duck', 'two duck', 'ugly duckling']
[Broker,0,127.0.0.1]  cache - removeDuck
[Broker,0,127.0.0.1] [2] ducks:  ['two duck', 'ugly duckling']
[Broker,0,127.0.0.1] dropping pond

$ ./cache_sender.py
I have [2] ducks
I have [3] ducks
I have [2] ducks
Main loop terminated.

Remarques

  • Il y en a un Observerpour chaque programme distant qui contient une référence active. Plusieurs références à l'intérieur du même programme n'ont pas d'importance : la couche de sérialisation remarque les doublons et effectue le comptage de références approprié.
  • Cela s'applique à plusieurs références via le même fichier Broker. Si vous avez réussi à établir plusieurs connexions TCP au même programme, vous méritez tout ce que vous obtenez.
  • Plusieurs observateurs doivent être conservés dans une liste, et tous doivent être mis à jour lorsque quelque chose change. En envoyant l'état initial en même temps que vous ajoutez l'observateur à la liste, en une seule action atomique qui ne peut pas être interrompue par un changement d'état, vous vous assurez de pouvoir envoyer la même mise à jour d'état à tous les observateurs.
  • Les observer.callRemoteappels peuvent encore échouer. Si le côté distant s'est déconnecté très récemment et stoppedObservingn'a pas encore été appelé, vous pouvez recevoir un DeadReferenceError. C'est une bonne idée d'ajouter un errback à ces callRemotes pour jeter une telle erreur. C'est un idiome utile:

observer.callRemote('foo', arg).addErrback(lambda f: None)

  • getStateToCacheAndObserverFordoit retourner un objet qui représente l'état actuel de l'objet. Il peut s'agir simplement de l' dictattribut de l'objet. C'est une bonne idée d'en supprimer les pb.Cacheablemembres spécifiques avant de l'envoyer à l'extrémité distante. La liste des Observers, en particulier, devrait être omise, pour éviter les références vertigineuses récursives Cacheable. L'esprit s'interroge sur les conséquences potentielles de laisser dans un tel article.
  • Un perspectiveargument est disponible pour getStateToCacheAndObserveFor, ainsi que stoppedObserving. Je pense que le but de ceci est de permettre des changements spécifiques au spectateur dans la façon dont le cache est mis à jour. Si tous les téléspectateurs distants sont censés voir les mêmes données, elles peuvent être ignorées.

Plus d'information

  • La meilleure source d'informations provient des docstrings dans twisted.spread.flavors, où pb.Cacheable est implémenté.
  • Le module spread.publish utilise également Cacheable et peut être une source d'informations supplémentaires.
perspective_broker_cacheable.txt · Dernière modification : 2022/12/15 10:18 de serge