2012-09-27 5 views
10

Mam konfigurację, w której Tornado służy jako rodzaj przejścia dla pracowników. Żądanie jest odbierane przez Tornado, które wysyła tę prośbę do N pracowników, agreguje wyniki i odsyła je do klienta. Które działa dobrze, z wyjątkiem sytuacji, gdy z jakiegoś powodu upłynął limit czasu —, a następnie mam wyciek pamięci.Wyciek pamięci Tornado na porzuconych połączeniach

Mam konfigurację podobną do tej, która Pseudokod:

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

Co robię źle? Skąd bierze się wyciek pamięci?

+0

Nie podoba mi się to "jeśli len (odpowiedzi) == len (pracownicy):" - czy jesteś pewien, że aplikacja zawsze tu przychodzi? Spróbuj zarejestrować próby zestawienia żądań i pomyślnych prób. –

+0

@Nikolay: w prawo, AFAIK, Tornado używa wywołania zwrotnego zarówno do sukcesu, jak i błędu. W ten sposób jestem pewien, że niezależnie od tego, ilu pracowników zawiodło, zawsze otrzymuję tyle odpowiedzi. Nie jestem pewien, co się dzieje, gdy klient anuluje wniosek. – vartec

+0

Także, jeśli masz więcej niż 10 pracowników, a wszystkie z nich umierają z powodu przekroczenia limitu czasu - masz okres, w którym tornado nie może utworzyć nowego połączenia - nie wiem, jak zachowuje się w tym momencie. Spróbuj zagrać z argumentem 'max_clients'. –

Odpowiedz

5

nie wiem źródło problemu, i wydaje się, GC powinien być w stanie zadbać o to, ale nie dwie rzeczy można spróbować.

Pierwsza metoda byłoby uproszczenie niektórych odniesień (wygląda na to, że może być jeszcze odniesień do responses gdy RequestHandler kończy):

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

Jeśli to nie pomoże, zawsze można powołać kolekcja śmieci ręcznie:

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

z Pythona 'gc' documention. 'gc.garbage': Lista przedmiotów, które kolekcjoner okazał się nieosiągalny, ale których nie można uwolnić. Zauważyłem, że ta lista jest pusta podczas uruchamiania, ale została dodana do każdego żądania. –

1

Kod wygląda dobrze. Przeciek jest prawdopodobnie wewnątrz Tornado.

ja tylko potknął się tej linii:

async_client = tornado.httpclient.AsyncHTTPClient() 

jesteś świadomy magii konkretyzacji w tym konstruktora? Od docs:

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

Więc faktycznie, nie trzeba w tym celu wewnątrz pętli. (Z drugiej strony, nie powinno to zaszkodzić.) Ale która implementacja jest dla ciebie przy użyciu CurlAsyncHTTPClient lub SimpleAsyncHTTPClient?

Jeśli jest SimpleAsyncHTTPClient, zdawać sobie sprawę z tego komentarza w kodzie:

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

Można spróbować przełączania CurlAsyncHTTPClient. Lub postępuj zgodnie z sugestią Nikolay Fominyh pod numerem i śledź wywołania funkcji __callback().