最近我开始在 Django (3.1) 测试中遇到一些问题,我终于找到了某种内存泄漏。我通常运行我的套件(目前大约有 4000 个测试),--parallel=4
这会产生大约 3GB 的高内存水印(从 500MB 左右开始)。不过,出于审计目的,我偶尔会运行它--parallel=1
——当我这样做时,内存使用量不断增加,最终超过了 VM 分配的 6GB。
我花了一些时间查看数据,很明显罪魁祸首是,不知何故,Webtest - 更具体地说,它的response.html
and response.forms
:在测试用例期间的每个调用可能会分配几个 MB(通常是两个或三个),这些没有得到在测试方法结束时发布,更重要的是,甚至在TestCase
.
我已经尝试了所有我能想到的东西——gc.collect()
向gc.DEBUG_LEAK
我展示了很多可收藏的物品,但它根本没有释放任何记忆;使用delattr()
各种TestCase
和TestResponse
属性等导致根本没有变化等。
我真的是束手无策,因此任何解决此问题的指针(除了编辑使用 WebTest 响应的数千个左右的测试,这实际上是不可行的)将不胜感激。
(请注意,我也尝试使用guppy
and tracemalloc
,memory_profiler
但都没有给我任何可操作的信息。)
更新
我发现我们的其中一个 EC2 测试实例不受该问题的影响,因此我花了一些时间试图解决这个问题。最初,我试图找到“合理”的潜在原因——例如,缓存模板加载器,它在我的本地 VM 上启用并在 EC2 实例上禁用——但没有成功。然后我全力以赴:我复制了 EC2 virtualenv(使用pip freeze
)和设置(复制 dotenv),并检查了测试在 EC2 上正常运行的相同提交。
瞧!内存泄漏仍然存在!
现在,我正式放弃并将--parallel=2
用于未来的测试,直到一些绝对的大师可以指出我正确的方向。
第二次更新
现在即使使用--parallel=2
. 我想这在某种程度上更好,因为它看起来越来越像是系统问题而不是应用程序问题。没有解决它,但至少我知道这不是我的错。
第三次更新
感谢 Tim Boddy 对这个问题的回复,我试图用chap
它来找出是什么让记忆增长。不幸的是,我无法正确“读取”结果,但看起来一些非 python 库实际上是导致问题的原因。所以,这就是我在运行我知道会导致泄漏的测试几分钟后分析核心的结果:
chap> summarize writable
49 ranges take 0x1e0aa000 bytes for use: unknown
1188 ranges take 0x12900000 bytes for use: python arena
1 ranges take 0x4d1c000 bytes for use: libc malloc main arena pages
7 ranges take 0x3021000 bytes for use: stack
139 ranges take 0x476000 bytes for use: used by module
1384 writable ranges use 0x38b5d000 (951,439,360) bytes.
chap> count used
3144197 allocations use 0x14191ac8 (337,189,576) bytes.
有趣的一点是,不泄漏的 EC2 实例显示的值与我从中得到的值几乎相同count used
——这表明那些“未知”范围是实际的猪。summarize used
(显示前几行)的输出也支持这一点:
Unrecognized allocations have 886033 instances taking 0x8b9ea38(146,401,848) bytes.
Unrecognized allocations of size 0x130 have 148679 instances taking 0x2b1ac50(45,198,416) bytes.
Unrecognized allocations of size 0x40 have 312166 instances taking 0x130d980(19,978,624) bytes.
Unrecognized allocations of size 0xb0 have 73886 instances taking 0xc66ca0(13,003,936) bytes.
Unrecognized allocations of size 0x8a8 have 3584 instances taking 0x793000(7,942,144) bytes.
Unrecognized allocations of size 0x30 have 149149 instances taking 0x6d3d70(7,159,152) bytes.
Unrecognized allocations of size 0x248 have 10137 instances taking 0x5a5508(5,920,008) bytes.
Unrecognized allocations of size 0x500018 have 1 instances taking 0x500018(5,242,904) bytes.
Unrecognized allocations of size 0x50 have 44213 instances taking 0x35f890(3,537,040) bytes.
Unrecognized allocations of size 0x458 have 2969 instances taking 0x326098(3,301,528) bytes.
Unrecognized allocations of size 0x205968 have 1 instances taking 0x205968(2,120,040) bytes.
这些单实例分配的大小与我在开始/停止测试时在测试运行程序中添加调用时看到的增量类型非常相似resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
——但它们不被识别为 Python 分配,因此我的感觉。