2016-11-15 1 views
3

Ich habe versucht, einen Server zu bauen. Neben der Annahme von Verbindungen von Clients, wie es normale Server tun, verbindet mein Server auch andere Server als einen Client.Twisted: Verwenden von ConnectProtocol zum Verbinden von Endpunkt verursachen Speicherverlust?

Ich habe das Protokoll und Endpunkt wie im Folgenden beschrieben:

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

Dann, nach Anruf reactor.run(), wird der Server hören/neue Socket-Verbindungen akzeptieren. wenn neue Socket-Verbindungen (in connectionMade) vorgenommen werden, wird der Server connectProtocol(client, p) aufrufen, die unten wie die Pseudo-Code wirkt:

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

Da die Verbindungen zum Client vorgenommen werden, wird der Speicher nach und nach verbraucht (expliziten Aufruf gc doesn arbeite nicht).

Verwende ich Twisted falsch?

----- ----- UPDATE

Mein Test programe: Server wartet Kunden zu verbinden. Wenn die Verbindung vom Client hergestellt wird, Server 50 Verbindungen zu anderen Server

Hier ist der Code schaffen:

#! /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

Das klingt, als ob es ein Fehler in Twisted sein könnte, aber Sie haben nicht genug Code hier angeschlossen, um zu erzählen. Kannst du ein ganzes Programm anhängen? – Glyph

+0

Danke zu antworten! Eine Aktualisierung wurde mit meinem Testcode vorgenommen. –

+0

Hier scheint tatsächlich ein Leck zu sein. Tatsächlich bekomme ich ein Leck mit * beiden * Beispielen, obwohl es mit dem connectProtocol-basierten Beispiel ein wenig schneller ist. Dies ist definitiv ein Fehler in Twisted, und wir müssen nachforschen. – Glyph

Antwort

4

Dieses Programm nicht tut jede Twisted-Log-Initialisierung. Das heißt, es läuft mit dem "Log-Anfänger" für den gesamten Lauf. Der Protokollanfänger zeichnet alle Protokollereignisse auf, die er in einer LimitedHistoryLogObserver (bis zu einem konfigurierbaren Maximum) beobachtet.

Das Protokoll Anfänger hält 2 ** 16 (_DEFAULT_BUFFER_MAXIMUM) Ereignisse und dann beginnt alten Haufen zu werfen, vermutlich zu vermeiden, den gesamten verfügbaren Speicher raubend, wenn ein Programm nie einen anderen Beobachter konfiguriert.

Wenn Sie die Twisted-Quelle hacken, um _DEFAULT_BUFFER_MAXIMUM auf einen kleineren Wert - z. B. 10 - zu setzen, "leckt" das Programm nicht mehr. Natürlich ist es wirklich nur ein Objektleck und kein Speicherleck und es ist begrenzt durch die 2 ** 16 Grenze, die Twisted auferlegt.

Allerdings erstellt connectProtocol bei jedem Aufruf eine neue Fabrik. Wenn jede neue Factory erstellt wird, protokolliert sie eine Nachricht. Und der Anwendungscode generiert für jede Protokollnachricht einen neuen Logger. Und der Protokollierungscode setzt die neue Loggerin die Protokollnachricht. Das bedeutet, dass die Speicherkosten für das Protokollieren dieser Protokollmeldungen ziemlich deutlich sind (im Vergleich dazu, dass nur ein kurzer Textblock oder sogar ein Diktat mit ein paar einfachen Objekten ausgegeben wird).

Ich würde sagen, der Code in Twisted verhält sich genauso wie beabsichtigt ... aber vielleicht jemand dachte nicht durch die Folgen dieses Verhaltens abgeschlossen.

Und natürlich, wenn Sie Ihren eigenen Log-Beobachter konfigurieren, dann wird der "Log-Anfänger" aus dem Bild genommen und es gibt kein Problem. Es scheint vernünftig zu erwarten, dass alle seriösen Programme das Protokollieren sehr schnell ermöglichen und dieses Problem vermeiden. Viele kurze Wegwerf- oder Beispielprogramme initialisieren jedoch häufig die Protokollierung nicht und verlassen sich stattdessen auf den Druck, wodurch sie diesem Verhalten unterliegen.

Hinweis Dieses Problem in #8164 und fixiert in 4acde626 17 wird dieses Verhalten nicht so Verdreht berichtet wurde.

Verwandte Themen