8

我正在努力在使用 Deferred 对象的 Web 服务代码中产生与不使用 Deferred 对象的代码相同的行为。我的目标是编写一个装饰器,它将任何方法(与 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 文档和其他问题后,我不确定如何实现这一点。我原以为添加 errback 应该会促进这一点,但显然不是。一定有一些关于 Deferred 对象和 twisted.web 堆栈的东西我不理解。

我在此处记录的日志记录效果可能会受到我使用 PythonLoggingObserver 将 Twisted 日志记录连接到标准日志记录模块的影响。

当包含“E1”时,浏览器会一直等待,直到反应器关闭,此时会记录带有堆栈跟踪的 ValueError 异常,并且浏览器会收到一个空文档。

当包含“E2”时,会立即记录带有堆栈跟踪的 ValueError 异常,但浏览器会等待直到反应器关闭,此时它会收到一个空文档。

当包含“E3”时,立即记录带有堆栈跟踪的 ValueError 异常,浏览器等待直到反应器关闭,然后接收预期的文档。

当包含 raise 语句“E4”时,预期的文档会立即返回给浏览器,并立即记录带有堆栈跟踪的 ValueError 异常。(在这种情况下是否存在资源泄漏的可能性?)

4

2 回答 2

4

好的,在多次阅读您的问题后,我想我明白您的要求了。我还修改了你的代码,使其比你原来的答案好一点。这个新答案应该展示 deferred 的所有力量。

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教程。它将教您有关延迟的所有知识。

编辑:

修改了上面的代码以修复一些愚蠢的错误。也对其进行了改进。如果任何地方发生异常(除了 in self.errback,但我们需要一定程度的信任),那么它将被传递给self.errback它将记录或打印扭曲的错误,然后将跟踪发送到浏览器关闭请求。这应该可以阻止任何资源泄漏。

于 2011-05-06T14:44:33.227 回答
1

我通过挖掘 Twisted 的来源找到了答案。必要的见解是反应器和延迟回调/errback 链逻辑与请求对象解耦,这是数据返回浏览器的方式。errback 是必要的,但不能像我发布的原始代码那样仅仅将 Failure 对象沿链传播。errback 必须向浏览器报告错误。

下面的代码符合我的要求(永远不会让浏览器等待,总是给出堆栈跟踪,不需要重新启动反应器来让事情重新开始)并且允许我装饰阻塞方法,从而将它们委托给线程以保持反应器响应到其他事件(这些方法本质上将在这里代替 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 回答