PHPで2015年に報告されたバグを踏んでしまった話
結論。ちゃんとサポートされているPHPのバージョンを使いましょう。
最近作っているもの
最近、勉強を兼ねてPHPのtestcontainers実装を作ってます。
PHPにもtestcontainers-phpというものが存在しているのですが、サポートしているPHPのバージョンが8.1以降のみ。
PHPのサポート状況としては間違っていないのですが、こういうテストで利用するライブラリを本当に必要とするのはもっと古いバージョンなんだよなぁ。というので、shivammathur/php
の一番古いバージョンの5.6から最新の8まで動くものを作っています。
とりあえずMySQLを起動して疎通できるところまではできてる!
(まだちゃんと型とテスト作り込んでないので変な挙動するかも)
PDOConnectWaitStrategy
testcontainersにはWaitStrategyという素敵な概念があり、コンテナ起動から実際に利用するまで待つことができる機能があります。
だいたいはポートの疎通確認で十分なのですが、MySQLは経験上クライアントから接続に成功するまで待った方がテストが安定する印象です。
というのでせっかくPHP使ってるしPDOで接続待機できると良さそうなのでPDOConnectWaitStrategy
を実装しました。
while (1) {
if (time() - $now > $this->timeout) {
throw new WaitingTimeoutException($this->timeout);
}
try {
$pdo = new PDO($dsn->toString(), $this->username, $this->password, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_TIMEOUT => 1,
]);
$pdo->query('SELECT 1');
$pdo = null;
break;
} catch (PDOException $e) {
// Do nothing
}
usleep($this->retryInterval);
}
とりあえずこんな感じにPDO
をnewして接続して、失敗したら繰り返せばいいやというスタンスです。
さくっとできたのでさくっとテストで確認してみよ!とPHP5.6環境で動かしてみたところ・・・
PHP Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 4104 bytes) in /Users/me/Projects/testcontainers-php/src/Containers/WaitStrategy/PDO/PDOConnectWaitStrategy.php on line 96
Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 4104 bytes) in /Users/me/Projects/testcontainers-php/src/Containers/WaitStrategy/PDO/PDOConnectWaitStrategy.php on line 96
プロセスは終了コード 255 で終了しました
_人人人人人_
> なんで <
 ̄Y^Y^Y^Y^Y^ ̄
PDOのバグの発見
最初はループの中でGCが動いてないのかと思いgc_collect_cycles
を呼び出したり、$pdo
に中途半端に何か確保されているのか?と空を設定したり、明示的にunset
を呼び出したり、whileループ内だと解放処理うまくいかないのかと関数に切り出してスコープから外すようにしたり、例外で抜けるのが悪いのかとPDO::ERRMODE_SILENT
を設定したりと本当に右往左往していました。
それでもどうにもならず、ChatGPTに聞いたり、Google先生に聞いたりと調べているとついに見つけました。
Memory Leak!!!
どうやらPDO
ではメモリを解放するのはデストラクタでのみであり、コンストラクタで例外が発生するとデストラクタが呼ばれずにメモリリークが発生してしまうようです。
PHP7以降では大丈夫なのか
これ何が怖いってStatus: Wont fix
で止まっていることなんですよね。
ざっとPDOのコードを読んでみても明示的に解放する処理入ってないし、もしかして今のバージョンでも・・・と思って7、8で試したところ大丈夫でした。
セーフ!!!
さすがにPHPのコードの方まで追う元気はないので掘り下げないですが、きっと7以降で何かが変わったんだろうなと一安心です。よかった。
おわりに
軽い気持ちでPHP5.6サポートをやってしまいましたが、タイプヒント使えないし、PHPStanは使えないし、2015年に報告されたバグは踏むしで何のいいこともありませんね!!
と言いつつも、僕はPHP5.xが一番長く触っていたので懐かしい気持ちでいっぱいです。
それにしてもPHP8ではクラスを使って型安全になるように組むのに、今でもPHP5.6を触ると配列をゴリゴリ使って型安全何それになるのは何でなんでしょうね。
何というかPHP5.6ではこう書かねばならないというDNAに刻まれた何かがありそうです。
Discussion