事の起こり
NIF (Native Implemented Functions) を含むパッケージを escript で利用しようとしたのですが。
ビルドした実行ファイルを実行しても NIF のライブラリファイルを読み込めないとエラーになり。
検索してみたらこのとおり。
Unfortunately that's a limitation of escripts. They cannot access anything in priv and therefore they cannot access embedded .so files. In other words, it is not currently possible to build escripts with NIFs in them.
https://github.com/elixir-lang/elixir/issues/5444#issuecomment-260178836
You can not use NIFs in an escript.
https://elixirforum.com/t/escript-can-not-load-argon2-nif-wrong-approach-for-cli/25399/2
と、いうことなので。 NIF を含むサーバを起動しておいて escript の実行ファイルからプロセス間通信でリクエストを送ればよいじゃないか、と考えたしだい。
サーバを用意する
まずリクエストを受ける適当なサーバを用意します。
$ mix new my_server --sup
モジュールを用意する
適当なサーバモジュールとアプリケーションモジュールを用意します。
defmodule MyServer do use GenServer @name __MODULE__ def start_link(opts) do GenServer.start_link(__MODULE__, opts, name: @name) end def double(value) do GenServer.call(@name, {:double, value}) end def init(_opts) do {:ok, :no_state} end def handle_call({:double, value}, _from, state) do {:reply, value * 2, state} end end
defmodule MyServer.Application do @moduledoc false use Application def start(_type, _args) do children = [ MyServer ] opts = [strategy: :one_for_one, name: MyServer.Supervisor] Supervisor.start_link(children, opts) end end
release 設定を用意する
ノード間で通信を行うには、ノードに名前を設定する必要があります。
まず mix release.init
コマンドを実行して設定ファイルを生成します。
$ mix release.init * creating rel/vm.args.eex * creating rel/env.sh.eex * creating rel/env.bat.eex
rel/env.sh.eex
(MS Windows のばあいは rel/env.bat.eex
)を編集し、ファイルの末尾の 2 行のコメントアウトを外します。
この行の直前のコメントに書かれているように、これでノード名が設定されます。
デフォルトではアプリケーション名 + 127.0.0.1
という名前になるので、今回は my_server@127.0.0.1
という名前が設定されます。
export RELEASE_DISTRIBUTION=name export RELEASE_NODE=<%= @release.name %>@127.0.0.1
cookie を設定する
安全のために cookie を設定します。
明示的に cookie を設定しない場合は自動的にランダムな値が設定されます。 その値を利用しても問題ないのですが、ここでは説明を兼ねて設定します。
mix.exs
ファイルを開き、project/0
が返すキーワードリストに :release
を追加します。
release には名前をつけて異なる設定で生成することができますが、ここではデフォルトのアプリケーション名に対して cookie の設定をします。
defmodule MyServer.MixProject do use Mix.Project def project do [ app: :my_server, version: "0.1.0", elixir: "~> 1.10", start_permanent: Mix.env() == :prod, deps: deps(), releases: [ my_server: [ cookie: "MY_SERVER_COOKIE" ] ] ] end # 以下略 end
release を作成する
mix release
を実行します。
$ mix release
実行に必要なファイルが _build/dev/rel/my_server
に生成されます。
今回は MIX_ENV
を指定していないのででデフォルトの dev
で生成されています。
サーバを起動するにはこのディレクトリに含まれる _build/dev/rel/my_server/bin/my_server
を利用します。
動作を確認する
start
または daemon
コマンドでサーバを起動します。
$ _build/dev/rel/my_server/bin/my_server start
または
$ _build/dev/rel/my_server/bin/my_server daemon
サーバが起動したら remote
コマンドでサーバのノードに接続します。
start
で起動したばあいは別のコンソールを開いてコマンドを実行してください。
$ _build/dev/rel/my_server/bin/my_server remote iex(my_server@127.0.0.1)1> MyServer.double(123) 246
動作が確認できたら stop
コマンドで停止します。
start
で起動したばあいは Ctrl+C でも停止できます。
$ _build/dev/rel/my_server/bin/my_server stop
escript を用意する
次にリクエストを送る適当な escript を用意します
$ mix new my_cli
モジュールを用意する
エントリポイントになる関数 main/1
を定義したモジュールを用意します。
defmodule MyCli do def main(["double", arg]) do value = String.to_integer(arg) result = :rpc.call(:"my_server@127.0.0.1", MyServer, :double, [value]) IO.puts("#{value} x2 = #{result}") end end
リモート呼び出しにここでは Erlang のモジュール rpc
の関数 call/4 を利用します。
まずサーバを起動します。
サーバのディレクトリでサーバを起動します。
$ _build/dev/rel/my_server/bin/my_server daemon
escript のディレクトリで iex
を起動します。
このとき cookie はサーバで設定した内容に合わせます。
$ iex --name my_cli@127.0.0.1 --cookie MY_SERVER_COOKIE -S mix
関数を呼び出します。
iex(my_cli@127.0.0.1)1> MyCli.main(["double", "123"]) 123 x2 = 246 :ok
無事サーバの関数を呼び出せることが確認できました。
動的にノード名と cookie を設定する
上の例では iex
のオプションとしてノード名と cookie を指定していますが、escript として単独の実行ファイルとして作成したいので別の方法で設定する必要があります。
ノード名の設定は Node.start/3
を、cookie の設定は Node.set_cookie/2
を使うことで実現できます。
先の main/1
を編集して動的に設定するようにしてみます。
defmodule MyCli do def main(["double", arg]) do value = String.to_integer(arg) {:ok, _pid} = Node.start(:"my_cli@127.0.0.1") Node.set_cookie(:MY_SERVER_COOKIE) result = :rpc.call(:"my_server@127.0.0.1", MyServer, :double, [value]) IO.puts("#{value} x2 = #{result}") end end
start/3
に渡すノード名と set_cookie/2
に渡す cookie の値はどちらも atom である必要があります。
動作を確認します。
今回はノード名と cookie を指定せずに iex
を起動します。
$ iex -S mix
main/1
を呼び出します。
iex(1)> MyCli.main(["double", "123"]) 123 x2 = 246 :ok iex(my_cli@127.0.0.1)2>
今回も無事サーバの関数を呼び出せることが確認できました。
また main/1
が終了してもノード名が設定されたままになっていることがプロンプトからわかります。
これを終了するには関数 Node.stop/0
を呼び出します。
escript を生成する
mix.exs
を編集して escript の設定を追加します。
defmodule MyCli.MixProject do use Mix.Project def project do [ app: :my_cli, version: "0.1.0", elixir: "~> 1.10", start_permanent: Mix.env() == :prod, deps: deps(), escript: [main_module: MyCli] ] end # 以下略 end
escript をビルドします。
$ mix escript.build
実行します。 サーバを起動しておくことを忘れないでください。
$ ./my_cli double 123 123 x2 = 246
これでコマンドラインからサーバの機能を利用することができるようになりました。
いつか読むはずっと読まない:sense of wonder
原著が 1981 年、邦訳は 1982 年。 数年前にあった復刊フェアで手に入れました。
数学パズルものや論理パズルものの一つのつもりで手にしましたが、むしろ SF 仕立ての部分が刺さります。 古典的な SF が好きな人はどうぞ。
- 作者:マーティン ガードナー
- 出版社/メーカー: 紀伊國屋書店
- 発売日: 2011/05/01
- メディア: 単行本