openssl_pkey_get_private 内存泄漏
发表于|更新于|码不能停
|总字数:451|阅读时长:1分钟|浏览量:
从前,有一块使用 swoole_process 做多线程处理的程序,这个程序有一个奇怪的问题:每运行一个星期左右都会因为内存爆表而停掉。
因为爆表的时间关系,开发的小伙伴们都认为是这段使用 swoole_process 的程序有问题,几经更新改版,代码已经趋于完美,平稳的运行了几天后,小伙伴们终于都松了一口气,终于解决了这个问题。
然而,伴随着百年难遇的血月,这块程序还是在一个意想不到的时间又爆了一次,小伙伴们终于意识到,问题的根本并不在这里,想要找出并改掉这个问题,是何等艰难!但是他们知道,真相只有一个!
又经过几天的排查,凶手终于浮出水面!
呐,就是这孙子:openssl_pkey_get_private
抽出的测试代码:
1 | while(true){ |
结果:
审判结果:该阉的就赶紧阉了吧
行刑后长相:
1 | $key = file_get_contents('mock_A.pem'); |
前线专家点评:纯属冤案,哪儿特么这么容易泄漏,都是因为自己写的不规范!
注:生成公私钥是需要消耗内存的,每次脚本执行结束后会释放掉,但是常驻内存的脚本没有释放的机会,所以每次循环调用都会造成内存增加,持续增加到一定量后系统也就崩了。
所以解决办法是在循环开始之前就把密钥生成好。
文章作者: m-finder
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 M-finder!
相关推荐

2025-01-22
mac m1 安装 swoole 报错问题记录
今天切本地环境的时候发现 swoole 还没装,装的时候又报了错:pcre2.h file not found,鉴于我这个记性越来越差,还是写条笔记记录一下解决办法。 brew install pcre2,如果本机已经安装,就直接进行第二步 ln -s /opt/homebrew/include/pcre2.h /opt/homebrew/Cellar/php@8.1/8.1.31/include/php/ext/pcre/pcre2.h,正常安装 pcre2 之后,需要软连接对应目录,注意这里要把目录切换成你实际的目录 pecl install swoole,再次运行安装 php -m | grep swoole,运行验证

2024-09-19
Mac 用 Brew 安装旧版本 PHP
最近又用到 php7.4,奈何本地早就已经升到了 8,想搞回来,发现一个简单好用爽歪歪的三方库,需要的可以试一下,5.6 - 8.4 都有。[ homebrew-php ] 添加地址库1brew tap shivammathur/php 安装指定版本1brew install shivammathur/php/php@7.4 切换版本12345brew services stop php@7.4brew unlink php@xxbrew link php@7.4brew services start php@7.4 搞定收工。

2024-02-28
Laravel Octane 和 Swoole 协程的使用分析
之前在工作中使用 Laravel Octane 的 concurrently 处理并发时,发现在队列和定时任务中不会触发并发效果。经过分析,作了如下猜测:队列定时任务都属于一个独立的进程,与 Octane 服务无关,而 Octane conturrently 恰恰需要在 Octane 环境下才能运行。 后来通过代码进行环境检测和查看 php 的进程,证明猜想成立。 1234info('check env', [ 'served by octane' => isset($_SERVER['LARAVEL_OCTANE']) && ((int)$_SERVER['LARAVEL_OCTANE'] === 1), 'on swoole server' => (extension_loaded('swoole') || extension_loaded('openswoole')) && app()->bound(Server::class)]); 为了能够在任意代码中实现并发,我们研究参考了 Hyperf 框架关于协程的代码,然后抽取了如下两个类: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071<?phpnamespace App\Services;use App\Exceptions\ParallelExecutionException;use Laravel\Octane\Facades\Octane;use Throwable;use Swoole\Coroutine as Co;class Parallel{ protected array $callbacks = []; protected array $results = []; /** * @var Throwable[] */ protected array $throwables = []; public function add(callable $callable, $key = null): void { if (is_null($key)) { $this->callbacks[] = $callable; } else { $this->callbacks[$key] = $callable; } } public function wait(bool $throw = true): array { if (isset($_SERVER['LARAVEL_OCTANE']) && ((int)$_SERVER['LARAVEL_OCTANE'] === 1)) { return Octane::concurrently($this->callbacks, 300000); } app('log')->useLoggingLoopDetection(false); Co\run(function () { foreach ($this->callbacks as $key => $callback) { Co::create(function () use ($callback, $key) { try { $this->results[$key] = $callback(); } catch (Throwable $throwable) { $this->throwables[$key] = $throwable; unset($this->results[$key]); } }); } }); if ($throw && ($throwableCount = count($this->throwables)) > 0) { $message = 'Detecting ' . $throwableCount . ' throwable occurred during parallel execution:' . PHP_EOL . $this->formatThrowAbles($this->throwables); $executionException = new ParallelExecutionException($message); $executionException->setResults($this->results); $executionException->setThrowAbles($this->throwables); unset($this->results, $this->throwables); throw $executionException; } app('log')->useLoggingLoopDetection(true); return $this->results; } private function formatThrowAbles(array $throwables): string { $output = ''; foreach ($throwables as $key => $value) { $output .= sprintf('(%s) %s: %s' . PHP_EOL . '%s' . PHP_EOL, $key, get_class($value), $value->getMessage(), $value->getTraceAsString()); } return $output; }} 1234567891011121314151617181920212223242526272829303132<?phpnamespace App\Exceptions;use RuntimeException;class ParallelExecutionException extends RuntimeException{ protected array $results = []; protected array $throwables = []; public function getResults(): array { return $this->results; } public function setResults(array $results): void { $this->results = $results; } public function getThrowAbles(): array { return $this->throwables; } public function setThrowAbles(array $throwables): array { return $this->throwables = $throwables; }} 其中,第一个类的作用是检测系统是否运行在 Octane 环境下,是则调用Octane concurrently,否则就执行 Swoole 协程代码,使用起来也比较简单: 1234567$parallel = new Parallel();$parallel->add(fn() => $this->analysisStructure(), 'structure');$parallel->add(fn() => $this->analysisHabit(), 'habit');[ 'structure' => $structure, 'habit' => $habit,] = $parallel->wait(); 之所以没有完全使用 Swoole 协程,是因为相比之下,Octane 代码更加优雅,我们在期待着某天更新后,Octane concurrently 也能直接在队列中运行使用。 第二个类的作用比较简单,就是对协程中异常的一个定义。 另外在分析过程中,我们也发现了一个比较有意思的事情: 如图所示,当我在路由中运行检测代码时,Octane 和 Swoole server 都为 true;在控制器中运行检测代码时,又只有 Octane 为true;为什么会有这样的区分?我个人猜测是 Octane 在将框架代码读进内存时,特意跳过了控制器中的代码,以避免数据更新不及时等问题。 有知道具体原因的小伙伴,欢迎留言探讨。

2019-04-11
laravel 测试模块学习
学习下 larave 内置的测试模块。 当你想把一些东西写到 print 语句或者调试表达式中时,别这么做,将其写成一个测试来代替。 –Martin Fowler 最开始看到的关于 laravel 测试的信息是借助模型工厂来生成测试数据,今天查完资料发现,这只是测试中的一小部分。 laravel 内置了 PHPUnit 来做测试,并且已经做好了配置文件,还提供了一些便利的辅助函数,可以更直观的测试程序。 在 laravel 的项目中,包含一个 tests 目录,这个目录又有两个子目录:Feature 和 Unit 分别用来做功能测试和单元测试。 功能测试用于测试较大区块的代码,包括若干组件之间的交互,甚至一个完整的 HTTP 请求。 单元测试用于小的 、相互隔离的代码。 配置可以使用默认的配置,也可以创建一个 .env.testing 文件,在运行测试或执行带有 --env=testing 开关的 Artisan 命令时覆盖 .env 文件中的环境变量。 创建 & 运行测试运行 artisan 生成测试用例: 12345// 在 Feature 目录下创建测试类php artisan make:test UserTest// 在 Unit 目录下创建测试类php artisan make:test UserTest --unit 先生成一个单元测试,然后改造一下: 1234$user = DB::table('users')->where('id',1)->first();$name = $user->name;$username = ucfirst($user->name);$this->assertEquals($username, $name); 把用户名首s字母大写,然后判断和原用户名是否相等。 运行结果如下: HTTP测试新建一个路由和方法: 1234567Route::get('/user-info/{id?}', 'HomeController@userInfo');public function userInfo(){ $id = request('id'); $user = User::where('id', $id)->first(); return $user;} 新建测试12345678910111213141516php artisan make:test HttpStatusTest$user = DB::table('users')->where('id',1)->first();$username = ucfirst($user->name);$response = $this->get('/user-info/1');$response->assertStatus(200)->assertJson([ 'id' => 1, 'name'=> 'wu', 'email'=> 'yf-wu@qq.com', 'email_verified_at'=>null, 'created_at'=> '2019-04-09 07:36:52', 'updated_at'=> '2019-04-09 07:36:52']); 运行测试发现接口需要登录,所以会被拦截: 所以需要先模拟用户登录,可以使用:Auth::loginUsingId(1); 使 id 为 1 的用户强制登录。 再次运行,结果 ok。 数据库测试数据库测试功能点更多,可以验证表中是否存在某条数据,也可以用来生成测试数据等。 123$this->assertDatabaseHas('users', [ 'email' => 'sally@example.com']); 也可以使用 assertDatabaseMissing 帮助程序断言数据库中不存在数据。 生成模型工厂运行命令生成模型工厂: 1php artisan make:factory PostFactory 在项目的 database\factories 目录中,已经预先生成了一个 UserFactory: 123456789$factory->define(User::class, function (Faker $faker) { return [ 'name' => $faker->name, 'email' => $faker->unique()->safeEmail, 'email_verified_at' => now(), 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 'remember_token' => Str::random(10), ];}); 改造之前的 TestUser 来测试一下: 123456public function testExample() { $user = factory(User::class)->create(['email' => 'm@m-finder.com']); $this->assertDatabaseHas('users', [ 'email' => 'm@m-finder.com' ]);} 状态ok。 更多操作还是要参考: [ PHPUnit ]

2018-06-19
Laravel使用chunk自更新有误的原因
Laravel 的 chunk 在查询大量数据时非常好用,但是在用某个字段做为检索条件,并且在程序内部有更新这个字段的时候,就会有一部分数据丢失。 原因是这样的,比如有7条数据,每次取2条,那么这7条数据就会被分为2,2,2,1这样几块,第一次调用时,取得第一页,也就是第一个2的数据块,更新完后再取数据,原来的第二页就变成了第一页,但是这时候取的还是第二页,所以这时候取的是第三个数据块,到了第三次调用的时候,原来的第二页变成了第一页,原来的第四页变成了第二页,第三页就变成了空的,所以第三页就什么都取不到了。 解决方法:可以使用 laravel 的游标 cursor()

2018-03-11
CentOS 7.2 64位安装LNMP php7+Mysql 5.7搭建教程
前前后后搭建过无数次环境,大大小小的坑也差不多都踩了,今天趁着腾讯搞活动,120块买了台16个月的服务器,想着晚上把环境搭建下吧,结果把之前的文档掏出来发现是没更新的,最新版在公司…… 算了,老子再搞一遍,放在网上随便看。 更新组件123456789yum -y updateyum -y groupinstall 'Development Tools'yum install gcc-c++ patch readline readline-devel zlib zlib-devel bzip2 autoconf automake libtool bison iconv-devel libyaml-devel libffi-devel openssl-devel make yum -y install readline readline-devel ncurses-devel gdbm-devel glibc-devel tcl-devel openssl-devel curl-devel expat-devel db4-devel byacc sqlite-devel libyaml libyaml-devel libffi libffi-devel libxml2 libxml2-devel libxslt libxslt-devel libicu libicu-devel system-config-firewall-tui sudo wget crontabs logwatch logrotate perl-Time-HiRes git cmake libcom_err-devel.i686 libcom_err-devel.x86_64yum install libxml2 libxml2-devel openssl openssl-devel bzip2 bzip2-devel libcurl libcurl-devel libjpeg libjpeg-devel libpng libpng-devel freetype freetype-devel gmp gmp-devel libmcrypt libmcrypt-devel readline readline-devel libxslt libxslt-devel libicu-devel openldap-devel 安装Pcre: 123456wget https://excellmedia.dl.sourceforge.net/project/pcre/pcre2/10.31/pcre2-10.31.tar.gztar -xf pcre2-10.31cd pcre2-10.31./configure make make install 安装Libmcrypt: 123456wget https://nchc.dl.sourceforge.net/project/mcrypt/Libmcrypt/2.5.8/libmcrypt-2.5.8.tar.gztar -xf libmcrypt-2.5.8.tar.gzcd libmcrypt-2.5.8./configuremake make install 安装php嗯……最新版7.2开怼 123wget http://cn2.php.net/get/php-7.2.3.tar.gz/from/this/mirrortar -xf mirrorcd php-7.2.3 该编译了,好长… 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566./configure \--prefix=/usr/local/php \--with-config-file-path=/usr/local/php/etc \--enable-fpm \--with-fpm-user=nginx \--with-fpm-group=nginx \--enable-inline-optimization \--disable-debug \--disable-rpath \--enable-shared \--enable-soap \--with-libxml-dir \--with-xmlrpc \--with-openssl \--with-mcrypt \--with-mhash \--with-pcre-regex \--with-sqlite3 \--with-zlib \--enable-bcmath \--with-iconv \--with-bz2 \--enable-calendar \--with-curl \--with-cdb \--enable-dom \--enable-exif \--enable-fileinfo \--enable-filter \--with-pcre-dir \--enable-ftp \--with-gd \--with-openssl-dir \--with-jpeg-dir \--with-png-dir \--with-zlib-dir \--with-freetype-dir \--enable-gd-native-ttf \--enable-gd-jis-conv \--with-gettext \--with-gmp \--with-mhash \--enable-json \--enable-mbstring \--enable-mbregex \--enable-mbregex-backtrack \--with-libmbfl \--with-onig \--enable-pdo \--with-mysqli=mysqlnd \--with-pdo-mysql=mysqlnd \--with-zlib-dir \--with-pdo-sqlite \--with-readline \--enable-session \--enable-shmop \--enable-simplexml \--enable-sockets \--enable-sysvmsg \--enable-sysvsem \--enable-sysvshm \--enable-wddx \--with-libxml-dir \--with-xsl \--enable-zip \--enable-mysqlnd-compression-support \ 结束后make,make install,时间较长,耐心等待。 好了以后,复制一份ini文件备份,然后把php-fpm添加成service 123456cp php.ini-development /usr/local/php/etc/php.inicp sapi/fpm/init.d.php-fpm /etc/init.d/php-fpmcp /usr/local/php/etc/php-fpm.d/www.conf.default www.confcp /usr/local/php/etc/www.conf.default www.confchmod +x /etc/init.d/php-fpmservice php-fpm start 添加php进环境变量: 123vim /etc/profile PATH=$PATH:/usr/local/php/bin export PATH 使修改生效: 1source /etc/profile 查看路径和php版本: 12echo $PATHphp -v 安装nginx安装nginx yum源 1yum localinstall http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm 然后安装nginx: 1yum install nginx 然后,启动下试试? 1service nginx start 访问http://你的ip/ 如果成功安装会出来nginx默认的欢迎界面 没成功就检查下端口是否可以访问 ,虚拟机可以直接干掉防火墙 安装mysql下载文件: 1wget http://dev.mysql.com/get/mysql57-community-release-el7-8.noarch.rpm 开始安装: 12rpm -ivh mysql57-community-release-el7-8.noarch.rpmyum install mysql-community-server 这个比较快,装好开始运行: 1systemctl start mysqld 加入开机自启: 1systemctl enable mysqld 查看默认密码: 12grep 'temporary password' /var/log/mysqld.log 登陆修改密码: 1234567891011121314151617181920212223242526272829303132[root@VM_34_176_centos ~]# mysql -u root -pEnter password: Welcome to the MySQL monitor. Commands end with ; or \g.Your MySQL connection id is 2Server version: 5.7.21Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.Oracle is a registered trademark of Oracle Corporation and/or itsaffiliates. Other names may be trademarks of their respectiveowners.Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.mysql> SET PASSWORD FOR 'root'@'localhost' = PASSWORD('Xx..XXXX');#密码不能太简单Query OK, 0 rows affected, 1 warning (0.00 sec)mysql> show databases;+--------------------+| Database |+--------------------+| information_schema || mysql || performance_schema || sys |+--------------------+4 rows in set (0.00 sec)mysql> exit;Bye 安装swoole12345pecl install swoole#添加到php.inicd /etcvi php.iniextension=swoole.so 注:安装好PHP后复制ini文件是因为编译时指定了 1--with-config-file-path=/usr/local/php/etc \ 如果不复制的话也一样能够运行php,但是就无法装扩展了,踩坑千百遍终有一疏,装完swoole发现无法加载,最后查到时这里的问题。 打完收工,一个不小心又搞到凌晨一点,感觉头上凉凉哒~