PHP SoapClient 中打开文件过多错误

最近在处理一个项目时,我遇到了一个我以前从未见过的 PHP 错误。在soap服务调用API的过程中,连接会失败,程序会出现致命错误并停止。

我有适当的保护机制来捕获这种连接错误,但是当程序试图抛出我为指示连接失败而设置的异常时,会导致致命错误。

这是错误消息(删除了一些细节)。这是一个 Drupal 站点,但该细节与问题无关。

PHP message: PHP Warning:  require(/docroot/modules/custom/api_integration/src/Exception/UnableToConnectException.php): failed to open stream: Too many open files in /vendor/symfony/class-loader/ApcClassLoader.php on line 112
PHP message: PHP Fatal error:  require(): Failed opening required '/docroot/modules/custom/api_integration/src/Exception/UnableToConnectException.php' (include_path='') in /vendor/symfony/class-loader/ApcClassLoader.php on line 112

这告诉我的是该程序无法连接到soap服务,但它无法打开异常来处理这个问题,因为“打开的文件太多”。

经过一番挖掘,我发现有人有类似的问题,但没有与 PHP SoapClient 对象特别相关的问题。事实证明,这个打开文件限制是在 PHP 内部控制的,但它是底层操作系统限制的一部分。事实上,我在几个不同的 PHP 版本上尝试了这个过程并得到了相同的结果。

这个限制(在 unix 系统上)可以通过使用带有-n标志的命令ulimit查看打开的文件描述符的最大数量来查看。在我当前的系统上,这表明限制为 256 个文件。

$ ulimit -n
256

为了人为地达到这个限制,我创建了一个小脚本,它会一遍又一遍地打开同一个文件,但永远不会关闭连接。

<?php
 
$handles = [];
 
for ($i = 1; $i <= 254; $i++) {
  $handles[] = fopen('/dev/null', 'w');
}

运行此脚本会产生以下错误,脚本尝试打开文件的最后一次会导致此警告。请注意,我将循环的限制设置为 254。这是因为打开的文件数包括 PHP 进程和运行脚本的文件。

PHP Warning:  fopen(/dev/null): failed to open stream: Too many open files intest.phpon line 6

回顾我正在运行的soap 进程,我可以看到soap 客户端正在连接到soap 服务,并且在连接一定数量后,它根本无法再连接并导致错误。关于包含异常文件的错误是掩盖问题的上游红鲱鱼。

经过几个小时的浏览支持论坛讨论如何更改操作系统设置(我不太乐意做的事情),我找到了解决方案。事实证明,PHP SoapClient 将连接到soap 服务并保持连接处于活动状态。不幸的是,下一个连接不使用当前活动的连接,而是在下一个soap请求发出时创建另一个连接。在大型操作期间,此限制会很快达到并导致错误。

为了防止soap 保持连接处于活动状态,您需要在创建SoapClient 对象时传入keep_alive选项。

$options['keep_alive'] = FALSE;
$soapClient = new \SoapClient($wsdl, $options);

这使我能够从几百个连接增加到几千个,并让肥皂过程成功完成。这也意味着我已经在代码中解决了这个问题,而不是修补操作系统来解决它。

老实说,我讨厌使用肥皂。