2016-11-15 12 views
3

私はサーバーを構築しようとしていました。通常のサーバーのようにクライアントからの接続を受け入れるだけでなく、私のサーバーは他のサーバーをクライアントとしても接続します。Twisted:connectProtocolを使用してエンドポイントにメモリリークを引き起こしますか?

私は以下のようなプロトコルとエンドポイントを設定しました:

p = FooProtocol() 
client = TCP4ClientEndpoint(reactor, '127.0.0.1' , 8080) # without ClientFactory 

その後、コールreactor.run()後、サーバが新しいソケット接続を受け入れる/リッスンします。新しいソケット接続が(connectionMadeで)行われた場合、サーバは以下の擬似コードのような役割を果たしている、connectProtocol(client, p)を呼び出します:クライアントへの接続など

while server accept new socket: 
    connectProtocol(client, p) 
    # client.client.connect(foo_client_factory) --> connecting in this way won't 
    #             cause memory leak 

、メモリが徐々に消費されて作られています(明示的gc doesnのを呼び出します仕事はしません)。

Twistedを間違った方法で使用していますか?

----- UPDATE -----

私のテストprograme:サーバーが接続するクライアントを待ちます。クライアントからの接続が確立されると、サーバーは、ここで他のサーバへの接続50

を作成するコードされています

#! /usr/bin/env python 

import sys 
import gc 

from twisted.internet import protocol, reactor, defer, endpoints 
from twisted.internet.endpoints import TCP4ClientEndpoint, connectProtocol 

class MyClientProtocol(protocol.Protocol): 
    def connectionMade(self): 
     self.transport.loseConnection() 

class MyClientFactory(protocol.ClientFactory): 
    def buildProtocol(self, addr): 
     p = MyClientProtocol() 
     return p 

class ServerFactory(protocol.Factory): 
    def buildProtocol(self, addr): 
     p = ServerProtocol() 
     return p 

client_factory = MyClientFactory() # global 
client_endpoint = TCP4ClientEndpoint(reactor, '127.0.0.1' , 8080) # global 

times = 0 

class ServerProtocol(protocol.Protocol): 
    def connectionMade(self): 
     global client_factory 
     global client_endpoint 
     global times 

     for i in range(50): 
      # 1) 
      p = MyClientProtocol() 
      connectProtocol(client_endpoint, p) # cause memleak 

      # 2) 
      #client_endpoint.connect(client_factory) # no memleak 

     times += 1 
     if times % 10 == 9: 
      print 'gc' 
      gc.collect() # doesn't work 

     self.transport.loseConnection() 

if __name__ == '__main__': 
    server_factory = ServerFactory() 
    serverEndpoint = endpoints.serverFromString(reactor, "tcp:8888") 
    serverEndpoint.listen(server_factory) 
    reactor.run() 
+1

これはTwistedのバグかもしれないようですが、あなたはここに十分なコードをつけて伝えていません。あなたはプログラム全体を添付できますか? – Glyph

+0

返信ありがとうございます!私のテストコードが更新されました。 –

+0

実際にここに漏れがあるようです。実際、connectProtocolベースの例では少し高速ですが、*両方の例でリークします。これは間違いなくTwistedのバグです。調査する必要があります。 – Glyph

答えて

4

このプログラムは、任意のツイストログの初期化を行いません。これは、それが実行全体のための "ログ初心者"で実行されることを意味します。ログ初心者は、すべてのログイベントをLimitedHistoryLogObserverに記録します(設定可能な最大値まで)。

ログ初心者が古いものを捨て始まり、その後2 ** 16(_DEFAULT_BUFFER_MAXIMUM)のイベントを保持し、おそらくプログラム決しては別のオブザーバを構成した場合、すべての利用可能なメモリを消費しないようにします。

Twistedソースをハックして_DEFAULT_BUFFER_MAXIMUMを小さな値(例:10)に設定すると、プログラムはもはや「リーク」しなくなります。もちろん、それは実際にはオブジェクトのリークであり、メモリリークではなく、2×16の制限によって制限されています。

しかし、connectProtocolは、呼び出されるたびに新しいファクトリを作成します。新しいファクトリが作成されるたびに、メッセージが記録されます。アプリケーションコードは、各ログメッセージに対して新しいLoggerを生成します。 ロギングコードは、新しいメッセージをLoggerに入れ、ログメッセージをに送ります。これは、これらのログメッセージを保存するためのメモリコストがかなり目立つことを意味します(テキストの短い塊やいくつかの単純なオブジェクトを含むdictを漏らすことに比べて)。

私はTwistedのコードが意図したとおりに動作していると言いたいと思いますが、おそらく誰かがその動作の結果を完全には考えなかったでしょう。

もちろん、独自のログオブザーバを設定すると、「ログ初心者」が画像から取り出され、問題はありません。すべての深刻なプログラムがかなり早くログを記録し、この問題を回避できると期待するのは合理的です。しかし、多くの短いスロー・アウェイやサンプルプログラムでは、ロギングを初期化せず、代わりに印刷に依存しているため、この動作の対象になります。

この問題は、#8164で報告し、4acde626 17は、この動作を持っていないので、ねじれ修正されました。

関連する問題