[](){: #gen_server }gen_server Behaviour ======================================== It is recommended to read this section alongside `m:gen_server` in STDLIB. Client-Server Principles ------------------------ The client-server model is characterized by a central server and an arbitrary number of clients. The client-server model is used for resource management operations, where several different clients want to share a common resource. The server is responsible for managing this resource. [](){: #clientserver } ```mermaid --- title: Client Server Model --- flowchart LR client1((Client)) client2((Client)) client3((Client)) server((Server)) client1 --> server server -.-> client1 client2 --> server server -.-> client2 client3 --> server server -.-> client3 subgraph Legend direction LR start1[ ] -->|Query| stop1[ ] style start1 height:0px; style stop1 height:0px; start2[ ] -.->|Reply| stop2[ ] style start2 height:0px; style stop2 height:0px; end ``` Example ------- An example of a simple server written in plain Erlang is provided in [Overview](design_principles.md#ch1). The server can be reimplemented using `gen_server`, resulting in this callback module: [](){: #ex } ```erlang -module(ch3). -behaviour(gen_server). -export([start_link/0]). -export([alloc/0, free/1]). -export([init/1, handle_call/3, handle_cast/2]). start_link() -> gen_server:start_link({local, ch3}, ch3, [], []). alloc() -> gen_server:call(ch3, alloc). free(Ch) -> gen_server:cast(ch3, {free, Ch}). init(_Args) -> {ok, channels()}. handle_call(alloc, _From, Chs) -> {Ch, Chs2} = alloc(Chs), {reply, Ch, Chs2}. handle_cast({free, Ch}, Chs) -> Chs2 = free(Ch, Chs), {noreply, Chs2}. ``` The code is explained in the next sections. Starting a Gen_Server --------------------- In the example in the previous section, `gen_server` is started by calling `ch3:start_link()`: ```erlang start_link() -> gen_server:start_link({local, ch3}, ch3, [], []) => {ok, Pid} ``` `start_link/0` calls function `gen_server:start_link/4`. This function spawns and links to a new process, a `gen_server`. - The first argument, `{local, ch3}`, specifies the name. The gen_server is then locally registered as `ch3`. If the name is omitted, the `gen_server` is not registered. Instead its pid must be used. The name can also be given as `{global, Name}`, in which case the `gen_server` is registered using `global:register_name/2`. - The second argument, `ch3`, is the name of the callback module, which is the module where the callback functions are located. The interface functions (`start_link/0`, `alloc/0`, and `free/1`) are located in the same module as the callback functions (`init/1`, `handle_call/3`, and `handle_cast/2`). It is usually good programming practice to have the code corresponding to one process contained in a single module. - The third argument, `[]`, is a term that is passed as is to the callback function `init`. Here, `init` does not need any indata and ignores the argument. - The fourth argument, `[]`, is a list of options. See `m:gen_server` for the available options. If name registration succeeds, the new `gen_server` process calls the callback function `ch3:init([])`. `init` is expected to return `{ok, State}`, where `State` is the internal state of the `gen_server`. In this case, the state is the available channels. ```erlang init(_Args) -> {ok, channels()}. ``` `gen_server:start_link/4` is synchronous. It does not return until the `gen_server` has been initialized and is ready to receive requests. `gen_server:start_link/4` must be used if the `gen_server` is part of a supervision tree, meaning that it was started by a supervisor. There is another function, `gen_server:start/4`, to start a standalone `gen_server` that is not part of a supervision tree. Synchronous Requests - Call --------------------------- The synchronous request `alloc()` is implemented using `gen_server:call/2`: ```text alloc() -> gen_server:call(ch3, alloc). ``` `ch3` is the name of the `gen_server` and must agree with the name used to start it. `alloc` is the actual request. The request is made into a message and sent to the `gen_server`. When the request is received, the `gen_server` calls `handle_call(Request, From, State)`, which is expected to return a tuple `{reply,Reply,State1}`. `Reply` is the reply that is to be sent back to the client, and `State1` is a new value for the state of the `gen_server`. ```erlang handle_call(alloc, _From, Chs) -> {Ch, Chs2} = alloc(Chs), {reply, Ch, Chs2}. ``` In this case, the reply is the allocated channel `Ch` and the new state is the set of remaining available channels `Chs2`. Thus, the call `ch3:alloc()` returns the allocated channel `Ch` and the `gen_server` then waits for new requests, now with an updated list of available channels. Asynchronous Requests - Cast ---------------------------- The asynchronous request `free(Ch)` is implemented using `gen_server:cast/2`: ```erlang free(Ch) -> gen_server:cast(ch3, {free, Ch}). ``` `ch3` is the name of the `gen_server`. `{free, Ch}` is the actual request. The request is made into a message and sent to the `gen_server`. `cast`, and thus `free`, then returns `ok`. When the request is received, the `gen_server` calls `handle_cast(Request, State)`, which is expected to return a tuple `{noreply,State1}`. `State1` is a new value for the state of the `gen_server`. ```erlang handle_cast({free, Ch}, Chs) -> Chs2 = free(Ch, Chs), {noreply, Chs2}. ``` In this case, the new state is the updated list of available channels `Chs2`. The `gen_server` is now ready for new requests. Stopping -------- ### In a Supervision Tree If the `gen_server` is part of a supervision tree, no stop function is needed. The `gen_server` is automatically terminated by its supervisor. Exactly how this is done is defined by a [shutdown strategy](sup_princ.md#shutdown) set in the supervisor. If it is necessary to clean up before termination, the shutdown strategy must be a time-out value and the `gen_server` must be set to trap exit signals in function `init`. When ordered to shut down, the `gen_server` then calls the callback function `terminate(shutdown, State)`: ```erlang init(Args) -> ..., process_flag(trap_exit, true), ..., {ok, State}. ... terminate(shutdown, State) -> %% Code for cleaning up here ... ok. ``` ### Standalone Gen_Servers If the `gen_server` is not part of a supervision tree, a stop function can be useful, for example: ```erlang ... -export([stop/0]). ... stop() -> gen_server:cast(ch3, stop). ... handle_cast(stop, State) -> {stop, normal, State}; handle_cast({free, Ch}, State) -> ... ... terminate(normal, State) -> ok. ``` The callback function handling the `stop` request returns a tuple `{stop,normal,State1}`, where `normal` specifies that it is a normal termination and `State1` is a new value for the state of the `gen_server`. This causes the `gen_server` to call `terminate(normal, State1)` and then it terminates gracefully. Handling Other Messages ----------------------- If the `gen_server` is to be able to receive other messages than requests, the callback function `handle_info(Info, State)` must be implemented to handle them. Examples of other messages are exit messages, if the `gen_server` is linked to other processes than the supervisor and it is trapping exit signals. ```erlang handle_info({'EXIT', Pid, Reason}, State) -> %% Code to handle exits here. ... {noreply, State1}. ``` The final function to implement is `code_change/3`: ```erlang code_change(OldVsn, State, Extra) -> %% Code to convert state (and more) during code change. ... {ok, NewState}. ```