python: httplib.CannotSendRequest при вложении многопоточных SimpleXMLRPCServers

Я периодически получаю исключение httplib.CannotSendRequest при использовании цепочки SimpleXMLRPCServers, которые используют SocketServer.ThreadingMixin.

Под цепочкой я подразумеваю следующее:

У меня есть клиентский скрипт, который использует xmlrpclib для вызова функции на SimpleXMLRPCServer. Этот сервер, в свою очередь, вызывает другой SimpleXMLRPCServer. Я понимаю, насколько запутанным это звучит, но есть веские причины, по которым эта архитектура была выбрана, и я не вижу причин, по которым это невозможно.

(testclient)client_script ---calls--> 
    (middleserver)SimpleXMLRPCServer ---calls---> 
        (finalserver)SimpleXMLRPCServer --- does something
  • Если я не использую SocketServer.ThreadingMixin, то эта проблема не возникает (но мне нужно, чтобы запросы были многопоточными, чтобы это не помогло).
  • Если у меня есть только один уровень услуг (т.е. только клиентский скрипт, вызывающий конечный сервер напрямую), этого не произойдет.

Мне удалось воспроизвести проблему в простом тестовом коде ниже. Есть три фрагмента:

finalserver:

import SocketServer
import time
from SimpleXMLRPCServer import SimpleXMLRPCServer
from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler

class AsyncXMLRPCServer(SocketServer.ThreadingMixIn,SimpleXMLRPCServer): pass

# Create server
server = AsyncXMLRPCServer(('', 9999), SimpleXMLRPCRequestHandler)
server.register_introspection_functions()

def waste_time():
    time.sleep(10)
    return True

server.register_function(waste_time, 'waste_time')
server.serve_forever()

middleserver:

import SocketServer
from SimpleXMLRPCServer import SimpleXMLRPCServer
from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler
import xmlrpclib

class AsyncXMLRPCServer(SocketServer.ThreadingMixIn,SimpleXMLRPCServer): pass

# Create server
server = AsyncXMLRPCServer(('', 8888), SimpleXMLRPCRequestHandler)
server.register_introspection_functions()

s = xmlrpclib.ServerProxy('http://localhost:9999')
def call_waste():
    s.waste_time()
    return True

server.register_function(call_waste, 'call_waste')
server.serve_forever()

testclient:

import xmlrpclib
s = xmlrpclib.ServerProxy('http://localhost:8888')
print s.call_waste()

Для воспроизведения необходимо использовать следующие шаги:

  1. Запустите python finalserver.py
  2. Запустите python middleserver.py
  3. Запустите python testclient.py
  4. Пока (3) все еще выполняется, запустите другой экземпляр python testclient.py

Довольно часто (почти каждый раз) вы получаете сообщение об ошибке ниже при первой попытке выполнить шаг 4. Интересно, что если вы сразу попытаетесь выполнить шаг (4) снова, ошибка не возникнет.

Traceback (most recent call last):
  File "testclient.py", line 6, in <module>
    print s.call_waste()
  File "/usr/lib64/python2.7/xmlrpclib.py", line 1224, in __call__
    return self.__send(self.__name, args)
  File "/usr/lib64/python2.7/xmlrpclib.py", line 1578, in __request
    verbose=self.__verbose
  File "/usr/lib64/python2.7/xmlrpclib.py", line 1264, in request
    return self.single_request(host, handler, request_body, verbose)
  File "/usr/lib64/python2.7/xmlrpclib.py", line 1297, in single_request
    return self.parse_response(response)
  File "/usr/lib64/python2.7/xmlrpclib.py", line 1473, in parse_response
    return u.close()
  File "/usr/lib64/python2.7/xmlrpclib.py", line 793, in close
    raise Fault(**self._stack[0])
xmlrpclib.Fault: <Fault 1: "<class 'httplib.CannotSendRequest'>:">

Интернет, кажется, говорит, что это исключение может быть вызвано множественными вызовами httplib.HTTPConnection.request без промежуточных вызовов getresponse. Тем не менее, Интернет не обсуждает это в контексте SimpleXMLRPCServer. Будем благодарны за любые указатели в направлении решения проблемы httplib.CannotSendRequest.

================================================== ========================================= ОТВЕТ:

Хорошо, я немного глуп Я думаю, что я смотрел на код слишком долгое время, что я пропустил очевидное решение, уставившееся мне в лицо (в буквальном смысле, потому что ответ на самом деле в реальном вопросе.)

По сути, CannotSendRequest возникает, когда httplib.HTTPConnection прерывается промежуточной операцией запроса. Каждый httplib.HTTPConnection.request должен быть связан с вызовом.getresponse(). Если это сопряжение прервано другой операцией запроса, второй запрос выдаст ошибку CannotSendRequest. так:

connection = httplib.HTTPConnection(...)
connection.request(...)
connection.request(...)

потерпит неудачу, потому что у вас есть два запроса на одно и то же соединение, прежде чем будет вызван любой ответ

Связав это с моим вопросом:

  1. единственное место в трех программах, где устанавливаются такие соединения, - это вызовы прокси-сервера.
  2. проблема возникает только во время многопоточности, поэтому, скорее всего, это состояние гонки.
  3. единственное место, где разделяется вызов serverproxy, находится в middleserver.py

Решение, очевидно, состоит в том, чтобы каждый поток создал свой собственный прокси-сервер. Ниже приведена исправленная версия middleserver, и она работает:

import SocketServer
from SimpleXMLRPCServer import SimpleXMLRPCServer
from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler
import xmlrpclib

class AsyncXMLRPCServer(SocketServer.ThreadingMixIn,SimpleXMLRPCServer): pass

# Create server
server = AsyncXMLRPCServer(('', 8888), SimpleXMLRPCRequestHandler)
server.register_introspection_functions()

def call_waste():
    # Each call to this function creates its own serverproxy.
    # If this function is called by concurrent threads, each thread
    # will safely have its own serverproxy.
    s = xmlrpclib.ServerProxy('http://localhost:9999')
    s.waste_time()
    return True

server.register_function(call_waste, 'call_waste')
server.serve_forever()

Поскольку эта версия приводит к тому, что каждый поток имеет свой собственный xmlrpclib.serverproxy, нет риска того, что один и тот же экземпляр serverproxy будет вызывать HTTPConnection.request более одного раза подряд. Программы работают как задумано.

Извините за беспокойство.

1 ответ

Решение

Хорошо, я немного глуп Я думаю, что я смотрел на код для того, чтобы затянуть период времени, когда я пропустил очевидное решение, уставившееся мне в лицо (в буквальном смысле, потому что ответ на самом деле в реальном вопросе).

По сути, CannotSendRequest возникает, когда httplib.HTTPConnection прерывается промежуточной операцией запроса. По сути, каждый httplib.HTTPConnection.request должен быть связан с вызовом.getresponse(). Если это сопряжение прервано другой операцией запроса, второй запрос выдаст ошибку CannotSendRequest. так:

connection = httplib.HTTPConnection(...)
connection.request(...)
connection.request(...)

потерпит неудачу, потому что у вас есть два запроса на одно и то же соединение, прежде чем будет вызван любой ответ

Связав это с моим вопросом:

  1. единственное место в трех программах, где устанавливаются такие соединения, - это вызовы прокси-сервера.
  2. проблема возникает только во время многопоточности, поэтому, скорее всего, это состояние гонки.
  3. единственное место, где разделяется вызов serverproxy, находится в middleserver.py

Решение, очевидно, состоит в том, чтобы каждый поток создал свой собственный прокси-сервер. Ниже приведена исправленная версия middleserver, и она работает:

import SocketServer
from SimpleXMLRPCServer import SimpleXMLRPCServer
from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler
import xmlrpclib

class AsyncXMLRPCServer(SocketServer.ThreadingMixIn,SimpleXMLRPCServer): pass

# Create server
server = AsyncXMLRPCServer(('', 8888), SimpleXMLRPCRequestHandler)
server.register_introspection_functions()

def call_waste():
    # Each call to this function creates its own serverproxy.
    # If this function is called by concurrent threads, each thread
    # will safely have its own serverproxy.
    s = xmlrpclib.ServerProxy('http://localhost:9999')
    s.waste_time()
    return True

server.register_function(call_waste, 'call_waste')
server.serve_forever()

Поскольку эта версия приводит к тому, что каждый поток имеет свой собственный xmlrpclib.serverproxy, нет риска того, что serverproxy вызовет HTTPConnection.request более одного раза подряд. Программы работают как задумано.

Извините за беспокойство.

Другие вопросы по тегам