zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

connection.ex (29442B)


      1 defmodule Connection do
      2   @moduledoc """
      3   A behaviour module for implementing connection processes.
      4 
      5   The `Connection` behaviour is a superset of the `GenServer` behaviour. The
      6   additional return values and callbacks are designed to aid building a
      7   connection process that can handle a peer being (temporarily) unavailable.
      8 
      9   An example `Connection` process:
     10 
     11       defmodule TCPConnection do
     12 
     13         use Connection
     14 
     15         def start_link(host, port, opts, timeout \\\\ 5000) do
     16           Connection.start_link(__MODULE__, {host, port, opts, timeout})
     17         end
     18 
     19         def send(conn, data), do: Connection.call(conn, {:send, data})
     20 
     21         def recv(conn, bytes, timeout \\\\ 3000) do
     22           Connection.call(conn, {:recv, bytes, timeout})
     23         end
     24 
     25         def close(conn), do: Connection.call(conn, :close)
     26 
     27         def init({host, port, opts, timeout}) do
     28           s = %{host: host, port: port, opts: opts, timeout: timeout, sock: nil}
     29           {:connect, :init, s}
     30         end
     31 
     32         def connect(_, %{sock: nil, host: host, port: port, opts: opts,
     33         timeout: timeout} = s) do
     34           case :gen_tcp.connect(host, port, [active: false] ++ opts, timeout) do
     35             {:ok, sock} ->
     36               {:ok, %{s | sock: sock}}
     37             {:error, _} ->
     38               {:backoff, 1000, s}
     39           end
     40         end
     41 
     42         def disconnect(info, %{sock: sock} = s) do
     43           :ok = :gen_tcp.close(sock)
     44           case info do
     45             {:close, from} ->
     46               Connection.reply(from, :ok)
     47             {:error, :closed} ->
     48               :error_logger.format("Connection closed~n", [])
     49             {:error, reason} ->
     50               reason = :inet.format_error(reason)
     51               :error_logger.format("Connection error: ~s~n", [reason])
     52           end
     53           {:connect, :reconnect, %{s | sock: nil}}
     54         end
     55 
     56         def handle_call(_, _, %{sock: nil} = s) do
     57           {:reply, {:error, :closed}, s}
     58         end
     59 
     60         def handle_call({:send, data}, _, %{sock: sock} = s) do
     61           case :gen_tcp.send(sock, data) do
     62             :ok ->
     63              {:reply, :ok, s}
     64             {:error, _} = error ->
     65               {:disconnect, error, error, s}
     66           end
     67         end
     68         def handle_call({:recv, bytes, timeout}, _, %{sock: sock} = s) do
     69           case :gen_tcp.recv(sock, bytes, timeout) do
     70             {:ok, _} = ok ->
     71               {:reply, ok, s}
     72             {:error, :timeout} = timeout ->
     73               {:reply, timeout, s}
     74             {:error, _} = error ->
     75               {:disconnect, error, error, s}
     76           end
     77         end
     78         def handle_call(:close, from, s) do
     79           {:disconnect, {:close, from}, s}
     80         end
     81       end
     82 
     83   The example above follows a common pattern. Try to connect immediately, if
     84   that fails backoff and retry after a delay. If a retry fails make another
     85   attempt after another delay. If the process disconnects a reconnection attempt
     86   is made immediately, if that fails backoff begins.
     87 
     88   Importantly when backing off requests will still be received by the process,
     89   which will need to be handled. In the above example the process replies with
     90   `{:error, :closed}` when it is disconnected.
     91   """
     92 
     93   @behaviour :gen_server
     94 
     95   @doc """
     96   Called when the connection process is first started. `start_link/3` will block
     97   until it returns.
     98 
     99   Returning `{:ok, state}` will cause `start_link/3` to return
    100   `{:ok, pid}` and the process to enter its loop with state `state` without
    101   calling `connect/2`.
    102 
    103   This return value is useful when the process connects inside `init/1` so that
    104   `start_link/3` blocks until a connection is established.
    105 
    106   Returning `{:ok, state, timeout}` is similar to `{:ok, state}`
    107   except `handle_info(:timeout, state)` will be called after `timeout` if no
    108   message arrives.
    109 
    110   Returning `{:ok, state, :hibernate}` is similar to
    111   `{:ok, state}` except the process is hibernated awaiting a message.
    112 
    113   Returning `{:connect, info, state}` will cause `start_link/3` to return
    114   `{:ok, pid}` and `connect(info, state)` will be called immediately - even if
    115   messages are in the processes message queue. `state` contains the state of the
    116   process and `info` should contain any information not contained in the state
    117   that is needed to connect.
    118 
    119   This return value is very useful because connecting in `connect/2` will not
    120   block the parent process and a connection attempt is guaranteed to occur
    121   before any messages are handled, which is not possible when using a
    122   `GenServer`.
    123 
    124   Returning `{:backoff, timeout, state}` will cause `start_link/3` to return
    125   `{:ok, pid}` and the process to enter its normal loop with state `state`.
    126   `connect(:backoff, state)` is called after `timeout` if `connect/2` or
    127   `disconnect/2` is not called within the timeout.
    128 
    129   This return value can be used to delay or stagger the initial connection
    130   attempt.
    131 
    132   Returning `{:backoff, timeout, state, timeout2}` is similar to
    133   `{:backoff, timeout, state}` except `handle_info(:timeout, state)` will be
    134   called after `timeout2` if no message arrives.
    135 
    136   Returning `{:backoff, timeout, state, :hibernate}` is similar to
    137   `{:backoff, timeout, state}` except the process hibernates.
    138 
    139   Returning `:ignore` will cause `start_link/3` to return `:ignore` and the
    140   process will exit normally without entering the loop or calling
    141   `terminate/2`.
    142 
    143   Returning `{:stop, reason}` will cause `start_link/3` to return
    144   `{:error, reason}` and the process to exit with reason `reason` without
    145   entering the loop or calling `terminate/2`.
    146   """
    147   @callback init(any) ::
    148     {:ok, any} | {:ok, any, timeout | :hibernate} |
    149     {:connect, any, any} |
    150     {:backoff, timeout, any} | {:backoff, timeout, any, timeout | :hibernate} |
    151     :ignore | {:stop, any}
    152 
    153 
    154   @doc """
    155   Called when the process should try to connect. The first argument will either
    156   be the `info` term from `{:connect, info, state}` or
    157   `{:connect, info, reply, state}`, or `:backoff` if the connection attempt is
    158   triggered by backing off.
    159 
    160   It might be beneficial to do handshaking in this callback if connecting is
    161   successful.
    162 
    163   Returning `{:ok, state}` or `{:ok, state, timeout | :hibernate}` will cause
    164   the process to continue its loop. This should be returned when the connection
    165   attempt was successful. In the later case `handle_info(:timeout, state)` is
    166   called after `timeout` if no message has been received, if the third element
    167   is a timeout. Otherwise if the third element is `:hibernate` the process
    168   hibernates.
    169 
    170   Returning `{:backoff, timeout, state}` will cause the process to continue
    171   its loop but `connect(:backoff, state)` will be called after `timeout` if
    172   `connect/2` or `disconnect/2` is not called before that point.
    173 
    174   This return value is used when a connection attempt fails but another attempt
    175   should be made after a delay. It might be beneficial to increase the delay
    176   up to a maximum if successive attempts fail to prevent unnecessary work. If
    177   several connection processes are connecting to the same peer it may also be
    178   beneficial to add some jitter (randomness) to the delays. This spreads out the
    179   connection attempts and helps prevent many attempts occurring at the same time.
    180 
    181   Returning `{:backoff, timeout, state, timeout2 | :hibernate}` is similar to
    182   `{:backoff, timeout, state}` except `handle_info(:timeout, state)` is called
    183   after `timeout2` if no message has been received, or if `:hibernate`, the
    184   process hibernates.
    185 
    186   Returning `{:stop, reason, state}` will terminate the loop and call
    187   `terminate(reason, state)` before the process exits with reason `reason`.
    188   """
    189   @callback connect(any, any) ::
    190     {:ok, any} | {:ok, any, timeout | :hibernate} |
    191     {:backoff, timeout, any} | {:backoff, timeout, any, timeout | :hibernate} |
    192     {:stop, any, any}
    193 
    194   @doc """
    195   Called when the process should disconnect. The first argument will either
    196   be the `info` term from `{:disconnect, info, state}` or
    197   `{:disconnect, info, reply, state}`. This callback should do any cleaning up
    198   required to disconnect the connection and update the state of the process.
    199 
    200   Returning `{:connect, info, state}` will call `connect(info, state)`
    201   immediately - even if there are messages in the message queue.
    202 
    203   Returning `{:backoff, timeout, state}` or
    204   `{:backoff, timeout, state, timeout2 | :hibernate}` starts a backoff timer and
    205   behaves the same as when returned from `connect/2`. See the `connect/2`
    206   callback for more information.
    207 
    208   Returning `{:noconnect, state}` or `{:noconnect, state, timeout | :hibernate}`
    209   will cause the process to continue is loop (and NOT call `connect/2` to
    210   try to reconnect). In the later case a timeout is started or the process
    211   hibernates.
    212 
    213   Returning `{:stop, reason, state}` will terminate the loop and call
    214   `terminate(reason, state)` before the process exits with reason `reason`.
    215   """
    216   @callback disconnect(any, any) ::
    217     {:connect, any, any} |
    218     {:backoff, timeout, any} | {:backoff, timeout, any, timeout | :hibernate} |
    219     {:noconnect, any} | {:noconnect, any, timeout | :hibernate} |
    220     {:stop, any, any}
    221 
    222   @doc """
    223   Called when the process receives a call message sent by `call/3`. This
    224   callback has the same arguments as the `GenServer` equivalent and the
    225   `:reply`, `:noreply` and `:stop` return tuples behave the same. However
    226   there are a few additional return values:
    227 
    228   Returning `{:connect, info, reply, state}` will reply to the call with `reply`
    229   and immediately call `connect(info, state)`. Similarly for
    230   `{:disconnect, info, reply, state}`, except `disconnect/2` is called.
    231 
    232   Returning `{:connect, info, state}` or `{:disconnect, info, state}` will
    233   call the relevant callback immediately without replying to the call. This
    234   might be useful when the call should block until the process has connected,
    235   failed to connect or disconnected. The second argument passed to this callback
    236   can be included in the `info` or `state` terms and a reply sent in the next
    237   or a later callback using `reply/2`.
    238   """
    239   @callback handle_call(any, {pid, any}, any) ::
    240     {:reply, any, any} | {:reply, any, any, timeout | :hibernate} |
    241     {:noreply, any} | {:noreply, any, timeout | :hibernate} |
    242     {:disconnect | :connect, any, any, any} |
    243     {:disconnect | :connect, any, any} |
    244     {:stop, any, any} | {:stop, any, any, any}
    245 
    246   @doc """
    247   Called when the process receives a cast message sent by `cast/3`. This
    248   callback has the same arguments as the `GenServer` equivalent and the
    249   `:noreply` and `:stop` return tuples behave the same. However
    250   there are two additional return values:
    251 
    252   Returning `{:connect, info, state}` will immediately call
    253   `connect(info, state)`. Similarly for `{:disconnect, info, state}`,
    254   except `disconnect/2` is called.
    255   """
    256   @callback handle_cast(any, any) ::
    257     {:noreply, any} | {:noreply, any, timeout | :hibernate} |
    258     {:disconnect | :connect, any, any} |
    259     {:stop, any, any}
    260 
    261   @doc """
    262   Called when the process receives a message that is not a call or cast. This
    263   callback has the same arguments as the `GenServer` equivalent and the `:noreply`
    264   and `:stop` return tuples behave the same. However there are two additional
    265   return values:
    266 
    267   Returning `{:connect, info, state}` will immediately call
    268   `connect(info, state)`. Similarly for `{:disconnect, info, state}`,
    269   except `disconnect/2` is called.
    270   """
    271   @callback handle_info(any, any) ::
    272     {:noreply, any} | {:noreply, any, timeout | :hibernate} |
    273     {:disconnect | :connect, any, any} |
    274     {:stop, any, any}
    275 
    276   @doc """
    277   This callback is the same as the `GenServer` equivalent and is used to change
    278   the state when loading a different version of the callback module.
    279   """
    280   @callback code_change(any, any, any) :: {:ok, any}
    281 
    282   @doc """
    283   This callback is the same as the `GenServer` equivalent and is called when the
    284   process terminates. The first argument is the reason the process is about
    285   to exit with.
    286   """
    287   @callback terminate(any, any) :: any
    288 
    289   defmacro __using__(_) do
    290     quote location: :keep do
    291       @behaviour Connection
    292 
    293       # The default implementations of init/1, handle_call/3, handle_info/2,
    294       # handle_cast/2, terminate/2 and code_change/3 have been taken verbatim
    295       # from Elixir's GenServer default implementation.
    296 
    297       @doc false
    298       def init(args) do
    299         {:ok, args}
    300       end
    301 
    302       @doc false
    303       def handle_call(msg, _from, state) do
    304         # We do this to trick dialyzer to not complain about non-local returns.
    305         reason = {:bad_call, msg}
    306         case :erlang.phash2(1, 1) do
    307           0 -> exit(reason)
    308           1 -> {:stop, reason, state}
    309         end
    310       end
    311 
    312       @doc false
    313       def handle_info(_msg, state) do
    314         {:noreply, state}
    315       end
    316 
    317       @doc false
    318       def handle_cast(msg, state) do
    319         # We do this to trick dialyzer to not complain about non-local returns.
    320         reason = {:bad_cast, msg}
    321         case :erlang.phash2(1, 1) do
    322           0 -> exit(reason)
    323           1 -> {:stop, reason, state}
    324         end
    325       end
    326 
    327       @doc false
    328       def terminate(_reason, _state) do
    329         :ok
    330       end
    331 
    332       @doc false
    333       def code_change(_old, state, _extra) do
    334         {:ok, state}
    335       end
    336 
    337       @doc false
    338       def connect(info, state) do
    339         reason = {:bad_connect, info}
    340         case :erlang.phash2(1, 1) do
    341           0 -> exit(reason)
    342           1 -> {:stop, reason, state}
    343         end
    344       end
    345 
    346       @doc false
    347       def disconnect(info, state) do
    348         reason = {:bad_disconnect, info}
    349         case :erlang.phash2(1, 1) do
    350           0 -> exit(reason)
    351           1 -> {:stop, reason, state}
    352         end
    353       end
    354 
    355       defoverridable [init: 1, handle_call: 3, handle_info: 2,
    356                       handle_cast: 2, terminate: 2, code_change: 3,
    357                       connect: 2, disconnect: 2]
    358     end
    359   end
    360 
    361   @doc """
    362   Starts a `Connection` process linked to the current process.
    363 
    364   This function is used to start a `Connection` process in a supervision tree.
    365   The process will be started by calling `init/1` in the callback module with
    366   the given argument.
    367 
    368   This function will return after `init/1` has returned in the spawned process.
    369   The return values are controlled by the `init/1` callback.
    370 
    371   See `GenServer.start_link/3` for more information.
    372   """
    373   @spec start_link(module, any, GenServer.options) :: GenServer.on_start
    374   def start_link(mod, args, opts \\ []) do
    375     start(mod, args, opts, :link)
    376   end
    377 
    378   @doc """
    379   Starts a `Connection` process without links (outside of a supervision tree).
    380 
    381   See `start_link/3` for more information.
    382   """
    383   @spec start(module, any, GenServer.options) :: GenServer.on_start
    384   def start(mod, args, opts \\ []) do
    385     start(mod, args, opts, :nolink)
    386   end
    387 
    388   @doc """
    389   Sends a synchronous call to the `Connection` process and waits for a reply.
    390 
    391   See `GenServer.call/2` for more information.
    392   """
    393   defdelegate call(conn, req), to: :gen_server
    394 
    395   @doc """
    396   Sends a synchronous request to the `Connection` process and waits for a reply.
    397 
    398   See `GenServer.call/3` for more information.
    399   """
    400   defdelegate call(conn, req, timeout), to: :gen_server
    401 
    402   @doc """
    403   Sends a asynchronous request to the `Connection` process.
    404 
    405   See `GenServer.cast/2` for more information.
    406   """
    407   defdelegate cast(conn, req), to: GenServer
    408 
    409   @doc """
    410   Sends a reply to a request sent by `call/3`.
    411 
    412   See `GenServer.reply/2` for more information.
    413   """
    414   defdelegate reply(from, response), to: :gen_server
    415 
    416   defstruct [:mod, :backoff, :raise, :mod_state]
    417 
    418   ## :gen callback
    419 
    420   @doc false
    421   def init_it(starter, _, name, mod, args, opts) do
    422     Process.put(:"$initial_call", {mod, :init, 1})
    423     try do
    424       apply(mod, :init, [args])
    425     catch
    426       :exit, reason ->
    427         init_stop(starter, name, reason)
    428       :error, reason ->
    429         init_stop(starter, name, {reason, __STACKTRACE__})
    430       :throw, value ->
    431         reason = {{:nocatch, value}, __STACKTRACE__}
    432         init_stop(starter, name, reason)
    433     else
    434       {:ok, mod_state} ->
    435         :proc_lib.init_ack(starter, {:ok, self()})
    436         enter_loop(mod, nil, mod_state, name, opts, :infinity)
    437       {:ok, mod_state, timeout} ->
    438         :proc_lib.init_ack(starter, {:ok, self()})
    439         enter_loop(mod, nil, mod_state, name, opts, timeout)
    440       {:connect, info, mod_state} ->
    441         :proc_lib.init_ack(starter, {:ok, self()})
    442         enter_connect(mod, info, mod_state, name, opts)
    443       {:backoff, backoff_timeout, mod_state} ->
    444         backoff = start_backoff(backoff_timeout)
    445         :proc_lib.init_ack(starter, {:ok, self()})
    446         enter_loop(mod, backoff, mod_state, name, opts, :infinity)
    447       {:backoff, backoff_timeout, mod_state, timeout} ->
    448         backoff = start_backoff(backoff_timeout)
    449         :proc_lib.init_ack(starter, {:ok, self()})
    450         enter_loop(mod, backoff, mod_state, name, opts, timeout)
    451       :ignore ->
    452         _ = unregister(name)
    453         :proc_lib.init_ack(starter, :ignore)
    454         exit(:normal)
    455       {:stop, reason} ->
    456         init_stop(starter, name, reason)
    457       other ->
    458         init_stop(starter, name, {:bad_return_value, other})
    459     end
    460   end
    461   ## :proc_lib callback
    462 
    463   @doc false
    464   def enter_loop(mod, backoff, mod_state, name, opts, :hibernate) do
    465     args = [mod, backoff, mod_state, name, opts, :infinity]
    466     :proc_lib.hibernate(__MODULE__, :enter_loop, args)
    467   end
    468   def enter_loop(mod, backoff, mod_state, name, opts, timeout)
    469   when name === self() do
    470     s = %Connection{mod: mod, backoff: backoff, mod_state: mod_state,
    471                     raise: nil}
    472     :gen_server.enter_loop(__MODULE__, opts, s, timeout)
    473   end
    474   def enter_loop(mod, backoff, mod_state, name, opts, timeout) do
    475     s = %Connection{mod: mod, backoff: backoff, mod_state: mod_state,
    476                     raise: nil}
    477     :gen_server.enter_loop(__MODULE__, opts, s, name, timeout)
    478   end
    479 
    480   @doc false
    481   def init(_) do
    482     {:stop, __MODULE__}
    483   end
    484 
    485   @doc false
    486   def handle_call(request, from, %{mod: mod, mod_state: mod_state} = s) do
    487     try do
    488       apply(mod, :handle_call, [request, from, mod_state])
    489     catch
    490       :throw, value ->
    491         :erlang.raise(:error, {:nocatch, value}, __STACKTRACE__)
    492     else
    493       {:noreply, mod_state} = noreply ->
    494         put_elem(noreply, 1, %{s | mod_state: mod_state})
    495       {:noreply, mod_state, _} = noreply ->
    496         put_elem(noreply, 1, %{s | mod_state: mod_state})
    497       {:reply, _, mod_state} = reply ->
    498         put_elem(reply, 2, %{s | mod_state: mod_state})
    499       {:reply, _, mod_state, _} = reply ->
    500         put_elem(reply, 2, %{s | mod_state: mod_state})
    501       {:connect, info, mod_state} ->
    502         connect(info, mod_state, s)
    503       {:connect, info, reply, mod_state} ->
    504         reply(from, reply)
    505         connect(info, mod_state, s)
    506       {:disconnect, info, mod_state} ->
    507         disconnect(info, mod_state, s)
    508       {:disconnect, info, reply, mod_state} ->
    509         reply(from, reply)
    510         disconnect(info, mod_state, s)
    511       {:stop, _, mod_state} = stop ->
    512         put_elem(stop, 2, %{s | mod_state: mod_state})
    513       {:stop, _, _, mod_state} = stop ->
    514         put_elem(stop, 3, %{s | mod_state: mod_state})
    515       other ->
    516         {:stop, {:bad_return_value, other}, %{s | mod_state: mod_state}}
    517     end
    518   end
    519 
    520   @doc false
    521   def handle_cast(request, s) do
    522     handle_async(:handle_cast, request, s)
    523   end
    524 
    525   @doc false
    526   def handle_info({:timeout, backoff, __MODULE__},
    527   %{backoff: backoff, mod_state: mod_state} = s) do
    528     connect(:backoff, mod_state, %{s | backoff: nil})
    529   end
    530   def handle_info(msg, s) do
    531     handle_async(:handle_info, msg, s)
    532   end
    533 
    534   @doc false
    535   def code_change(old_vsn, %{mod: mod, mod_state: mod_state} = s, extra) do
    536     try do
    537       apply(mod, :code_change, [old_vsn, mod_state, extra])
    538     catch
    539       :throw, value ->
    540         exit({{:nocatch, value}, __STACKTRACE__})
    541     else
    542       {:ok, mod_state} ->
    543         {:ok, %{s | mod_state: mod_state}}
    544     end
    545   end
    546 
    547   @doc false
    548   def format_status(:normal, [pdict, %{mod: mod, mod_state: mod_state}]) do
    549     try do
    550       apply(mod, :format_status, [:normal, [pdict, mod_state]])
    551     catch
    552       _, _ ->
    553         [{:data, [{'State', mod_state}]}]
    554     else
    555       mod_status ->
    556         mod_status
    557     end
    558   end
    559   def format_status(:terminate, [pdict, %{mod: mod, mod_state: mod_state}]) do
    560     try do
    561       apply(mod, :format_status, [:terminate, [pdict, mod_state]])
    562     catch
    563       _, _ ->
    564         mod_state
    565     else
    566       mod_state ->
    567         mod_state
    568     end
    569   end
    570 
    571   @doc false
    572   def terminate(reason, %{mod: mod, mod_state: mod_state, raise: nil}) do
    573     apply(mod, :terminate, [reason, mod_state])
    574   end
    575   def terminate(stop, %{raise: {class, reason, stack}} = s) do
    576     %{mod: mod, mod_state: mod_state} = s
    577     try do
    578       apply(mod, :terminate, [stop, mod_state])
    579     catch
    580       :throw, value ->
    581         :erlang.raise(:error, {:nocatch, value}, __STACKTRACE__)
    582     else
    583       _ when stop in [:normal, :shutdown] ->
    584         :ok
    585       _ when tuple_size(stop) == 2 and elem(stop, 0) == :shutdown ->
    586         :ok
    587       _ ->
    588         :erlang.raise(class, reason, stack)
    589     end
    590   end
    591 
    592   # start helpers
    593 
    594   defp start(mod, args, options, link) do
    595     case Keyword.pop(options, :name) do
    596       {nil, opts} ->
    597         :gen.start(__MODULE__, link, mod, args, opts)
    598       {atom, opts} when is_atom(atom) ->
    599         :gen.start(__MODULE__, link, {:local, atom}, mod, args, opts)
    600       {{:global, _} = name, opts} ->
    601         :gen.start(__MODULE__, link, name, mod, args, opts)
    602       {{:via, _, _} = name, opts} ->
    603         :gen.start(__MODULE__, link, name, mod, args, opts)
    604     end
    605   end
    606 
    607   # init helpers
    608 
    609   defp init_stop(starter, name, reason) do
    610     _ = unregister(name)
    611     :proc_lib.init_ack(starter, {:error, reason})
    612     exit(reason)
    613   end
    614 
    615   defp unregister(name) when name === self(), do: :ok
    616   defp unregister({:local, name}), do: Process.unregister(name)
    617   defp unregister({:global, name}), do: :global.unregister_name(name)
    618   defp unregister({:via, mod, name}), do: apply(mod, :unregister_name, [name])
    619 
    620   defp enter_connect(mod, info, mod_state, name, opts) do
    621     try do
    622       apply(mod, :connect, [info, mod_state])
    623     catch
    624       :exit, reason ->
    625         report_reason = {:EXIT, {reason, __STACKTRACE__}}
    626         enter_terminate(mod, mod_state, name, reason, report_reason)
    627       :error, reason ->
    628         reason = {reason, __STACKTRACE__}
    629         enter_terminate(mod, mod_state, name, reason, {:EXIT, reason})
    630       :throw, value ->
    631         reason = {{:nocatch, value}, __STACKTRACE__}
    632         enter_terminate(mod, mod_state, name, reason, {:EXIT, reason})
    633     else
    634       {:ok, mod_state} ->
    635         enter_loop(mod, nil, mod_state, name, opts, :infinity)
    636       {:ok, mod_state, timeout} ->
    637         enter_loop(mod, nil, mod_state, name, opts, timeout)
    638       {:backoff, backoff_timeout, mod_state} ->
    639         backoff = start_backoff(backoff_timeout)
    640         enter_loop(mod, backoff, mod_state, name, opts, :infinity)
    641       {:backoff, backoff_timeout, mod_state, timeout} ->
    642         backoff = start_backoff(backoff_timeout)
    643         enter_loop(mod, backoff, mod_state, name, opts, timeout)
    644       {:stop, reason, mod_state} ->
    645         enter_terminate(mod, mod_state, name, reason, {:stop, reason})
    646       other ->
    647         reason = {:bad_return_value, other}
    648         enter_terminate(mod, mod_state, name, reason, {:stop, reason})
    649     end
    650   end
    651 
    652   defp enter_terminate(mod, mod_state, name, reason, report_reason) do
    653     try do
    654       apply(mod, :terminate, [reason, mod_state])
    655     catch
    656       :exit, reason ->
    657         report_reason = {:EXIT, {reason, __STACKTRACE__}}
    658         enter_stop(mod, mod_state, name, reason, report_reason)
    659       :error, reason ->
    660         reason = {reason, __STACKTRACE__}
    661         enter_stop(mod, mod_state, name, reason, {:EXIT, reason})
    662       :throw, value ->
    663         reason = {{:nocatch, value}, __STACKTRACE__}
    664         enter_stop(mod, mod_state, name, reason, {:EXIT, reason})
    665     else
    666       _ ->
    667         enter_stop(mod, mod_state, name, reason, report_reason)
    668     end
    669   end
    670 
    671   defp enter_stop(_, _, _, :normal, {:stop, :normal}), do: exit(:normal)
    672   defp enter_stop(_, _, _, :shutdown, {:stop, :shutdown}), do: exit(:shutdown)
    673   defp enter_stop(_, _, _, {:shutdown, reason} = shutdown,
    674   {:stop, {:shutdown, reason}}) do
    675       exit(shutdown)
    676   end
    677   defp enter_stop(mod, mod_state, name, reason, {_, reason2}) do
    678     s = %{mod: mod, backoff: nil, mod_state: mod_state}
    679     mod_state = format_status(:terminate, [Process.get(), s])
    680     format = '** Generic server ~p terminating \n' ++
    681       '** Last message in was ~p~n' ++ ## No last message
    682       '** When Server state == ~p~n' ++
    683       '** Reason for termination == ~n** ~p~n'
    684     args = [report_name(name), nil, mod_state, report_reason(reason2)]
    685     :error_logger.format(format, args)
    686     exit(reason)
    687   end
    688 
    689   defp report_name(name) when name === self(), do: name
    690   defp report_name({:local, name}), do: name
    691   defp report_name({:global, name}), do: name
    692   defp report_name({:via, _, name}), do: name
    693 
    694   defp report_reason({:undef, [{mod, fun, args, _} | _] = stack} = reason) do
    695     cond do
    696       :code.is_loaded(mod) === false ->
    697         {:"module could not be loaded", stack}
    698       not function_exported?(mod, fun, length(args)) ->
    699         {:"function not exported", stack}
    700       true ->
    701         reason
    702     end
    703   end
    704   defp report_reason(reason) do
    705     reason
    706   end
    707 
    708   ## backoff helpers
    709 
    710   defp start_backoff(:infinity), do: nil
    711   defp start_backoff(timeout) do
    712     :erlang.start_timer(timeout, self(), __MODULE__)
    713   end
    714 
    715   defp cancel_backoff(%{backoff: nil} = s), do: s
    716   defp cancel_backoff(%{backoff: backoff} = s) do
    717     case :erlang.cancel_timer(backoff) do
    718       false ->
    719         flush_backoff(backoff)
    720       _ ->
    721         :ok
    722     end
    723     %{s | backoff: nil}
    724   end
    725 
    726   defp flush_backoff(backoff) do
    727     receive do
    728       {:timeout, ^backoff, __MODULE__} ->
    729         :ok
    730     after
    731       0 ->
    732         :ok
    733     end
    734   end
    735 
    736   ## GenServer helpers
    737 
    738   defp connect(info, mod_state, %{mod: mod} = s) do
    739     s = cancel_backoff(s)
    740     try do
    741       apply(mod, :connect, [info, mod_state])
    742     catch
    743       class, reason ->
    744         stack = __STACKTRACE__
    745         callback_stop(class, reason, stack, %{s | mod_state: mod_state})
    746     else
    747       {:ok, mod_state} ->
    748         {:noreply, %{s | mod_state: mod_state}}
    749       {:ok, mod_state, timeout} ->
    750         {:noreply, %{s | mod_state: mod_state}, timeout}
    751       {:backoff, backoff_timeout, mod_state} ->
    752         backoff = start_backoff(backoff_timeout)
    753         {:noreply, %{s | backoff: backoff, mod_state: mod_state}}
    754       {:backoff, backoff_timeout, mod_state, timeout} ->
    755         backoff = start_backoff(backoff_timeout)
    756         {:noreply, %{s | backoff: backoff, mod_state: mod_state}, timeout}
    757       {:stop, _, mod_state} = stop ->
    758         put_elem(stop, 2, %{s | mod_state: mod_state})
    759       other ->
    760         {:stop, {:bad_return_value, other}, %{s | mod_state: mod_state}}
    761     end
    762   end
    763 
    764   defp disconnect(info, mod_state, %{mod: mod} = s) do
    765     s = cancel_backoff(s)
    766     try do
    767       apply(mod, :disconnect, [info, mod_state])
    768     catch
    769       class, reason ->
    770         stack = __STACKTRACE__
    771         callback_stop(class, reason, stack, %{s | mod_state: mod_state})
    772     else
    773       {:connect, info, mod_state} ->
    774         connect(info, mod_state, s)
    775       {:noconnect, mod_state} ->
    776         {:noreply, %{s | mod_state: mod_state}}
    777       {:noconnect, mod_state, timeout} ->
    778         {:noreply, %{s | mod_state: mod_state}, timeout}
    779       {:backoff, backoff_timeout, mod_state} ->
    780         backoff = start_backoff(backoff_timeout)
    781         {:noreply, %{s | backoff: backoff, mod_state: mod_state}}
    782       {:backoff, backoff_timeout, mod_state, timeout} ->
    783         backoff = start_backoff(backoff_timeout)
    784         {:noreply, %{s | backoff: backoff, mod_state: mod_state}, timeout}
    785       {:stop, _, mod_state} = stop ->
    786         put_elem(stop, 2, %{s | mod_state: mod_state})
    787       other ->
    788         {:stop, {:bad_return_value, other}, %{s | mod_state: mod_state}}
    789     end
    790   end
    791 
    792   # In order to have new mod_state in terminate/2 must return the exit reason.
    793   # However to get the correct GenServer report (exit with stacktrace),
    794   # include stacktrace in reason and re-raise after calling mod.terminate/2 if
    795   # it does not raise.
    796 
    797   defp callback_stop(:throw, value, stack, s) do
    798     callback_stop(:error, {:nocatch, value}, stack, s)
    799   end
    800   defp callback_stop(class, reason, stack, s) do
    801     raise = {class, reason, stack}
    802     {:stop, stop_reason(class, reason, stack), %{s | raise: raise}}
    803   end
    804 
    805   defp stop_reason(:error, reason, stack), do: {reason, stack}
    806   defp stop_reason(:exit, reason, _),      do: reason
    807 
    808   defp handle_async(fun, msg, %{mod: mod, mod_state: mod_state} = s) do
    809     try do
    810       apply(mod, fun, [msg, mod_state])
    811     catch
    812       :throw, value ->
    813         :erlang.raise(:error, {:nocatch, value}, __STACKTRACE__)
    814     else
    815       {:noreply, mod_state} = noreply ->
    816         put_elem(noreply, 1, %{s | mod_state: mod_state})
    817       {:noreply, mod_state, _} = noreply ->
    818         put_elem(noreply, 1, %{s | mod_state: mod_state})
    819       {:connect, info, mod_state} ->
    820         connect(info, mod_state, s)
    821       {:disconnect, info, mod_state} ->
    822         disconnect(info, mod_state, s)
    823       {:stop, _, mod_state} = stop ->
    824         put_elem(stop, 2, %{s | mod_state: mod_state})
    825       other ->
    826         {:stop, {:bad_return_value, other}, %{s | mod_state: mod_state}}
    827     end
    828   end
    829 end