Question

J'ai du mal à produire le même comportement dans le code de service Web qui utilise des objets différés comme dans le code qui ne le fait pas. Mon objectif est d'écrire un décorateur qui déléguera le traitement de n'importe quelle méthode (qui est découplé de Twisted) au pool de threads torsadé, de sorte que le réacteur n'est pas bloqué, sans modifier la sémantique de cette méthode.

Lorsqu'une instance d'écho de classe ci-dessous est exposée en tant que service Web, ce code:

from twisted.web import server, resource
from twisted.internet import defer, threads
from cgi import escape
from itertools import count

class echo(resource.Resource):
  isLeaf = True
  def errback(self, failure): return failure
  def callback1(self, request, value):
    #raise ValueError  # E1
    lines = ['<html><body>\n',
             '<p>Page view #%s in this session</p>\n' % (value,),
             '</body></html>\n']
    return ''.join(lines)
  def callback2(self, request, encoding):
    def execute(message):
      #raise ValueError  # E2
      request.write(message.encode(encoding))
      #raise ValueError  # E3
      request.finish()
      #raise ValueError  # E4
      return server.NOT_DONE_YET
    return execute
  def render_GET(self, request):
    content_type, encoding = 'text/html', 'UTF-8'
    request.setHeader('Content-Type', '%s; charset=%s' %
        tuple(map(str, (content_type, encoding))))
    s = request.getSession()
    if not hasattr(s, 'counter'):
      s.counter = count(1)
    d = threads.deferToThread(self.callback1, request, s.counter.next())
    d.addCallback(self.callback2(request, encoding))
    d.addErrback(self.errback)
    #raise ValueError  # E5
    return server.NOT_DONE_YET

Affichera un document HTML au navigateur lorsque toutes les instructions RISE sont commentées et affichent une trace de pile bien formatée (ce qui fait Twisted pour moi) lorsque l'instruction RISPLET étiquetée "E5" est incluse. C'est ce que je veux. De même, si je n'utilise pas du tout d'objets différés et que je place tout le comportement de Callback1 et de Callback2 dans render_get (), une exception soulevée n'importe où dans Render_get produira la trace de pile souhaitée.

J'essaie d'écrire du code qui répondra immédiatement au navigateur, ne provoquera pas de fuites de ressources dans Twisted, et entraînera également l'affichage de la trace de pile du navigateur dans les cas où l'une des instructions de relance "E1" à "E3" est incluse dans Le code différé - bien que je comprends bien sûr que la pile se trace elles-mêmes. (Le cas "E4" que je ne me soucie pas autant.) Après avoir lu la documentation tordue et d'autres questions sur ce site, je ne sais pas comment y parvenir. J'aurais pensé que l'ajout d'un errback devrait faciliter cela, mais évidemment non. Il doit y avoir quelque chose à propos des objets différés et de la pile Twisted.Web que je ne comprends pas.

Les effets sur l'exploitation de l'exploitation I Ici peuvent être affectés par mon utilisation du PythonLoggingObserver pour rejeter la journalisation tordue au module de journalisation standard.

Lorsque "E1" est inclus, le navigateur attend jusqu'à l'arrêt du réacteur, à quel point l'exception de valeur d'énergie avec la trace de pile soit enregistrée et le navigateur reçoit un document vide.

Lorsque "E2" est inclus, l'exception ValueError avec la trace de pile est enregistrée immédiatement, mais le navigateur attend que le réacteur s'arrête à quel point il reçoit un document vide.

Lorsque "E3" est inclus, l'exception ValueError avec la trace de pile est immédiatement enregistrée, le navigateur attend que le réacteur s'arrête et reçoit à ce stade le document prévu.

Lorsque l'énoncé de relance "E4" est inclus, le document prévu est renvoyé immédiatement au navigateur et l'exception ValueError avec la trace de pile est enregistrée immédiatement. (Y a-t-il une possibilité d'une fuite de ressources dans ce cas?)

Était-ce utile?

La solution

OK, après avoir lu votre question plusieurs fois, je pense que je comprends ce que vous demandez. J'ai également retravaillé votre code pour rendre un peu meilleur que votre réponse originale. Cette nouvelle réponse devrait montrer tous les pouvoirs des reportages.

from twisted.web import server, resource
from twisted.internet import defer, threads
from itertools import count

class echo(resource.Resource):
  isLeaf = True
  def errback(self, failure, request):
    failure.printTraceback() # This will print the trace back in a way that looks like a python exception.
    # log.err(failure) # This will use the twisted logger. This is the best method, but
    # you need to import twisted log.

    request.processingFailed(failure) # This will send a trace to the browser and close the request.
    return None #  We have dealt with the failure. Clean it out now.

  def final(self, message, request, encoding): 
    # Message will contain the message returned by callback1
    request.write(message.encode(encoding)) # This will write the message and return it to the browser.

    request.finish() # Done

  def callback1(self, value):
    #raise ValueError  # E1
    lines = ['<html><body>\n',
             '<p>Page view #%s in this session</p>\n' % (value,),
             '</body></html>\n']
    return ''.join(lines)

    #raise ValueError  # E4

  def render_GET(self, request):
    content_type, encoding = 'text/html', 'UTF-8'
    request.setHeader('Content-Type', '%s; charset=%s' %
        tuple(map(str, (content_type, encoding))))
    s = request.getSession()
    if not hasattr(s, 'counter'):
      s.counter = count(1)
    d = threads.deferToThread(self.callback1, s.counter.next())
    d.addCallback(self.final, request, encoding)
    d.addErrback(self.errback, request) # We put this here in case the encoding raised an exception.
    #raise ValueError  # E5
    return server.NOT_DONE_YET

Je recommande également de lire le krondo Didacticiel. Cela vous apprendra tout ce que vous devez savoir sur le report.

Éditer:

Ont modifié le code ci-dessus pour corriger certains bogues idiots. L'a également amélioré. Si une exception se produit n'importe où (sauf dans self.errback, mais nous avons besoin d'un certain niveau de confiance) alors il sera transmis à self.errback qui enregistrera ou imprimera l'erreur dans Twisted, puis envoie la trace au navigateur et Fermez la demande. Cela devrait arrêter toutes les fuites de ressources.

Autres conseils

Je l'ai compris en creusant à travers la source tordue. Le point de vue nécessaire est que la logique de la chaîne de rappel / de la chaîne de rappel différée est découplée à partir de l'objet de demande, c'est comment les données reviennent au navigateur. L'errback est nécessaire, mais ne peut pas simplement propager l'objet de défaillance dans la chaîne comme dans le code d'origine que j'ai publié. L'errback doit signaler l'erreur au navigateur.

Le code ci-dessous répond à mes exigences (ne garde jamais le navigateur en attente, donne toujours la trace de pile, ne nécessite pas de redémarrage du réacteur pour faire avancer les choses) et me permettra de décorer les méthodes de blocage et de les déléguer ainsi aux fils pour garder le réacteur réactif réactif à d'autres événements (de telles méthodes remplaceront essentiellement le callback1 ici). Cependant, j'ai constaté que dans le code ci-dessous, la non-communication de l'instruction "E4" produit un comportement très étrange sur les demandes de navigateur ultérieures (données partielles des demandes précédentes renvoyées au navigateur; impasse).

Espérons que d'autres trouveront que c'est un exemple différé utile.

from twisted.web import server, resource
from twisted.internet import defer, threads
from itertools import count

class echo(resource.Resource):
  isLeaf = True
  def errback(self, request):
    def execute(failure):
      request.processingFailed(failure)
      return failure
    return execute
  def callback1(self, value):
    #raise ValueError  # E1
    lines = ['<html><body>\n',
             '<p>Page view #%s in this session</p>\n' % (value,),
             '</body></html>\n']
    return ''.join(lines)
  def callback2(self, request, encoding):
    def execute(message):
      #raise ValueError  # E2
      request.write(message.encode(encoding))
      #raise ValueError  # E3
      request.finish()
      #raise ValueError  # E4
      return server.NOT_DONE_YET
    return execute
  def render_GET(self, request):
    content_type, encoding = 'text/html', 'UTF-8'
    request.setHeader('Content-Type', '%s; charset=%s' %
        tuple(map(str, (content_type, encoding))))
    s = request.getSession()
    if not hasattr(s, 'counter'):
      s.counter = count(1)
    d = threads.deferToThread(self.callback1, s.counter.next())
    eback = self.errback(request)
    d.addErrback(eback)
    d.addCallback(self.callback2(request, encoding))
    d.addErrback(eback)
    #raise ValueError  # E5
    return server.NOT_DONE_YET
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top