2012-09-27 15 views
10

Ich habe ein Setup, wo Tornado als eine Art Pass-through für Arbeiter verwendet wird. Die Anfrage wird von Tornado empfangen, die diese Anfrage an N Worker sendet, Ergebnisse aggregiert und sie an den Client zurücksendet. Was funktioniert gut, außer wenn aus irgendeinem Grund Timeout auftritt — dann habe ich Speicherverlust.Tornado Speicherleck auf fallengelassenen Verbindungen

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

Was mache ich falsch:

Ich habe ein Setup, die ähnlich wie diese Pseudo-Code erhalten? Woher kommt das Speicherleck?

+0

Ich mag das nicht 'wenn len (Antworten) == len (Arbeiter): '- Sind Sie sicher, dass die Anwendung immer hier ankommt? Versuchen Sie, Versuche zu protokollieren, um einen Stapel von Anfragen und erfolgreichen Versuchen zu erstellen. –

+0

@Nikolay: Recht, AFAIK, verwendet Tornado einen Rückruf sowohl für Erfolg und Fehler. Daher bin ich mir ziemlich sicher, dass unabhängig davon, wie viele Mitarbeiter versagt haben, immer so viele Antworten eingehen. Ich bin mir nicht sicher, was passiert, wenn der Kunde die Anfrage storniert. – vartec

+0

Auch wenn Sie mehr als 10 Arbeiter haben, und alle sterben durch Timeout - Sie haben Zeit, wenn Tornado keine neue Verbindung erstellen kann - ich weiß nicht, wie es sich in diesem Moment verhält. Versuchen Sie, mit dem Argument max_clients zu spielen. –

Antwort

5

Ich kenne die Quelle des Problems nicht, und es scheint, GC sollte in der Lage sein, sich darum kümmern, aber es gibt zwei Dinge, die Sie versuchen können.

Die erste Methode einige der Referenzen zu vereinfachen wäre (es sieht aus wie es noch Hinweise auf responses sein kann, wenn die Request abgeschlossen):

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): 
     .... 

Wenn das, Sie können immer aufrufen funktioniert nicht Garbage Collection manuell:

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

von python 'gc' document. 'gc.garbage': Eine Liste von Objekten, die der Collector als nicht erreichbar, aber nicht freigegeben gefunden hat. Ich habe bemerkt, dass diese Liste beim Start leer ist, aber bei jeder Anfrage hinzugefügt wird. –

1

Der Code sieht gut aus. Das Leck ist wahrscheinlich in Tornado.

ich stolperte nur über diese Leitung:

async_client = tornado.httpclient.AsyncHTTPClient() 

Sind Sie sich der Instanziierung Magie in diesem Konstruktor? Aus der Dokumentation:

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

Also eigentlich brauchen Sie nicht diese innerhalb der Schleife zu tun. (Auf der anderen Seite Hand, sollte es nicht schaden.) Aber welche Implementierung sind Sie mit CurlAsyncHTTPClient oder SimpleAsyncHTTPClient?

Wenn es SimpleAsyncHTTPClient ist, sich bewusst sein diesen Kommentar im Code:

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

Sie können versuchen, zu CurlAsyncHTTPClient wechseln. Oder folgen Sie Nikolay Fominyhs Vorschlag und verfolgen Sie die Aufrufe von __callback().