背景: デッドロックの問題というと、テスト環境であればアクセスの遅さやホワイトページ現象を思い浮かべる方が多いと思います(実際に私もテスト環境でこの記事で述べたのと同じ問題に遭遇しました)。 ) PHP php-fpm プロセスを再起動すると、再び正常であることがわかりますが、しばらくすると同様の問題が再び発生します。「最大実行タイムアウト」というログが多数あることがわかります。 60 秒を超えました。これは、一部の php デーモン プロセスが原因である可能性があることがわかります。テスト環境の問題を解決するには、より多くの php-fpm プロセスを開いた方がよいと考えます。そのため、オープンします。この問題の原因は、会社がインストールした PHP は運用保守によってインストールされており、デバッグ版の PHP をインストールする方法と時間がないためです。この問題は運用保守担当者に確認してもらう必要があるとのことですが、発見できると思いますか?それで、この問題は何度も延期されましたが、解決されていませんでした。しかし、ある日、du を使用してディスク全体を確認すると、ディスクがいっぱいであることがわかりました。ディレクトリを 1 つずつ見てみると、それほど多くは占有されていないことがわかります。私は、PHP のデッドロックによってディスク領域が過剰に占有されるのではないかと考えていました。実際に上記の状況に遭遇しました。その後、オペレーティング システムを再起動すると、ディスクが回復しました。そのため、この記事は、PHP 拡張機能のコード品質を厳密に管理する必要があることを説明したいと考えたので、この記事に転送しました。そして、PHP 自体のロックの側面を弱める必要があります (Cookie/セッションとキャッシュのロックを除き、必要でなければ他のものを使用できます)。ロックの使用はできるだけ少なくする必要があります。これはブロガーのちょっとした意見です。本題に入りましょう。
はじめに:
今回は、クラウド ディスク サーバー チームの技術専門家である Xu Tiecheng 氏をお招きし、長らく隠されていた PHP のデッドロックの問題が明らかになりました。この楽しい体験をお届けします、皆さん、このテクノロジーの旅への準備はできていますか?
---------------
問題が見つかりました
最近、多くのオンライン マシンにディスク容量アラームが発生していることがわかりました。ログ ファイルはクリーンアップされましたが、ディスク領域は解放されませんでした。 ps aux | grep php-cgi を通じて、多くのプロセスが数日から数週間、場合によっては数か月前に開始されたことがわかりました。弊社のオンライン php-cgi には最大実行数があります。通常、1 日以内に 1 回再起動されます。暫定的な結論は、これらの CGI プロセスに問題があるということです。
lsof -p [pid] により、長時間起動された CGI プロセスで一部のログ ファイル ハンドルが開かれ、閉じられていないことが判明しました。これらのログ ファイルはファイル システムから削除されました。ただし、ハンドルが閉じられていないため、ディスク領域が解放されません。この時点で、異常なディスク容量の問題は基本的に特定されます。これは、CGI がファイル ハンドルを閉じていないことが原因です。
プロセスをさらに分析し、strace -p [pid] を実行すると、すべての異常なプロセスが fmutex 状態でブロックされていることを確認します。つまり、異常な CGI プロセスがデッドロック状態になります。プロセスのデッドロックにより、開いているファイル ハンドルが閉じられなくなり、ディスク領域の異常が発生します。
CGI プロセスがデッドロックするのはなぜですか?
デッドロックとは
オペレーティング システムを勉強したことのある人なら誰でも、マルチスレッドの概念を知っています。複数のスレッドでパブリック リソースにアクセスするには、リソースをロックする必要があります。アクセスが完了したらロックを解除してください。ロックが解放されないと、次のスレッドがリソースを取得するときにリソース ロックを取得できなくなり、スレッドはデッドロックになります。では、CGI はマルチスレッドのパブリック リソース アクセスによって引き起こされるデッドロックなのでしょうか? 答えは「ノー」です。
1. CGI はシングルスレッドプロセスであり、ps を通じて見ることができます。 (プロセスステータスS1はマルチスレッドプロセスである)。
2. マルチスレッドであっても、デッドロックは PHP のシャットダウン処理中に glibc の time 関数が呼び出されたときに発生します。これは PHP モジュールが原因ではありません。 glibc の時間関連関数はスレッドセーフであり、デッドロックを引き起こしません。
デッドロックの原因は何ですか?
Linux におけるデッドロックのメカニズムを解析した結果、マルチスレッドによるデッドロック以外に、信号処理関数によってもデッドロックが発生する可能性があることがわかりました。では、CGI のデッドロックはシグナル処理が原因なのでしょうか?その前に、一つ考えを紹介します。
関数の再入性とシグナルの安全性
関数の再入性とは、関数が何度入力されても、関数が正常に実行され、結果を返すことができることを意味します。では、スレッドセーフ関数はリエントラントなのでしょうか?答えは「ノー」です。 スレッドセーフ関数は、パブリック リソースに初めてアクセスするときにグローバル ロックを取得します。関数が実行されておらず、ロックも解除されていない場合、処理は中断されます。その後、割り込み処理関数で再度アクセスするとデッドロックが発生します。では、割り込み処理関数ではどのような関数にアクセスできるのでしょうか。 グローバル ロックを使用しない関数に加えて、使用できるシグナル セーフ システム コールもいくつかあります。他の非シグナルセーフ関数を呼び出すと、予期しない結果 (デッドロックなど) が発生します。 詳細については、マンシグナルを参照してください。デッドロックの原因を分析する前に、まずはCGIの実行プロセスを見て、デッドロックの可能性があるか分析してみましょう。
PHP-CGI 実行プロセス
Glibc の time 関数は、関数のスレッド安全性を確保するためにグローバル ロックを使用しますが、信号の安全性は保証されません。以前の分析の後、私たちは当初、デッドロックの原因は PHP-CGI プロセスがシグナルを受信し、シグナル ハンドルで非シグナル セーフ関数を実行したことが原因であると疑っていました。メインプロセスが中断される前に、glibc の time 関数が実行されます。関数で取得したロックが解除される前に割り込み処理に移行します。割り込み処理中に、glibc の time 関数がアクセスされます。これによりデッドロックが発生しました。
PHP-CGI の実行フローは次の図に示すとおりです。
さらなる分析により、デッドロックされたすべての CGI プロセスの sapi_global にエラー メッセージが記録されていることが判明しました。
「最大実行タイムアウト 60 秒を超えました。」
60 秒は、php-cgi で設定された実行タイムアウトです。そこで、cig が実行中にタイムアウト例外を生成し、longjmp によりシャットダウン プロセスに入ったことを確認しました。 glibc の時間関数はシャットダウン プロセス中にアクセスされます。デッドロックを引き起こした。
void zend_set_timeout(長い秒数)
{
TSRMLS_FETCH();
EG(timeout_秒) = 秒;
if( !秒) {
return;
}
……
setitimer(ITIMER_PROF, &t_r, NULL);
signal( SIGPROF, zend_timeout); // ここで zend 例外処理関数が呼び出されます
sigemptyset(&sigset);
sigaddset(&sigset, SIGPROF);
……
}
gdb デバッグにより、すべての PHP-CGI が zend_request_shutdown でブロックされていることがわかりました。 zend_request_shutdown は、ユーザー定義の PHP スクリプトに実装されたシャットダウン関数を呼び出します。 CGI がスーパーマーケットを実行すると、タイマーは SIGPROF 信号を生成して実行プロセスを中断します。この時点でたまたまスクリプトが time 関数を呼び出している状態で、ロック リソースがまだ解放されていない場合。次に、実行プロセスはタイムアウト関数に入り、引き続き zend_request_shutdown にジャンプします。このとき、カスタムシャットダウン関数で時刻関数にアクセスした場合。デッドロックが発生します。
register_shutdown_function ('SimpleWebSvc:: shutdown');
PHP コードでは qalarm システムを使用しており、qalarm システムは CGI の後に終了 (シャットダウン) します。このとき、CGI の実行が正常かどうかを解析するためのフック関数が挿入されます。正常に実行されていない場合は、アラーム メッセージが送信されます。たまたま、qalarm のアラーム処理関数で time 関数がアクセスされています。したがって、一定の確率でデッドロックが発生します。
結論
上記の分析により、CGI デッドロックの原因がわかりました。シグナル ハンドラー内でシグナル セーフではない関数が使用されており、デッドロックが発生していることがわかりました。
解決策
qalarm がシャットダウン時に登録するフック関数を削除または簡略化します。安全でない関数呼び出しを避けてください。
出典: http://www.v2gg.com/lady/shishangzixun/20140924/57266.html