2012-11-23 8 views
7

Nieregularnie odbieram wyjątek httplib.CannotSendRequest, gdy używam łańcucha SimpleXMLRPCServers korzystającego z SocketServer.ThreadingMixin.python: httplib.CannotSendRequest podczas zagnieżdżania wątkowanych SimpleXMLRPCServers

Co mam na myśli przez „łańcuch” jest następujący:

Mam skrypt klienta, który wykorzystuje xmlrpclib wywołać funkcję na SimpleXMLRPCServer. Ten serwer z kolei wywołuje inny serwer SimpleXMLRPC. Zdaję sobie sprawę, że brzmi jak zawiłe, ale istnieją powody, że architektura ta została wybrana, a nie widzę powodu, to nie powinno być możliwe.

(testclient)client_script ---calls--> 
    (middleserver)SimpleXMLRPCServer ---calls---> 
     (finalserver)SimpleXMLRPCServer --- does something 
  • Jeśli nie używam SocketServer.ThreadingMixin to ten problem nie występuje (ale muszę żądania być wielowątkowy więc to nie pomoże.)
  • Gdybym tylko pojedynczy poziom usług (tj. po prostu skrypt klienta wywołujący bezpośrednio serwer końcowy) tak się nie dzieje.

Byłem w stanie odtworzyć problem w prostym kodzie testu poniżej. Są trzy fragmenty:

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() 

Aby odtworzyć, należy stosować następujące kroki:

  1. Run pyton finalserver.py
  2. Run pyton middleserver.py
  3. Run pyton testclient.py
  4. Chociaż (3) nadal działa, należy uruchomić inne wystąpienie Pythona testclient.py

Dość często (prawie za każdym razem) otrzymasz błąd poniżej po raz pierwszy spróbować uruchomić krok 4. Co ciekawe, jeśli natychmiast spróbować uruchomić etap (4) ponownie błąd nie występuje.

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'>:"> 

Internet wydaje się, że wyjątek ten może być spowodowane przez wiele połączeń do httplib.HTTPConnection.request bez interwencji połączeń GetResponse. Jednak Internet nie omawia tego w kontekście SimpleXMLRPCServer. Dowolne wskazówki w kierunku rozwiązania problemu httplib.CannotSendRequest będą mile widziane.

============================================== ============================================= ODPOWIEDŹ:

Ok, jestem trochę głupie. Myślę, że patrzyłem na kod przez zbyt długi czas, że przegapiłem oczywiste rozwiązanie, patrząc mi w twarz (całkiem dosłownie, ponieważ odpowiedź brzmi właściwie w rzeczywistym pytaniu).

Zasadniczo pojawia się CannotSendRequest gdy połączenie httplib.HTTPC przerwane jest przez interweniującą operację "request".Każde httplib.HTTPConnection.request musi być sparowane z wywołaniem .getresponse(). Jeśli parowanie zostanie przerwane przez inną operację żądania, drugie żądanie spowoduje błąd CannotSendRequest. tak:

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

zakończy się niepowodzeniem, ponieważ masz dwa żądania na tym samym połączeniu, zanim zostanie wywołana jakakolwiek reakcja.

Łączenie tego z powrotem do mojego pytania:

  1. jedyne miejsce w trzech programach, w których takie połączenia są wykonane w zaproszeniach serverproxy.
  2. Problem występuje tylko podczas gwintowania, więc prawdopodobnie jest to stan wyścigu.
  3. jedyne miejsce połączenia serverproxy dzielone jest w middleserver.py

Rozwiązaniem jest więc oczywiście mieć każdy wątek stworzyć swój własny serverproxy. Poprawiona wersja middleserver jest poniżej, a to działa:

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() 

Od tego Wersja wyników w każdym wątku posiadającego własną xmlrpclib.serverproxy, nie ma ryzyka samej instancji z serverproxy powołuje HTTPConnection.request więcej niż jeden raz z rzędu. Programy działają zgodnie z przeznaczeniem.

Przepraszamy za kłopot.

Odpowiedz

11

OK, jestem trochę głupi. Myślę, że wpatrywałam się w kod, który przeciągał się przez pewien okres czasu, że przegapiłem oczywiste rozwiązanie, wpatrujące się we mnie w twarz (całkiem dosłownie, ponieważ odpowiedź brzmi właściwie w rzeczywistym pytaniu).

Zasadniczo pojawia się CannotSendRequest gdy połączenie httplib.HTTPC przerwane jest przez interweniującą operację "request". Zasadniczo każde zadanie httplib.HTTPConnection.request musi być sparowane z wywołaniem .getresponse(). Jeśli parowanie zostanie przerwane przez inną operację żądania, drugie żądanie spowoduje błąd CannotSendRequest. tak:

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

zakończy się niepowodzeniem, ponieważ masz dwa żądania na tym samym połączeniu, zanim zostanie wywołana jakakolwiek reakcja.

Łączenie tego z powrotem do mojego pytania:

  1. jedyne miejsce w trzech programach, w których takie połączenia są wykonane w zaproszeniach serverproxy.
  2. Problem występuje tylko podczas gwintowania, więc prawdopodobnie jest to stan wyścigu.
  3. jedyne miejsce połączenia serverproxy dzielone jest w middleserver.py

Rozwiązaniem jest więc oczywiście mieć każdy wątek stworzyć swój własny serverproxy. Poprawiona wersja middleserver jest poniżej, a to działa:

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() 

Od tego Wersja wyników w każdym wątku posiadającego własną xmlrpclib.serverproxy, nie ma ryzyka serverproxy powołuje HTTPConnection.request więcej niż raz z rzędu. Programy działają zgodnie z przeznaczeniem.

Przepraszamy za kłopot.

Powiązane problemy