Packages

A GenServer which pings itself on an interval

Current section

Files

Jump to
ex_ping_server lib ex_ping_server.ex
Raw

lib/ex_ping_server.ex

defmodule PingServer do
@moduledoc """
Documentation for PingServer.
A GenServer extension which receives a ping at a regular interval.
## Example usage
```
defmodule UsePing do
use PingServer, interval: 10_000
def start_link(thing) do
# You can start link this way. It will call PingServer.init(thing)
PingServer.start_link(__MODULE__, thing, [])
end
def handle_ping(state) do
# Even though the interval is 10 seconds, we can make it be every 1 second this way
PingServer.ping_after(self(), 1_000)
state
end
def handle_info(:manual_ping, state) do
# only one ping will happen but it will happen pretty much right away.
PingServer.ping(self())
PingServer.ping(self())
PingServer.ping(self())
{:noreply, state}
end
end
```
"""
defmacro __using__(opts) do
{interval, opts} = Keyword.pop(opts, :interval, 1_000)
if length(opts) > 0 do
raise "unknown options to PingServer: #{inspect opts}"
end
quote location: :keep do
def start_link(opts) do
GenServer.start_link(__MODULE__, opts)
end
def handle_info(:_ping_mixin_poll, state) do
# queue up another deferred ping by default
PingServer.ping_after(self(), unquote(interval))
state = apply(__MODULE__, :handle_ping, [state])
{:noreply, state}
end
def handle_info({:_ping_mixin_ping_after, delay}, state) do
Process.put(:_ping_mixin_delayed_ping, delay)
send(self(), :_ping_mixin_process_ping_requests)
{:noreply, state}
end
def handle_info(:_ping_mixin_ping, state) do
Process.put(:_ping_mixin_immediate_ping, true)
send(self(), :_ping_mixin_process_ping_requests)
{:noreply, state}
end
def handle_info(:_ping_mixin_process_ping_requests, state) do
immediate = Process.get(:_ping_mixin_immediate_ping, false)
delay = Process.get(:_ping_mixin_delayed_ping, 0)
Process.put(:_ping_mixin_immediate_ping, false)
Process.put(:_ping_mixin_delayed_ping, 0)
cond do
immediate ->
oldref = Process.get(:_ping_mixin_timer_ref)
if oldref, do: Process.cancel_timer(oldref)
Process.put(:_ping_mixin_timer_ref, nil)
send(self(), :_ping_mixin_poll)
delay > 0 ->
oldref = Process.get(:_ping_mixin_timer_ref)
current_delay =
if oldref do
oldref_delay = Process.read_timer(oldref)
cond do
oldref_delay == false -> :infinity
true -> oldref_delay
end
else
:infinity
end
if current_delay > delay do
if oldref, do: Process.cancel_timer(oldref)
ref = Process.send_after(self(), :_ping_mixin_poll, delay)
Process.put(:_ping_mixin_timer_ref, ref)
end
true -> nil
end
{:noreply, state}
end
def init(arg) do
PingServer.init(self())
{:ok, arg}
end
def start_link(arg) do
PingServer.start_link(__MODULE__, [arg], [])
end
defoverridable [init: 1, start_link: 1]
end
end
def start_link(module, args, opts) do
GenServer.start_link(module, args, opts)
end
def init(pid) do
PingServer.ping(pid)
end
def ping_after(pid, delay \\ 1_000) do
if delay < 1 do
send(pid, :_ping_mixin_ping)
else
send(pid, {:_ping_mixin_ping_after, delay})
end
:ok
end
def ping(pid) do
PingServer.ping_after(pid, 0)
end
end