2016-11-15 10 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 Безразлично работа).

Я использую витой неверный путь?

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

Эта программа не выполняет никакой Twisted инициализации журнала. Это означает, что он запускается с «новичком журнала» для всего его запуска. Начальный журнал записывает все зарегистрированные события журнала в LimitedHistoryLogObserver (до настраиваемого максимума).

Начинающий журнал держит 2 ** 16 (_DEFAULT_BUFFER_MAXIMUM) события, а затем начинает выбрасывая старые, по-видимому, чтобы избежать потребить всю доступную память, если программа никогда настраивает другой наблюдатель.

Если вы взломали витой источник, установите _DEFAULT_BUFFER_MAXIMUM на меньшее значение - например, 10 - тогда программа больше не «течет». Конечно, это просто утечка объекта, а не утечка памяти, и она ограничена пределом 2 ** 16 Twisted.

Однако connectProtocol создает новую фабрику каждый раз, когда ее вызывают. Когда каждый новый завод создается, он регистрирует сообщение. И код приложения генерирует новый Logger для каждого сообщения журнала. И код регистрации ставит новый Loggerв сообщение журнала. Это означает, что стоимость хранения этих сообщений журнала довольно значительна (по сравнению с простое пропускание короткого блоба текста или даже dict с несколькими простыми объектами в нем).

Я бы сказал, что код в Twisted ведет себя так, как предполагалось ... но, возможно, кто-то не думал о последствиях этого поведения.

И, конечно, если вы сконфигурируете свой собственный наблюдатель журнала, тогда «изображение начинающего» вынимается из изображения, и проблем нет. Представляется разумным ожидать, что все серьезные программы позволят вести регистрацию довольно быстро и избежать этой проблемы. Тем не менее, много коротких отбрасывающих или примерных программ часто никогда не инициализируют протоколирование и не полагаются на печать вместо этого, что делает их подверженными этому поведению.

Примечание Эта проблема сообщалось в #8164 и фиксируется в 4acde626 так Витая 17 не будет иметь такое поведение.