12

Tornado が一種のワーカーのパススルーとして使用されるセットアップがあります。リクエストは Tornado によって受信され、Tornado はこのリクエストを N 個のワーカーに送信し、結果を集計してクライアントに送り返します。なんらかの理由でタイムアウトが発生した場合を除いて、これは正常に機能します — その後、メモリ リークが発生します。

この疑似コードに似たセットアップがあります:

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

私は何を間違っていますか?メモリリークはどこから来るのですか?

4

2 に答える 2

5

問題の原因はわかりません。gcで対処できるはずですが、試すことができることが2つあります。

responses最初の方法は、いくつかの参照を単純化することです( RequestHandlerが完了したときにまだ参照があるように見えます)。

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

それが機能しない場合は、いつでも手動でガベージコレクションを呼び出すことができます。

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

    def _finish_req(self):
        ....

    def on_connection_close(self):
        gc.collect()
于 2012-10-05T07:39:36.993 に答える
1

コードはよさそうだ。リークはおそらくトルネードの内部にあります。

私はこの線につまずいただけです:

async_client = tornado.httpclient.AsyncHTTPClient()

このコンストラクターのインスタンス化の魔法を知っていますか?ドキュメントから:

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

したがって、実際には、ループ内でこれを行う必要はありません。(一方、害はありません。)しかし、CurlAsyncHTTPClientまたはSimpleAsyncHTTPClientのどちらの実装を使用していますか?

SimpleAsyncHTTPClientの場合は、コード内の次のコメントに注意してください。

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

CurlAsyncHTTPClientへの切り替えを試すことができます。または、Nikolay Fominyhの提案に従い、__ callback()への呼び出しをトレースします。

于 2012-10-09T07:33:48.840 に答える