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