2012-09-27 8 views
10

Tengo una configuración en la que Tornado se utiliza como una especie de paso para trabajadores. Tornado recibe la solicitud, que envía esta solicitud a N trabajadores, agrega los resultados y los envía al cliente. Lo cual funciona bien, excepto cuando por algún motivo se agota el tiempo de espera —, entonces tengo una pérdida de memoria.Fuga de memoria de tornado en conexiones caídas

Tengo una configuración similar a la que este pseudocódigo:

workers = ["http://worker1.example.com:1234/", 
      "http://worker2.example.com:1234/", 
      "http://worker3.example.com:1234/" ...] 

class MyHandler(tornado.web.RequestHandler): 
    @tornado.web.asynchronous 
    def post(self): 
     responses = [] 

     def __callback(response): 
      responses.append(response) 
      if len(responses) == len(workers): 
       self._finish_req(responses) 

     for url in workers: 
      async_client = tornado.httpclient.AsyncHTTPClient() 
      request = tornado.httpclient.HTTPRequest(url, method=self.request.method, body=body) 
      async_client.fetch(request, __callback) 

    def _finish_req(self, responses): 
     good_responses = [r for r in responses if not r.error] 
     if not good_responses: 
      raise tornado.web.HTTPError(500, "\n".join(str(r.error) for r in responses)) 
     results = aggregate_results(good_responses) 
     self.set_header("Content-Type", "application/json") 
     self.write(json.dumps(results)) 
     self.finish() 

application = tornado.web.Application([ 
    (r"/", MyHandler), 
]) 

if __name__ == "__main__": 
    ##.. some locking code 
    application.listen() 
    tornado.ioloop.IOLoop.instance().start() 

¿Qué estoy haciendo mal? ¿De dónde viene la fuga de memoria?

+0

No me gusta esto 'si len (responses) == len (workers):' - ¿estás seguro de que la aplicación siempre llega aquí? Intente registrar intentos para realizar un lote de solicitudes e intentos exitosos. –

+0

@Nikolay: a la derecha, AFAIK, Tornado utiliza una devolución de llamada tanto para el éxito como para el error. Por lo tanto, estoy bastante seguro de que, independientemente de la cantidad de trabajadores que fallaron, siempre recibe muchas respuestas. Lo que no estoy seguro es qué sucede cuando el cliente cancela la solicitud. – vartec

+0

Además, si tiene más de 10 trabajadores, y todos ellos mueren por tiempo de espera, tiene un período de tiempo cuando el tornado no puede crear una nueva conexión, no sé cómo se comporta en este momento. Intenta jugar con el argumento 'max_clients'. –

Respuesta

5

No sé la fuente del problema, y ​​parece que gc debería ser capaz de encargarse de ello, pero hay dos cosas que puedes probar.

El primer método sería simplificar algunas de las referencias (que parece que todavía puede haber referencias a responses cuando el RequestHandler completa):

class MyHandler(tornado.web.RequestHandler): 
    @tornado.web.asynchronous 
    def post(self): 
     self.responses = [] 

     for url in workers: 
      async_client = tornado.httpclient.AsyncHTTPClient() 
      request = tornado.httpclient.HTTPRequest(url, method=self.request.method, body=body) 
      async_client.fetch(request, self._handle_worker_response) 

    def _handle_worker_response(self, response): 
     self.responses.append(response) 
     if len(self.responses) == len(workers): 
      self._finish_req() 

    def _finish_req(self): 
     .... 

Si eso no funciona, siempre se puede invocar recolección de basura manualmente:

import gc 
class MyHandler(tornado.web.RequestHandler): 
    @tornado.web.asynchronous 
    def post(self): 
     .... 

    def _finish_req(self): 
     .... 

    def on_connection_close(self): 
     gc.collect() 
+0

de python 'gc' documention. 'gc.garbage': una lista de objetos que el recopilador encontró inalcanzables pero que no pudieron ser liberados. Me di cuenta de que esta lista está vacía al inicio, pero se agrega en cada solicitud. –

1

El código se ve bien. La fuga probablemente esté dentro de Tornado.

sólo tropezó con esta línea:

async_client = tornado.httpclient.AsyncHTTPClient() 

¿Es consciente de la magia de instancias de este constructor? De la documentación:

""" 
The constructor for this class is magic in several respects: It actually 
creates an instance of an implementation-specific subclass, and instances 
are reused as a kind of pseudo-singleton (one per IOLoop). The keyword 
argument force_instance=True can be used to suppress this singleton 
behavior. Constructor arguments other than io_loop and force_instance 
are deprecated. The implementation subclass as well as arguments to 
its constructor can be set with the static method configure() 
""" 

Así que en realidad, usted no necesita hacer esto dentro del bucle. (En la otra mano, , no debería hacer ningún daño). ¿Pero qué implementación está usando usando CurlAsyncHTTPClient o SimpleAsyncHTTPClient?

Si es SimpleAsyncHTTPClient, ser conscientes de este comentario en el código:

""" 
This class has not been tested extensively in production and 
should be considered somewhat experimental as of the release of 
tornado 1.2. 
""" 

Usted puede tratar de cambiar a CurlAsyncHTTPClient. O siga la sugerencia de Nikolay Fominyh y rastree las llamadas a __callback().