@torifukukaiou さんが以前取り組まれた内容を複数のDockerコンテナを使ってやってみるという企画です。
あえて違ったアプローチで取り組みます。
執筆時点で最新のElixirバージョンは1.14.2なのですが、あえてオリジナルの記事と同じ1.12.1を対象とします。
やりたいこと
- Elixir 1.12で追加された関数、削除された関数をコード上で確認する
- Elixirバージョンごとにノードをたてて、:application.get_key/2の結果を比較する
- ノードは別々のDockerコンテナから起動する
ノードとは
分散Erlangのドキュメントによると
A distributed Erlang system consists of a number of Erlang runtime systems communicating with each other. Each such runtime system is called a node.
- 分散Erlangシステムは複数のErlangランタイムシステムがお互いに通信することで構成される。
- 分散Erlangシステム上の各ランタイムシステムはノードと呼ばれる。
論よりRun
<
ElixirバージョンごとにDockerコンテナを準備
まずはhexpm/elixirのDockerイメージを使用してElixirが使える仮想Linuxマシンを立ち上げます。
Elixir 1.11.4用の仮想Linuxマシン
docker run --rm -it hexpm/elixir:1.11.4-erlang-24.3.4.6-alpine-3.15.6 /bin/sh
Elixir 1.12.1用の仮想Linuxマシン
docker run --rm -it hexpm/elixir:1.12.1-erlang-24.3.4.6-alpine-3.15.6 /bin/sh
仮想マシンのIPアドレスを探す
他の仮想マシンと接続するにはIPアドレスが必要になりますので、それぞれの仮想マシンのIPアドレスを調べます。いろんなコマンドがありますが、ここではifconfig
を使用します。
ifconfig eth0 | grep inet
ノードを起動
ノードの名前とErlangクッキーを指定してIExを起動することにより、ノードが立ち上がります。
iex --name hoge@172.17.0.7 --cookie mycookie
ノードの名前
--name
オプションにノードの場所(IPアドレス)を含む完全修飾名を指定します。
IPアドレスはご自身のものに置き換えてください。
@
の左側は任意の文字列で大丈夫です。
Erlangクッキー
ノードが接続する際にクッキーが必要になりますので、--cookie
オプションにクラスター共通の文字列を渡します。あるクラスターに属するすべてのノードが同じクッキーを共有することになります。
以上の作業を別々のシェルから実行し、別々の仮想マシンを立ち上げ、ノードを起動します。
分散Erlangシステムを構築する
ノードを接続
hoge
ノードのIExからfuga
ノードに接続します。
:pong = Node.ping(:"fuga@172.17.0.8")
Node.ping/1は、成功した場合は :pong
、失敗した場合は:pang
を返します。IPアドレスはご自身のものに置き換えてください。
Node.list/0で現在接続されているノードのリストが確認できます。
別ノード内でコードを実行できるか確認
hoge
ノードのIExからfuga
ノードに入ってコードを実行します。 試しにIEx.Helpers.runtime_info/0でシステムの情報を取得してみます。
# hogeノード自身のシステム情報
IO.puts(System.version)
runtime_info
# fugaノードのシステム情報
Node.spawn_link(:"fuga@172.17.0.8", fn -> IO.puts(System.version) end)
Node.spawn_link(:"fuga@172.17.0.8", &runtime_info/0)
ちゃんとノード間で通信できることが確認できました。
Elixirすべての関数のリストを取得するコードを定義
:application.get_key/2を利用して、:elixir
アプリケーションのモジュールのリストを取得します。
IEx.configure(inspect: [limit: :infinity])
:application.get_key(:elixir, :modules)
さらにのちに関数リストの差分の計算をしやすいようにあらかじめ:application.get_key/2の結果を加工します。 AllLoadedElixir.get/0
という形にまとめることにしました。
それぞれのノードのIExに以下のモジュールを貼り付けます。
defmodule AllLoadedElixir do
def get do
{:ok, modules} = :application.get_key(:elixir, :modules)
functions =
modules
|> Enum.filter(&String.starts_with?("#{&1}", "Elixir."))
|> Enum.reduce([], fn mod, acc ->
mod.__info__(:functions)
|> Enum.map(fn {f, a} -> "#{mod}.#{f}/#{a}"end)
|> Kernel.++(acc)
end)
|> Enum.map(fn "Elixir." <> mfa -> mfa end)
|> Enum.sort()
%{
version: System.version(),
otp_release: System.otp_release(),
functions: functions
}
end
end
使い方は簡単です。
AllLoadedElixir.get
:code.all_loaded/0だと:elixir
アプリケーション以外のモジュールも含んでいる印象を持ったので、:application.get_key/2を使って明示的に:elixir
アプリケーションのモジュールを取得することにしました。
関数リストを比較
:rpc.call/4を用いて別ノード内でコードを実行します。Node.spawn/2と似ていますが、:rpc.call/4を使うと別ノード内で計算した結果を簡単に受信できます。
# hogeノードの結果(Elixir 1.11)
old = AllLoadedElixir.get
# fugaノードの結果(Elixir 1.12)
new = :rpc.call(:"fuga@172.17.0.8", AllLoadedElixir, :get, [])
removed = old.functions -- new.functions
added = new.functions -- old.functions
length(removed)
length(added)
結果発表
追加された関数
for(f <- added, do: IO.puts(['* ', f]))
- Calendar.ISO.parse_date/2
- Calendar.ISO.parse_naive_datetime/2
- Calendar.ISO.parse_time/2
- Calendar.ISO.parse_utc_datetime/2
- Code.cursor_context/1
- Code.cursor_context/2
- Code.ensure_compiled!/1
- Code.ensure_loaded!/1
- Date.range/3
- Date.to_iso_days/1
- DateTime.to_iso8601/3
- Enum.count_until/2
- Enum.count_until/3
- Enum.product/1
- Enum.zip_reduce/3
- Enum.zip_reduce/4
- Enum.zip_with/2
- Enum.zip_with/3
- ErlangError.error_info/2
- Float.pow/2
- IO.binstream/0
- IO.binstream/1
- IO.stream/0
- IO.stream/1
- Integer.extended_gcd/2
- Integer.pow/2
- List.first/2
- List.last/2
- Module.Types.Expr.of_expr/4
- Module.Types.Unify.collect_var_indexes/2
- Module.Types.Unify.collect_var_indexes/3
- Module.Types.Unify.lift_types/2
- Module.Types.Unify.refine_var!/4
- Module.Types.Unify.restore_var!/3
- Module.delete_definition/2
- Module.get_definition/2
- Module.reserved_attributes/0
- Range.new/3
- Range.size/1
- Registry.Supervisor.start_link/6
- Registry.values/3
- Stream.zip_with/2
- Stream.zip_with/3
- System.SignalHandler.handle_call/2
- System.SignalHandler.handle_event/2
- System.SignalHandler.handle_info/2
- System.SignalHandler.init/1
- System.shell/1
- System.shell/2
- System.trap_signal/2
- System.trap_signal/3
- System.untrap_signal/2
- Tuple.product/1
- Tuple.sum/1
- URI.decode_query/3
- URI.encode_query/2
- URI.query_decoder/2
削除された関数
for(f <- removed, do: IO.puts(['* ', f]))
- Module.Types.Expr.of_expr/3
- Module.Types.Pattern.unify_kinds/2
- Module.Types.Unify.refine_var/4
- Module.Types.lift_type/2
- Module.Types.lift_types/2
- Registry.Supervisor.start_link/5
Elixir公式のリリースドキュメントにざっくりと変更内容が書かれています。
答えがあっているのかどうかは知りませんが、楽しく遊べたのでこの記事はここでお開きとさせていただきます。
ご参考までに