【问题标题】:Twisted: Using connectProtocol to connect endpoint cause memory leak?Twisted:使用 connectProtocol 连接端点会导致内存泄漏?
【发布时间】:2017-02-03 03:58:33
【问题描述】:

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

我已将协议和端点设置如下:

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?

-----更新-----

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

【问题讨论】:

  • 这听起来可能是 Twisted 中的一个错误,但您没有在此处附加足够的代码来说明。你能附上整个程序吗?
  • 感谢回复!我的测试代码已更新。
  • 这里确实似乎存在泄漏。事实上,我得到了 both 示例的泄漏,尽管基于 connectProtocol 的示例更快一些。这绝对是 Twisted 中的一个错误,我们需要进行调查。

标签: python memory-leaks twisted


【解决方案1】:

这个程序不做任何 Twisted 日志初始化。这意味着它在整个运行过程中都与“日志初学者”一起运行。日志初学者将其观察到的所有日志事件记录在 LimitedHistoryLogObserver 中(最多可配置的最大值)。

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

如果您破解 Twisted 源以将 _DEFAULT_BUFFER_MAXIMUM 设置为较小的值 - 例如,10 - 那么程序不再“泄漏”。当然,这实际上只是对象泄漏而不是内存泄漏,它受到 Twisted 施加的 2 ** 16 限制。

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

我会说 Twisted 中的代码的行为与预期的一样......但也许有人没有考虑过这种行为的后果。

当然,如果您配置自己的日志观察器,那么“日志初学者”就被排除在外,没有问题。期望所有严肃的程序都会相当快地启用日志记录并避免这个问题似乎是合理的。但是,许多简短的一次性程序或示例程序通常不会初始化日志记录,而是依赖打印,从而使它们受到这种行为的影响。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-04-18
    • 1970-01-01
    • 2021-09-02
    • 2010-11-18
    • 1970-01-01
    • 1970-01-01
    • 2018-04-21
    相关资源
    最近更新 更多