PHPのシステムをHHVMに載せた時に動かなくて直した所一覧

PHPの既存システムをHHVMに載せ、その後サービスを維持しつつHackに変え、静的型付けやモダンな書き方により、堅牢なものに変えていくことを目的に試験環境を構築しハマりつつも大体動いたレベルの所まで行ったのでここに残しておく。

phpconには行ってないけどHHVMの注目度も上がりそうだし役に立つかもなので今日書く事にしました。

まずやったこと

  1. 既存PHPプロジェクトをDocker上に展開できるように用意
    何度も試行錯誤することが目に見えていたため、環境構築・再現がしやすい事が必須と考えた
  2. HHVMをDocker上でビルドできるように
    Ubuntuだとそれ程困らないが、元の環境がCentOSだったため、自分でビルドするものを用意した。元はCentOS6系だったが残念ながらそちらでのビルドはハマりどころが多すぎたためCentOS7にした。githubとDocker hub両方に上げておいた。


    suzuki0keiichi/docker_centos7_hhvm · GitHub


    suzuki0keiichi/centos7_hhvm Repository | Docker Hub Registry - Repositories of Docker Images

    Dockerfileの方はRUNが大量に書いてあるビルドスクリプト同様のものなので、Docker上で動かさない場合でもビルドするのには役に立つかも。
    10/12追記 静的解析などに使用するhh_serverコマンドは$USERが設定されていないと動かないため、Dockerで起動したそのまま(root)の状態だと実行できないので気をつける。
  3. 1で出来たもののPHP部分だけを2に差し替え
  4. httpdとかの設定を追加
    mod_rewriteとmod_phpでのベタな構成だったため、追加でProxyPassMatchを入れるだけで良かった。

 ProxyPassMatch ^/(.+.(hh|php)(/.*)?)$ fcgi://127.0.0.1:9000/PHPSOURCEROOT/$1

はまりどころ

「不具合まで再現する」をポリシーとしてPHPを再現しているHHVMだが、残念ながらまだ差異がある所がある。

ここからの説明では「さすがにそんなコードを書くほうが悪い」と思われる所が出て来るかもですが、旧石器時代且つ自分が書いたものではないのでお察し下さい、、、

大きく分けると際のある場所には32種類あった。

  • 各組み込み型、関数の微細な挙動の違い
  • 組み込み型や組み込み型を継承した型のオブジェクトのシリアライズ、get_object_vars等のオブジェクト全体に対する操作
  • evalやpreg_replaceなどの中でのevalが実行されるスコープの違い 10/13修正
各組み込み型、関数の微細な挙動の違い

今回出会ったのは幸い一件だけだった。

2件の問題に遭遇した。

一件目はfile_get_contentsでhttpヘッダーを渡す際にヘッダーの改行文字の扱いで、PHPの場合はCRLFでもLFでも正常にヘッダーが送信されるが、HHVMの場合ではLFだとヘッダーのフォーマットがおかしくリクエスト先サーバーにBad Requestを返されてしまう。


hhvm/http-stream-wrapper.cpp at 07f8201682818abdcc0aa581c9eaa991b2b04965 · facebook/hhvm · GitHub

この辺りが問題と思われるので、報告すれば修正してもらえるかも。

英語コミュニケーションの億劫さであんまり自分でやる気がない、、、

 

二件目はpreg_replaceで/eした場合のevalが実行されるスコープの違いだった。

(preg_replaceの/eは現在非推奨となっている)

通常のevalの方はselfもローカル変数も正しく参照できた。

PHPの場合はpreg_replaceが動いた箇所によってselfが何を指しているかちゃんと理解されたり、外で宣言されているローカル変数だったりを使うことが出来る。

HHVMの場合はpreg_replaceでselfを指定するとphpレベルで解決できない旨の警告メッセージが表示される。また、ローカル変数も見つけられなかったようなので、とりあえずstatic変数に入れるという不安な形で実現させた。

動くには動くが、非常にモヤモヤするので解決策を探して行きたい。

当初この差異はeval系全般における問題と勘違いして別カテゴリで書いていたが、実際にはpreg_replaceのみに起こる問題だったため関数個別の問題のカテゴリに移しました。

組み込み型や組み込み型を継承した型のオブジェクトのシリアライズ、get_object_vars等のオブジェクト全体に対する操作

全部の組み込み型かは不明。今回あったのはSimpleXMLElementとDOMDocumentだった。

serialize関数を呼んだ際にphpのエラーログとして通知してくれるケースもあれば、unserializeの時に何故か抜け落ちるケースも有る。後者は特定がかなり難しい。

しかし問題箇所さえ分かってしまえば対応は結構簡単で、__sleepと__wakeupの際に上手く組み込み型を保存しなくても済む状態にすれば良い。

継承してしまっていてコードの変更が大変そうに見える場合でも、privateなプロパティーとして持つように変更し、__callで中継してやることで影響箇所を小さくすることが出来る。

 

大体こんな事をやったら今回実験対象だったシステムは動くようになった。

これから先に同じことをやろうとする人たちのお役に立てば幸いと言うか、ぜひHHVMに乗り換えていってもらってHHVMをFacebookと一蓮托生じゃない感じにして頂けたらと思います。