扭曲:使用connectProtocol连接端点导致内存泄漏?

时间:2016-11-15 07:47:45

标签: python memory-leaks twisted

我正在尝试构建服务器。除了像普通服务器那样接受来自客户端的连接之外,我的服务器也将连接其他服务器作为客户端。

我已经设置了协议和端点,如下所示:

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无效)。

我是否以错误的方式使用Twisted?

- - - - - - - - UPDATE

我的测试程序:服务器等待客户端连接。当建立与客户端的连接时,服务器将创建与其他服务器的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 个答案:

答案 0 :(得分:4)

此程序不进行任何Twisted日志初始化。这意味着它与" log beginner"整个运行。日志初学者记录它在LimitedHistoryLogObserver中观察到的所有日志事件(最多可配置的最大值)。

日志初学者保留2 ** 16(_DEFAULT_BUFFER_MAXIMUM)个事件然后开始丢弃旧事件,大概是为了避免在程序从不配置另一个观察者时消耗所有可用内存。 / p>

如果您破解Twisted源以将_DEFAULT_BUFFER_MAXIMUM设置为较小的值(例如10),则程序不再“泄漏”#34;。当然,它实际上只是一个物体泄漏而不是内存泄漏,它受到2 ** 16限制扭曲强加的限制。

但是,connectProtocol每次调用时都会创建一个新工厂。创建每个新工厂时,它会记录一条消息。应用程序代码为每条日志消息生成一个新的Logger日志记录代码将新的Logger 放入日志消息中。这意味着保持这些日志消息的内存成本是非常显着的(与仅仅泄漏一小段文本甚至是带有一些简单对象的dict相比)。

我说Twisted中的代码表现得像预期的那样......但是也许有人没有想到完成该行为的后果。

当然,如果您配置自己的日志观察者,那么" log beginner"从图片中删除并没有问题。期望所有严肃的程序能够快速启用日志并避免此问题似乎是合理的。但是,许多短暂的丢失或示例程序通常不会初始化日志记录而是依赖于打印,这使得它们受到这种行为的影响。

注意此问题已在#8164中报告并已在4acde626中修复,因此Twisted 17将不会出现此问题。