8

Deferredオブジェクトを使用するWebサービスコードで、使用しないコードと同じ動作を生成するのに苦労しています。私の目的は、任意のメソッド(Twistedから切り離されている)の処理をTwistedスレッドプールに委任するデコレーターを作成して、そのメソッドのセマンティクスを変更せずに、リアクターがブロックされないようにすることです。

以下のクラスechoのインスタンスがWebサービスとして公開されると、次のコードが表示されます。

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

すべてのraiseステートメントがコメントアウトされるとブラウザにHTMLドキュメントが表示され、「E5」というラベルの付いたraiseステートメントが含まれると適切にフォーマットされたスタックトレースが表示されます(Twistedが私に代わって行います)。それが私が欲しいものです。同様に、Deferredオブジェクトをまったく使用せず、callback1とcallback2のすべての動作をrender_GET()内に配置すると、render_GET内のどこかで例外が発生すると、目的のスタックトレースが生成されます。

Twisted内でリソースリークを引き起こさず、ブラウザにすぐに応答し、raiseステートメント「E1」から「E3」のいずれかが含まれている場合にもブラウザスタックトレースを表示するコードを記述しようとしています。延期されたコード-もちろん、スタックトレース自体が異なることは理解しています。(「E4」の場合はあまり気にしません。)このサイトのTwistedドキュメントやその他の質問を読んだ後、これを実現する方法がわかりません。エラーバックを追加するとこれが容易になると思いましたが、明らかにそうではありません。遅延オブジェクトとtwisted.webスタックについて、私が理解していないことがあるはずです。

ここで説明するロギングへの影響は、PythonLoggingObserverを使用してTwistedロギングを標準のロギングモジュールにブリッジすることによって影響を受ける可能性があります。

「E1」が含まれている場合、ブラウザはリアクタがシャットダウンされるまで待機します。シャットダウンすると、スタックトレースを含むValueError例外がログに記録され、ブラウザは空のドキュメントを受け取ります。

「E2」が含まれている場合、スタックトレースを含むValueError例外はすぐにログに記録されますが、ブラウザはリアクタがシャットダウンするまで待機し、その時点で空のドキュメントを受け取ります。

「E3」が含まれている場合、スタックトレースを含むValueError例外がすぐにログに記録され、ブラウザはリアクタがシャットダウンするまで待機し、その時点で目的のドキュメントを受信します。

発生ステートメント「E4」が含まれている場合、目的のドキュメントはすぐにブラウザに返され、スタックトレースを含むValueError例外がすぐにログに記録されます。(この場合、リソースリークの可能性はありますか?)

4

2 に答える 2

4

さて、あなたの質問を数回読んだ後、私はあなたの質問が何であるかを理解したと思います。また、元の回答よりも少し良くなるようにコードを作り直しました。この新しい答えは、延期されたもののすべての力を誇示するはずです。

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

また、 krondoチュートリアルを読むことをお勧めします。それはあなたが延期について知る必要があるすべてをあなたに教えるでしょう。

編集:

いくつかのばかげたバグを修正するために上記のコードを変更しました。また、それを改善しました。例外がどこかで発生した場合(を除くがself.errback、ある程度の信頼が必要です)、例外が渡されself.errback、エラーがtwistedでログに記録または出力され、トレースがブラウザーに送信され要求が閉じられます。これにより、リソースリークが停止します。

于 2011-05-06T14:44:33.227 に答える
1

Twistedソースを掘り下げて理解しました。必要な洞察は、reactorとDeferredコールバック/エラーバックチェーンロジックがリクエストオブジェクトから分離されていることです。これにより、データがブラウザに返されます。errbackは必要ですが、私が投稿した元のコードのように、Failureオブジェクトをチェーンの下流に単純に伝播することはできません。errbackは、ブラウザにエラーを報告する必要があります。

以下のコードは私の要件を満たし(ブラウザを待たせず、常にスタックトレースを提供し、処理を再開するためにreactorを再起動する必要はありません)、ブロッキングメソッドを装飾して、reactorの応答性を維持するためにスレッドに委任することができます他のイベントへ(このようなメソッドは基本的にここではcallback1の代わりになります)。ただし、以下のコードで、「E4」raiseステートメントのコメントを解除すると、後続のブラウザー要求(ブラウザーに返された以前の要求からの部分データ、デッドロック)で非常に奇妙な動作が発生することがわかりました。

うまくいけば、他の人がこれが有用な延期された例であることがわかるでしょう。

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
于 2011-05-06T14:21:44.180 に答える