PHP7にアップデートしたらSegmentation Fault (11)がたくさん出た

事件の発端ですが、何が起きていたのかについてちょっと書いておこうと思います。
ログの調査のあらましはteratailさんで質問させていただいた中で色々書きました。

PHP7update後、Segmentation faultが起きます。追記・PHPのGCが原因?(26242)|teratail

従来環境のPHP5.4の状態では発生しなかったエラーが、PHP7の環境になって急に出てきたという話なのですが、PHPのWarning等ではなくcoreが吐いているエラーなので、環境側の設定の問題かと考えて、コアダンプを見てみたり試行錯誤していましたが、結局解決に至りませんでした。

さくらVPSのプランがちょっと古かったこともあって、
もういっそWeb環境を引っ越しして、Web+DB構成にしつつ、スクリプトやApache設定を色々と見なおそう!と思い立ち、新VPSを契約して新環境を構築、様々なリファクタリングを施したうえでのお引越しとなったのでした。

この件、最初に環境側を疑ってしまってはいるのですが、そもそも自作のスクリプト部分に問題は無かったのか、というと、おそらくその辺が核心だったのでは…と、今となっては思います。

再現性が無く、憶測でしかないのですが、なんとなく現時点での原因と思しき部分は、AWS for PHP v2でさくらのオブジェクトストレージに接続してファイル操作している部分です。接続した後にS3Cliantを解放していないと、このエラーが発生する可能性があるような気がします。

というのも、新環境での検証や、引っ越し直後の本番検証中などでも、実はSegmentation Faultがいくつか発生することを確認したのですが、その時のアクセスログ等をまとめると、どうやらそのあたりを利用しているPHPファイルのアクセスが近いので、そうなのかな…と。
確信があったのではなく、リファクタリング中にそこは直していたのですが、一部直し漏れていたところで発生したのかも?と思えるような挙動があったのです。

一部の例ですが、オブジェクトストレージから画像を取ってきて、そのまま画像ファイルとして出力するスクリプトを下記のようにしていました。
 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
//オブジェクトストレージから画像ファイルを取り出して出力する
require_once("aws.phar");
use Aws\S3\S3Client;
use Aws\Common\Enum\Region;
use Aws\S3\Exception\S3Exception;
use Guzzle\Http\EntityBody;
  //S3Cliantを作成
  $client = S3Client::factory(array(
        'key' => "key_name",
        'secret' => "secret_key",
        "base_url"=> "http://b.sakurastorage.jp",
        'region' => Region::AP_NORTHEAST_1));
  //バケット名
  $bucket = "namespace";
  //key
  $key = "file_path"
    try {
      //ファイル取得
      $result = $client->getObject(array(
        'Bucket' => $bucket,
        'Key' => $key
        ));
    } catch (S3Exception $exc) {
      //取得失敗・出力指定があれば
      echo $ext;
      exit();
    }
     
    //ファイルサイズ
    $length = $result['ContentLength'];
    //ファイルポインタを先頭に戻し、ファイルを読み込む
    $result['Body']->rewind();
    $data = $result['Body']->read($length);
     
    header('Content-Type: '.$result['ContentType']);
    //キャッシュはさせない
    header( 'Expires: Thu, 01 Jan 1970 00:00:01 GMT' );
    header( 'Last-Modified: '.gmdate( 'D, d M Y H:i:s' ).' GMT' );
     
    // HTTP/1.1
    header( 'Cache-Control: no-store, no-cache, must-revalidate' );
    header( 'Cache-Control: post-check=0, pre-check=0', FALSE );
     
    // HTTP/1.0
    header( 'Pragma: no-cache' );
     
    //出力   
    echo $data;
   
    exit();

アクセスログなどを追いかけるに、こういったことをしているスクリプトのまわりで、Segmentation Faultが起きていたように思えます。
このスクリプトへ、終了するタイミングの手前で$cliantをunsetする等の処置を施していたところ、エラーが止まったように思います。
メモリの解放時に発生するエラーだったようなので、妙なオブジェクトが残ったままスクリプトが終了すると、GCの処理時に何か悪さをするのかもしれません。

■修正後:26行目・52~54行目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
//オブジェクトストレージから画像ファイルを取り出して出力する
require_once("aws.phar");
use Aws\S3\S3Client;
use Aws\Common\Enum\Region;
use Aws\S3\Exception\S3Exception;
use Guzzle\Http\EntityBody;
  //S3Cliantを作成
  $client = S3Client::factory(array(
        'key' => "key_name",
        'secret' => "secret_key",
        "base_url"=> "http://b.sakurastorage.jp",
        'region' => Region::AP_NORTHEAST_1));
  //バケット名
  $bucket = "namespace";
  //key
  $key = "file_path"
    try {
      //ファイル取得
      $result = $client->getObject(array(
        'Bucket' => $bucket,
        'Key' => $key
        ));
    } catch (S3Exception $exc) {
      //取得失敗・出力指定があれば
      echo $ext;
      unset($client); //解放処理を追加
      exit();
    }
     
    //ファイルサイズ
    $length = $result['ContentLength'];
    //ファイルポインタを先頭に戻し、ファイルを読み込む
    $result['Body']->rewind();
    $data = $result['Body']->read($length);
     
    header('Content-Type: '.$result['ContentType']);
    //キャッシュはさせない
    header( 'Expires: Thu, 01 Jan 1970 00:00:01 GMT' );
    header( 'Last-Modified: '.gmdate( 'D, d M Y H:i:s' ).' GMT' );
     
    // HTTP/1.1
    header( 'Cache-Control: no-store, no-cache, must-revalidate' );
    header( 'Cache-Control: post-check=0, pre-check=0', FALSE );
     
    // HTTP/1.0
    header( 'Pragma: no-cache' );
     
    //出力
    echo $data;
     
    //解放処理を追加
    unset($result);
    unset($client);
    unset($data);
   
    exit();

この修正をする前で、新環境でのテスト中でも、自分のアクセスでこのエラーは発生しなかったので、
元々100%発生するものではなかったのだと思いますが、少なくとも原因のひとつだったのではという気がします。

ちなみに、AWS for PHPはv3も出ているのですが、さくらのオブジェクトストレージはおそらくv2でないと接続出来ないようでしたので、v2を使っています。
接続先がAWSで、ライブラリもv3ならば同じ問題は起きないかもしれないです。

コメント

タイトルとURLをコピーしました