2012-11-23 17 views
7

Ricevo a intermittenza un'eccezione httplib.CannotSendRequest quando si utilizza una catena di SimpleXMLRPCServers che utilizza SocketServer.ThreadingMixin.python: httplib.CannotSendRequest durante l'annidamento di thread SimpleXMLRPCServers

Cosa intendo per 'catena' è la seguente:

Ho uno script client che utilizza xmlrpclib di chiamare una funzione su un SimpleXMLRPCServer. Quel server, a sua volta, chiama un altro SimpleXMLRPCServer. Mi rendo conto di quanto suoni contorto, ma ci sono buone ragioni per cui questa architettura è stata selezionata, e non vedo una ragione per cui non dovrebbe essere possibile.

(testclient)client_script ---calls--> 
    (middleserver)SimpleXMLRPCServer ---calls---> 
     (finalserver)SimpleXMLRPCServer --- does something 
  • Se io non uso SocketServer.ThreadingMixin allora questo problema non si verifica (ma ho bisogno le richieste di essere multi-threaded quindi questo non aiuta.)
  • Se ho solo un singolo livello di servizi (cioè solo script client che chiama direttamente il server finale) questo non accade.

sono stato in grado di riprodurre il problema nella semplice codice di prova di seguito. Ci sono tre frammenti:

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

Per riprodurre, devono essere utilizzati i seguenti passi:

  1. Run pitone finalserver.py
  2. Run pitone middleserver.py
  3. Run pitone testclient.py
  4. Mentre (3) è ancora in funzione, eseguire un'altra istanza di pitone testclient.py

abbastanza spesso (quasi ogni volta) si verifica l'errore sotto la prima volta che si tenta di eseguire il passaggio 4. È interessante notare che se si tenta immediatamente di eseguire il passaggio (4) di nuovo l'errore non si verificherà.

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 sembri dire che questa eccezione può essere causato da più chiamate a httplib.HTTPConnection.request senza intervenire chiamate GetResponse. Tuttavia, Internet non ne discute nel contesto di SimpleXMLRPCServer. Sarebbe apprezzato qualsiasi suggerimento nella direzione di risolvere il problema httplib.CannotSendRequest.

============================================== ============================================= RISPOSTA:

Ok, sono un po 'stupido. Penso che stavo fissando il codice per un periodo di tempo troppo lungo che mi mancava l'ovvia soluzione che mi guardava in faccia (letteralmente, perché la risposta è in realtà nella domanda reale.)

Fondamentalmente, la CannotSendRequest si verifica quando un httplib.HTTPConnection viene interrotto da un'operazione "richiesta" intervenuta.Ogni httplib.HTTPConnection.request deve essere associato a una chiamata .getresponse(). Se tale associazione viene interrotta da un'altra operazione di richiesta, la seconda richiesta genererà l'errore CannotSendRequest. quindi:

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

non riuscirà perché si hanno due richieste sulla stessa connessione prima che venga richiamata qualsiasi risposta ricevuta.

Collegamento che di nuovo alla mia domanda:

  1. l'unico posto nei tre programmi in cui tali collegamenti sono stati fatti sono nelle chiamate ServerProxy.
  2. il problema si verifica solo durante il threading, quindi è probabile che si tratti di una race condition.
  3. l'unico luogo una chiamata ServerProxy è condivisa è in middleserver.py

La soluzione quindi, è ovviamente avere ogni thread creare il proprio ServerProxy. La versione fissa di middleserver è inferiore, e funziona:

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

Da questa versione risultati in ogni filo con la propria xmlrpclib.serverproxy, non v'è alcun rischio che il stessa istanza di ServerProxy invocare HTTPConnection.request più di una volta in successione. I programmi funzionano come previsto.

Scusate per il disturbo.

risposta

11

Ok, sono un po 'stupido. Penso che stavo fissando il codice per protrarre un periodo di tempo in cui mi mancava l'ovvia soluzione che mi guardava in faccia (letteralmente, perché la risposta è effettivamente nella domanda reale.)

Fondamentalmente, la CannotSendRequest si verifica quando un httplib.HTTPConnection viene interrotto da un'operazione "richiesta" intervenuta. Fondamentalmente, ogni httplib.HTTPConnection.request deve essere associato a una chiamata .getresponse(). Se tale associazione viene interrotta da un'altra operazione di richiesta, la seconda richiesta genererà l'errore CannotSendRequest. così:

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

non riuscirà perché si dispone di due richieste sulla stessa connessione prima qualsiasi getresponse è chiamato.

Collegamento che di nuovo alla mia domanda:

  1. l'unico posto nei tre programmi in cui tali collegamenti sono stati fatti sono nelle chiamate ServerProxy.
  2. il problema si verifica solo durante il threading, quindi è probabile che si tratti di una race condition.
  3. l'unico luogo una chiamata ServerProxy è condivisa è in middleserver.py

La soluzione quindi, è ovviamente avere ogni thread creare il proprio ServerProxy. La versione fissa di middleserver è inferiore, e funziona:

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

Da questa versione risultati in ogni filo con la propria xmlrpclib.serverproxy, non v'è alcun rischio che il ServerProxy invocare HTTPConnection.request più di una volta in successione. I programmi funzionano come previsto.

Scusate per il disturbo.