Искаженная отсрочка против блокировки в веб-сервисах

StackOverflow https://stackoverflow.com/questions/5904734

Вопрос

Я изо всех сил пытаюсь добиться такого же поведения в коде веб-службы, который использует отложенные объекты, как и в коде, который этого не делает.Моя цель - написать декоратор, который будет делегировать обработку любого метода (который отделен от Twisted) пулу Twisted thread, чтобы реактор не был заблокирован, без изменения какой-либо семантики этого метода.

Когда экземпляр класса echo, приведенный ниже, предоставляется как веб-служба, этот код:

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

отобразит HTML-документ в браузере, когда все операторы raise будут закомментированы, и отобразит красиво отформатированную трассировку стека (что Twisted делает для меня), когда будет включен оператор raise с пометкой "E5".Это то, чего я хочу.Аналогично, если я вообще не использую отложенные объекты и помещаю все поведение из callback1 и callback2 в render_GET(), исключение, возникающее в любом месте render_GET, приведет к созданию желаемой трассировки стека.

Я пытаюсь написать код, который будет немедленно реагировать на браузер, не вызывать утечек ресурсов внутри Twisted и заставлять трассировку стека браузера также отображаться в тех случаях, когда любой из операторов повышения с "E1" на "E3" включен в отложенный код - хотя, конечно, я понимаю, что сами трассировки стека будут другими.(Случай "E4" меня не волнует так сильно.) После прочтения документации Twisted и других вопросов на этом сайте я не уверен, как этого добиться.Я бы подумал, что добавление обратной ошибки должно облегчить это, но, очевидно, это не так.Должно быть что-то об отложенных объектах и twisted.веб-стек, который я не понимаю.

Влияние на ведение журнала, которое я описываю здесь, может быть вызвано моим использованием PythonLoggingObserver для подключения искаженного ведения журнала к стандартному модулю ведения журнала.

Когда включено значение "E1", браузер ожидает, пока реактор не будет выключен, после чего регистрируется исключение ValueError с трассировкой стека, и браузер получает пустой документ.

Когда включено "E2", исключение ValueError с трассировкой стека регистрируется немедленно, но браузер ждет, пока реактор не выключится, после чего он получает пустой документ.

Когда включен параметр "E3", исключение ValueError с трассировкой стека регистрируется немедленно, браузер ожидает, пока реактор не выключится, и в этот момент получает требуемый документ.

Когда включена инструкция raise "E4", предполагаемый документ немедленно возвращается в браузер, и исключение ValueError с трассировкой стека немедленно регистрируется.(Есть ли какая-либо вероятность утечки ресурсов в этом случае?)

Это было полезно?

Решение

Хорошо, прочитав ваш вопрос несколько раз, я думаю, что понимаю, о чем вы спрашиваете.Я также переработал ваш код, чтобы сделать его немного лучше вашего первоначального ответа.Этот новый ответ должен продемонстрировать все возможности 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

Также я рекомендую вам ознакомиться с крондо Учебник.Это научит вас всему, что вам нужно знать о deferred.

Редактировать:

Изменили приведенный выше код, чтобы исправить некоторые глупые ошибки.Также улучшил его.Если исключение происходит где угодно (кроме как в self.errback, но нам нужен некоторый уровень доверия), тогда это будет передано self.errback который зарегистрирует или распечатает ошибку в twisted, а затем отправит трассировку в браузер и закройте запрос.Это должно остановить любые утечки ресурсов.

Другие советы

Я понял это, покопавшись в Искаженном источнике.Необходимое понимание заключается в том, что логика реактора и цепочки отложенных обратных вызовов / ошибок отделена от объекта запроса, именно так данные возвращаются в браузер.Обратная ошибка необходима, но она не может просто распространить объект Failure вниз по цепочке, как в исходном коде, который я опубликовал.Функция возврата с ошибкой должна сообщить об ошибке браузеру.

Приведенный ниже код соответствует моим требованиям (никогда не заставляет браузер ждать, всегда выдает трассировку стека, не требует перезапуска реактора, чтобы все снова заработало) и позволит мне украсить методы блокировки и тем самым делегировать их потокам, чтобы реактор реагировал на другие события (такие методы здесь, по сути, заменят callback1).Однако я обнаружил, что в приведенном ниже коде раскомментирование оператора raise "E4" приводит к очень странному поведению при последующих запросах браузера (частичные данные из предыдущих запросов возвращаются в браузер;тупик).

Надеюсь, другие сочтут это полезным отложенным примером.

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
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top