Elixir Concurrent Programming(2) - GenServer

**■ ElixirのConcurrent Programmingについてまとめた過去記事**
[Elixir Concurrent Programming(1) - Spawn - Qiita](https://qiita.com/sand/items/d7fda56fe2fef0f4a24d)
[Elixir Concurrent Programming(2) - GenServer - Qiita](https://qiita.com/sand/items/5fd91c0b86b4919d7cc9)
[Elixir Concurrent Programming(3) - Supervisors - Qiita](https://qiita.com/sand/items/3075b909b65c2fa2fc23)
[Elixir Concurrent Programming(4) - Task and Agent　-Qiita](https://qiita.com/sand/items/244f060555d106a923d8)


　spawnで作られたプロセスは、tail recursionの中でreceiveを使い、永続的にクライアントからのリクエストを処理を行うことでサーバの役割を実現することができます。さらに recursionにおいて引数としてstateを持たせることも簡単にできます。

　しかしもっと簡単にstateを持ったサーバを実現するには、OTP GenServer behaviorを使うことです。これはModuleの定義においてuse GenServer行を追加することで可能です。OTPはクライアントからのリクエストに応じて適切なcallbackを呼び出します。例えばcallbackはhandle_call(request,client,state)のような形で呼び出されます。callbackは何らかの処理を行い、stateを更新し、returnを返します。

　OTP behavioursにはGenServerの他に、supervisor（concurrent systemsにおけるエラーリカバリー）やapplication(components や librariesの実装)などがあります。


**■ Phoenix LiveView で GenServer を使ってみた(2022/10/23)**
[東京電力電力供給状況監視 - Phoenix LiveView - Qiita](https://qiita.com/sand/items/ecac700bc3e32f106279)


# １．GenServerの定型


## １－１．pidでcallする

　
　GenServerのmoduleは、ServerプロセスとclientのインターフェースであるClientラッピングから構成されます。それぞれの関数の詳細は後で説明しますが、説明なしでも大体理解できると思います。

```elixir:my_server1.ex
defmodule MyServer1 do
  use GenServer    # OTP GenServer behaviorを使う

  ##### (1) Clientラッピング

  def start do
    GenServer.start(MyServer1, nil)
  end

  def put(pid, key, value) do
    GenServer.cast(pid, {:put, key, value})
  end

  def get(pid, key) do
    GenServer.call(pid, {:get, key})
  end


  ##### (2) Serverプロセス

  def init(_) do  # GenServer.startからcallされstateを初期化します
    {:ok, %{}}
  end

  def handle_cast({:put, key, value}, state) do
    {:noreply, Map.put(state, key, value)}
  end

  def handle_call({:get, key}, _, state) do
    {:reply, Map.get(state, key), state}
  end
end
```


　それではiexで実行してみます。

```
# iex  my_server1.ex

### serverを起動させてpidを得ます
iex(1)> {:ok, pid} = MyServer1.start()
{:ok, #PID<0.89.0>}

### pidを指定してstateを更新します
iex(2)> MyServer1.put(pid, :my_key, :taro)
:ok

### pidを指定してstateをgetします
iex(3)> MyServer1.get(pid, :my_key)
:taro

### pidを指定してstateを再度更新します
iex(4)> MyServer1.put(pid, :my_key, :jiro)
:ok

### pidを指定してstateをgetします
iex(5)> MyServer1.get(pid, :my_key)
:jiro

###  Clientラッピング（インターフェース）を通さずに直接call
iex(6)>  GenServer.call(pid ,{:get, :my_key})
:jiro
```





## １－２．名前でcallする

　通常はGenServerからcallbackを呼び出すときは、pidで呼び出します。しかしGenServer.startでnameオプションで名前をつけることで、pidの代わりに名前を使うことができます。多くの場合は、名前としてmodule名(__MODULE__)を使うことが多いようです。

```elixir:my_server2.ex
defmodule MyServer2 do
  use GenServer

  ##### (1) Clientラッピング

  def start do
    GenServer.start(MyServer2, nil, name: __MODULE__) ### 名前付きでstart
  end

  def put(key, value) do
    GenServer.cast(__MODULE__, {:put, key, value})
  end

  def get(key) do
    GenServer.call(__MODULE__, {:get, key})
  end


  ##### (2) Serverプロセス

  def init(_) do
    {:ok, %{}}
  end

  def handle_cast({:put, key, value}, state) do
    {:noreply, Map.put(state, key, value)}
  end

  def handle_call({:get, key}, _, state) do
    {:reply, Map.get(state, key), state}
  end
end
```


　それではiexで実行してみます。

```
# iex  my_server2.ex

### serverを起動させてpidを得ます
iex(1)> {:ok, pid} = MyServer2.start()
{:ok, #PID<0.89.0>}

### pidを指定せずに、stateを更新します
iex(2)> MyServer2.put(:my_key, :taro)
:ok

### pidを指定せずに、stateをgetします
iex(3)> MyServer2.get(:my_key)
:taro

### pidを指定せずに、stateを再度更新します
iex(4)> MyServer2.put(:my_key, :jiro)
:ok

### pidを指定せずに、stateをgetします
iex(5)> MyServer2.get(:my_key)
:jiro

###  Clientラッピング（インターフェース）を通さずに直接call
iex(6)>  GenServer.call(MyServer2 ,{:get, :my_key})
:jiro
```



# ２．Callback関数

　以下にGenServerのcallback関数を説明します。

## ２－１．init(start_arguments)
　新サーバを作成する時にGenServerのstart_link(MyServer, start_arguments)によってcallされます。引数が渡されます。initの通常の処理は state = start_arguments で初期化することです。

```
return値:
{:ok, state} <- 成功時 
{:stop, reason} <- start失敗時
{:ok, state, timeout} 
  <- timeout時間内にmessageが無ければGenServerが:timeout messageを送る
```


## ２－２．handle_call(request, from, state)
　クライアント側がGenServer.call(pid, request)を実行したときに呼ばれるcallbackです。引数のrequestはクライアントからのリクエストです。from = クライアントのpidです。stateはGenServerが保持している状態です。ちなみに一般的にcallという単語は同期呼び出しに使い、castは非同期呼び出しに使われます。

```
{:reply, result, new_state} <- 成功時
{:stop, reason, new_state}  <- Severプロセスを停止したとき
{:stop, reason, response, new_state} <-Serverプロセスの停止前に通知が必要なとき
```



## ２－３．handle_cast(request, state)
　クライアント側がGenServer.cast(pid, request)を実行したときに呼ばれるcallbackです。クライアント側はreplyを期待していません。

```
{:noreply, new_state} <- 成功時
{:stop, reason,new_state}  <- Severプロセスを停止したとき
```


## ２－４．handle_info(info, state)
　callやcast以外のmessageを処理します。例えばtimeout messagesや、linked processesからのtermination messagesがここで処理されます。またGenServerを遠さずにpid指定でsendされたmessageもここで処理されます。



## ２－５．terminate(reason, state)
　Serverプロセスが停止しそうなときに呼ばれます。しかしsupervisionを使うことでここでの処理は必要なくなります。


## ２－６．code_change(from_version, state, extra)
　システム更新（コード変更）が行われても、Serverは停止することがありません。このcallbackはシステム更新があったときに呼ばれ、更新の事実を確認することができます。


## ２－７．format_status(reason, [pdict, state])
stateを表示するフォーマットを変更します。




# ３．デバッグ


## ３－１．[debug: [:trace]]オプション

　GenServer.startで**[debug: [:trace]]**を指定することで、実行時にServerプロセスのstateをtraceすることができます。

```
# iex  my_server1.ex
iex(1)> {:ok,pid} = GenServer.start(MyServer1, nil, [debug: [:trace]])
{:ok, #PID<0.89.0>}
iex(2)> MyServer1.put(pid, :my_key, :taro)
*DBG* <0.89.0> got cast {put,my_key,taro}
*DBG* <0.89.0> new state #{my_key => taro}
:ok
iex(3)> MyServer1.get(pid, :my_key)
*DBG* <0.89.0> got call {get,my_key} from <0.87.0>
*DBG* <0.89.0> sent taro to <0.87.0>, new state #{my_key => taro}
:taro
iex(4)> MyServer1.put(pid, :my_key, :jiro)
*DBG* <0.89.0> got cast {put,my_key,jiro}
*DBG* <0.89.0> new state #{my_key => jiro}
:ok
```


## ３－２．[debug: [:statistics]]オプション

　GenServer.startで**[debug: [:statistics]]**を指定することで、実行時にServerプロセスの簡単な統計を取ることができます。**:sys.statistics**で確認できます。

```
# iex  my_server1.ex
iex(1)> {:ok,pid} = GenServer.start(MyServer1, nil, [debug: [:statistics]])
{:ok, #PID<0.89.0>}
iex(2)> MyServer1.put(pid, :my_key, :taro)
:ok
iex(3)> MyServer1.get(pid, :my_key)
:taro
iex(4)> MyServer1.put(pid, :my_key, :jiro)
:ok
iex(5)> :sys.statistics pid, :get
{:ok,
 [
   start_time: {{2018, 9, 26}, {10, 52, 17}},
   current_time: {{2018, 9, 26}, {10, 53, 2}},
   reductions: 107,
   messages_in: 3,
   messages_out: 0
 ]}
```


## ３－３．:sys.trace 関数

　また別の方法ですが、**:sys.trace**でtraceをon/offできます。さらに**:sys.get_status**で指定したpidのプロセスのstatusを確認することができます。

```
# iex  my_server1.ex
iex(1)> {:ok, pid} = MyServer1.start() # [debug: [:trace]]は不要です。
{:ok, #PID<0.89.0>}
iex(2)> :sys.trace pid, true   # traceをonにします。
:ok
iex(3)> MyServer1.put(pid, :my_key, :taro)  
*DBG* <0.89.0> got cast {put,my_key,taro}
*DBG* <0.89.0> new state #{my_key => taro}
:ok
iex(4)> MyServer1.get(pid, :my_key)
*DBG* <0.89.0> got call {get,my_key} from <0.87.0>
*DBG* <0.89.0> sent taro to <0.87.0>, new state #{my_key => taro}
:taro
iex(5)> MyServer1.put(pid, :my_key, :jiro)
*DBG* <0.89.0> got cast {put,my_key,jiro}
*DBG* <0.89.0> new state #{my_key => jiro}
:ok
iex(6)> :sys.trace pid, false   # traceをoffにします。
:ok
iex(7)> MyServer1.get(pid, :my_key)
:jiro
iex(8)> :sys.get_status pid     # プロセスpidのstatusを確認
{:status, #PID<0.89.0>, {:module, :gen_server},
 [
   [
     "$initial_call": {MyServer1, :init, 1},
     "$ancestors": [#PID<0.87.0>, #PID<0.57.0>]
   ],
   :running,
   #PID<0.89.0>,
   [],
   [
     header: 'Status for generic server <0.89.0>',
     data: [
       {'Status', :running},
       {'Parent', #PID<0.89.0>},
       {'Logged events', []}
     ],
     data: [{'State', %{my_key: :jiro}}]
   ]
 ]}
```


## ３－４．コンパイルチェック @impl GenServer


　my_server3.exをkピーしてmy_server3.exをつくり、以下のhandle_callの引数を削除します。この修正は誤りでありますが、普通にiex  my_server3.exでコンパイルしたときはエラーは生じません。  @impl GenServerを関数の直前に置くことで、コンパイルエラーとなります。

```elixir:my_server3.ex
defmodule MyServer3 do
#
  @impl GenServer
  def handle_call({:get, key},  state) do
  #def handle_call({:get, key}, _, state) do
#
end
```

　それではiexを立ち上げてみましょう。

```
#iex  my_server3.ex
Erlang/OTP 20 [erts-9.1] [source] [64-bit] [smp:3:3] [ds:3:3:10] [async-threads:10] [hipe] [kernel-poll:false]

warning: got "@impl GenServer" for function handle_call/2 but this behaviour does not specify such callback. The known callbacks are:

  * GenServer.code_change/3 (function)
  * GenServer.format_status/2 (function)
  * GenServer.handle_call/3 (function)
  * GenServer.handle_cast/2 (function)
  * GenServer.handle_info/2 (function)
  * GenServer.init/1 (function)
  * GenServer.terminate/2 (function)

  my_server3.ex:31

Interactive Elixir (1.7.0-dev) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>
```




# ４．プロジェクトの中でComponent化する

　Elixirのプロジェクトを作成し、今まで見てきたmy_server1.exをプロジェクトの中におくことにします。

```
mix new mymap
cd  mymap
mkdir lib/mymap
```


　my_server1.exのプログラムコードをcomponent化します。つまり(1) Clientラッピングの部分と、(2) Serverプロセスの部分で、2ファイルに分割します。lib/mymap.exとlib/mymap/server.exに分けます。


　client側のファイルです。これはServerへのAPIと考えられます。

```elixir:lib/mymap.ex
defmodule Mymap do

  @server Mymap.Server   # module attributesで名前を定義します。

  ##### (1) Clientラッピング

  def start_link do
    GenServer.start_link(@server, nil, name: @server) ### 名前付きでstart
  end

  def put(key, value) do
    GenServer.cast(@server, {:put, key, value})
  end

  def get(key) do
    GenServer.call(@server, {:get, key})
  end
```



　Server側のファイルです。

```elixir:lib/mymap/server.ex
defmodule Mymap.Server do
  use GenServer

  ##### (2) Serverプロセス

  def init(_) do
    {:ok, %{}}
  end

  def handle_cast({:put, key, value}, state) do
    {:noreply, Map.put(state, key, value)}
  end

  def handle_call({:get, key}, _, state) do
    {:reply, Map.get(state, key), state}
  end
end
```

　それでは実行してみましょう。

```
# iex -S mix
iex(1)> Mymap.start_link
{:ok, #PID<0.120.0>}
iex(2)> Mymap.put(:my_key, :taro)
:ok
iex(3)> Mymap.get(:my_key)
:taro
iex(4)> Mymap.put(:my_key, :jiro)
:ok
iex(5)> Mymap.get(:my_key)
:jiro
```


今回は以上です。