1. ホーム
  2. スクリプト・コラム
  3. ルア

.soファイルを呼び出すためのNginx+lua

2022-01-06 05:41:50

本サービスでは、携帯電話から渡されたビーコンデバイスリストをもとに、特定のショッピングモールをあるアルゴリズムに従って算出し、モールIDとビーコンデバイスリストをパラメータとして、.soファイル内の計算メソッドを呼び出して位置データ(座標:x、y、z)を導き出し、携帯電話に返送するものです。

このサービスはQPSの要求が高く、純粋なクエリ操作であるため、(社内で主流となり成熟している)Nginx+lua+Redisアーキテクチャを採用することにしました。luaが.soファイルを呼び出す方法について説明します。

luaが.soファイルを呼び出す方法は、大きく分けて2つあります。

1. Luaは、Tech Shareに記載されているように、ダイナミックリンクライブラリを直接呼び出します。
2. C 言語で Wrapper を作成します。

1番目のアプローチではサードパーティのツールキットを導入する必要があり、効率も悪いことから、2番目のアプローチで実装することにします。実装は以下の通りである。

1. ビジネスメソッドのラッパーを含むビジネスコードを記述する。

  static int lua_Locate( lua_State* L )
  {
    long handle = lua_tonumber( L, 1 );
    const char* beacon_rssi_json = lua_tostring( L, 2 );

    vector<RSSI_INFO> rssi_info_vec;

    FingerprintLocationServer* p = (FingerprintLocationServer*)handle;

    ConvertJson2CppRSSI( beacon_rssi_json, rssi_info_vec );

    double x;
    double y;
    float floor;

    p->UpdateBeaconSignalGetResult( rssi_info_vec, x, y, floor );

    lua_pushnumber(L,x);
    lua_pushnumber(L,y);
    lua_pushnumber(L,floor);

    return 3;
  }

  static const struct luaL_Reg myLib[] =  
  { 
    {"lua_Locate", lua_Locate} 
    {NULL, NULL} // the last pair in the array must be {NULL, NULL}, to indicate the end   
  }; 

  int luaopen_mLualib(lua_State *L) 
  { 
    luaL_register(L, "FPCalc", myLib); 
    return 1; // pressed the myLib table onto the stack, so it needs to return 1 
  }



wapper関数の名前には命名規則があり、luaopenというプレフィックスを付け、その後にluaで必要な文字列を付けると、次のような例外が報告されます。

lua entry thread aborted: runtime error: error loading module 'mLualib' from file '/var/wdd/wrs/webroot/intelligent_lua/mLualib.so':
  /var/wdd/wrs/webroot/intelligent_lua/mLualib.so: undefined symbol: _Z13lua_tolstringP9lua_StateiPm
stack traceback:
coroutine 0:
  [C]: in function 'require'
  /var/wdd/wrs/webroot/intelligent_lua/location.lua:18: in function...


また、.cppファイルであれば、luaopen_mylibにextern "C"を必ず追加してください。そうしないと、エクスポートした関数がリネームされます。覚えておいてくださいね。extern "C"については、extern "C" Usage Explanationを参照してください。

2. Nginxの設定ファイルにsoパッケージがあるフォルダを指定します。

lua_package_cpath '/var/wdd/wrs/webroot/intelligent_lua/? .so;;';

なお、luaがsoファイルを見つけることができれば、例えば、luaのコードpackage.cpathを通じて導入したり(以下のスニペットを参照)、soファイルをlua環境変数で指定したディレクトリに直接コピーするなど、様々な方法で設定することが可能です。
package.cpath = '/usr/local/lib/lua/5.1/? .so;'         -- So モジュールの検索

cpathが指定されていない場合、またはcpathにsoファイルが見つからない場合は、以下のような例外が報告されます。

no file '. /mLualib.lua'
  no file '/usr/local/openresty/luajit/share/luajit-2.1.0-beta1/mLualib.lua'
  no file '/usr/local/share/lua/5.1/mLualib.lua'
  no file '/usr/local/share/lua/5.1/mLualib/init.lua'
  no file '/usr/local/openresty/luajit/share/lua/5.1/mLualib.lua'
  no file '/usr/local/openresty/luajit/share/lua/5.1/mLualib/init.lua'
  no file '/usr/local/openresty/lualib/mLualib.so'
  no file '. /mLualib.so'
  no file '/usr/local/lib/lua/5.1/mLualib.so'
  no file '/usr/local/openresty/luajit/lib/lua/5.1/mLualib.so'
  no file '/usr/local/lib/lua/5.1/loadall.so'
  no file '/var/wdd/wrs/webroot/intelligent_lua/mLualib.so'


3. luaのコードでは、soパッケージを導入し、呼び出しを実行しています。

local FPCalc = require "mLualib"

local x, y, floor = FPCalc.lua_Locate(c_addr, umm_json)

ngx.log(ngx.ERR, "lua_Locate:end:return result:", "x=" . x, " y=" . y, " floor=" . floor)



これが、luaから.soパッケージを呼び出すまでの全体の流れです。

実際の圧力テストでは、さらにいくつかの問題が見つかりました。

1. soパッケージは実行環境でコンパイルする必要があり、異なる環境でコンパイルされたsoパッケージは必ずしも一般的ではありません。例えば、macでコンパイルしたsoパッケージをそのまま本番環境(centos)にコピーすると動作せず、本番環境で再コンパイルしないと動作しないことがありました。

2. コンパイルしたsoパッケージは、単一プロセスでは正常に実行されるが、複数プロセスでアクセスすると例外が発生し、以下のようなエラーメッセージが表示される(この問題はまだ解決されていない)。

2017/05/03 16:52:41 [notice] 14355#0: 信号17(SIGCHLD)を受信しました。
2017/05/03 16:52:41 [alert] 14355#0: ワーカープロセス14361がシグナル11で終了しました。
2017/05/03 16:52:41 [通知] 14355#0: ワーカープロセス14427を開始します。
2017/05/03 16:52:41 [notice] 14355#0: 信号29(SIGIO)を受信しました。
2017/05/03 16:52:41 [notice] 14427#0: sched_setaffinity(0x00000008)です。

2017-05-04 やっと原因がわかりました。

原因:あるプロセスが.soファイルにアクセスする際、.so内の初期化メソッドを呼び出す必要がある。このメソッドはメモリの初期化などを行うもので、各プロセスは個別に1回呼び出す(初期化する)必要がありますが、私のプロセスはすべて1回しか呼び出さない(初期化しない)ため、初期化しないプロセスがあると例外的にコードを実行することになります。

解決方法 原因がわかったら、各プロセスを一度初期化することで問題は解決します。