首页
Preview

Laravel:修复测试中的内存泄漏问题

如果你正在创建 Laravel 应用程序并试图测试它是否按预期工作,你就知道当测试失败时会有多么破坏性。

测试运行时出现错误是正常的,但在某些情况下,测试框架不会抛出错误,相反,它会带着一条血腥的消息自杀。

那个“内存耗尽”实际上就是经典的 内存泄漏,是某些东西在测试用例之间持续存在和存留的副产品。当你创建带有许多测试用例的应用程序时,你可能会遇到这个问题,因为不仅 Laravel 会保留一些数据,你的测试类也会保留一些数据。

有许多方法可以确保你的测试结果像进来时一样干净,这肯定会帮助你修复随机出现的内存泄漏。在本文中,我将分享一些可能适用于你的解决方案,以及对我最终奏效的解决方案(奇怪但有效)。

手动查找老旧的错误

信不信由你,常见的内存泄漏之一就是静态属性。在经典的请求中,PHP 执行并销毁自身,因此这些属性不会造成问题,因为每次请求都会被抛弃。

当你重用 PHP 进程来执行每个测试用例时,静态数据将保留并不断增长,这就成为了 内存泄漏。这对于 Laravel Octane 或类似的东西也是正确的。

检查应用程序是否是罪魁祸首而不是 用户空间代码无需工具 的方法是创建一个循环,该循环多次创建、启动应用程序并清除它。

如果这会触发内存耗尽,那么你就知道某个服务提供者或服务实例没有被正确清除。

在测试循环之前,进入你的项目,在 config/app.php 中的服务提供者部分开始删除它们,直到找到哪个服务正在快速增长。对于外部包的情况,你将不得不检查 bootstrap/cache/packages.php 文件,而不是逐个删除它们。

在 Windows 下,这是检查内存泄漏的唯一方法。如果你在 Linux 或 MacOS 上,则有一些工具可以帮助你找出正在使用内存的内容以及它的位置。如果它发生在特定的测试中,则 Roave's No-Leaks 可以帮助找到它。

整理你的房间

简而言之,Test 类的 析构函数 从不被调用,因为在 Test 类完成后,某个引用仍然存在(可能在 PHPUnit 内部),即使 Test 类完成后,引用也不会被删除。实际上,引用是在没有更多测试要运行时被删除的,也就是 PHPUnit 退出时。

如果对象仍然存在引用,那么在 Test 类中定义的所有属性都将保留,即使只有一个布尔值。

每次 Test Case 完成时,Laravel 都会对应用程序进行 清理运行,包括在删除应用程序实例之前刷新每个服务,但你必须将自己的属性设置为 null(或取消设置)。

幸运的是,你可以在 setUp() 方法中使用 beforeApplicationDestroyed() 和回调来删除类中的那些挂留属性,例如一个巨大的数组或具有大量关系的模型。

使用吸尘器

如果你真的很懒,或者你定义了很多属性,而你无法在任何地方跟踪它们,你可能会想使用基于 StackOverflow 中找到的代码。

这个代码基本上收集每个不是内部的属性,这些属性可能被你遗留下来,然后将它们取消设置。大多数情况下都不会创建任何问题。你可以在主 TestCasetearDown() 方法中调用它。

将容器推向无尽的深渊

有时候 PHP 可能会保留 Laravel 应用程序实例在内存中,因为是的。这很罕见,但有些人说有时会发生这种情况。

一种强制删除应用程序实例的方法是使用容器静态帮助程序,在应用程序被清除并死亡后使用它。

上面的代码基本上将共享容器实例(一个单例)设置为 null,当没有任何容器引用时,任何持有的引用都应该死亡,直到下一个 Test Case 运行。同样,这是罕见的,但确实存在。

把垃圾扔出去

你不应该需要调用 PHP 的垃圾回收器,但你可以使用 gc_collect_cycles() 调用它。这个函数调用垃圾回收器,它立即释放任何指向 null 或未设置的变量的内存。

这可能是立即删除未使用内存的好方法,例如在测试期间闲置的变量,下一个测试再次创建它时,你被迫使用该变量。

通常,PHP的垃圾回收器在何时运行方面非常智能,但你可以加快它的速度,并且有时它可以解决你的任何泄漏问题。

拥有许多具有此类内存泄漏的测试用例会使整个测试随着进度变慢。幸运的是,Laravel与Paratest兼容。换句话说,每个测试类将为每个PHPUnit进程生成,这些进程将并行执行。

这不会修复你的内存泄漏。由于PHP进程在Test Class完成后会死亡,因此泄漏不会像在数百个Test Cases中那样变得更大。同样,这不是万能药,而是解决测试范围之外的问题的一种补丁,你可以稍后解决。

如果你无法找到内存泄漏,但仍然迫切需要在单个进程中进行测试,请允许我向你介绍_提高内存限制_。

如果你对忽略互联网上的任何建议感到毫不在意,只需使用-d参数直接增加PHP的内存限制。是的,空间不足并非打字错误,你可以将任何选项名称粘贴到d中。

PS> php -dmemory_limit=1024M ./vendor/phpunit/phpunit/phpunit

**为什么这很糟糕?因为你没有解决问题的根源。**测试套件越大,PHPUnit和Laravel消耗的内存越多,只有当测试运行结束时,它才会报告消耗了多少内存,你只能_猜测_是否足够。

OPcache来拯救!?

这很奇怪,但我还是找到了。让我们从OPcache如何描述自己开始:

OPcache通过在共享内存中存储预编译的脚本字节码来提高PHP性能,从而消除了PHP在每个请求上加载和解析脚本的需要。

换句话说,一旦读取和解析脚本,就会将其存储在内存中。每次PHP 8.0.12需要该脚本时,它都会从内存中读取它,而不是一遍又一遍地询问文件系统。本质上,就像所需文件的数组一样。

我发现PHP通过不断读取使用require执行的无害脚本而泄漏了_一些_内存。因此,理论上,如果这是罪魁祸首,OPcache将停止内存泄漏。再次说明,这很奇怪,但无论我做了什么,所有的线索都指向在Autoloader Class Map之外连续加载脚本并将它们堆叠在无人能找到的地方。

我通过php.ini在CLI上启用了OPcache,然后再次运行了所有测试。

太神奇了。不仅执行了1,000个测试,而且相对较快。我在PHPUnit上直接运行了另一个测试,并报告了40MB的内存占用,与痛苦结束时报告的1GB相比非常低。

请注意,如果你依赖artisan test,则应在php.ini上启用OPcache,而不是仅传递-dopcache.enable_cli=1。这是因为Laravel创建了一个新的PHP进程来处理测试,并将其挂钩以显示你看到的漂亮测试信息。

我将强制OPcache不验证时间戳或缓存文件的校验和的档位提高到11,以使测试更快,并在并行模式下运行它。花了大约20秒。不再有内存泄漏,问题一直都是PHP。

由于我对向PHP报告问题的事情不熟悉,如果有人在Windows上遇到相同的问题,我会留下这个问题。也许稍后PHP的人会修复它。

译自:https://darkghosthunter.medium.com/laravel-fixing-memory-leaks-on-tests-4fe87b87f0ad

版权声明:本文内容由TeHub注册用户自发贡献,版权归原作者所有,TeHub社区不拥有其著作权,亦不承担相应法律责任。 如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

点赞(0)
收藏(0)
anko
宽以待人处事,严于律己修身。

评论(0)

添加评论