Last active
November 28, 2019 16:39
-
-
Save joshnuss/4282f2edccfd1f395091 to your computer and use it in GitHub Desktop.
A module for defining GenServer proxies
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# defines a generic proxy | |
defmodule GenProxy do | |
defmacro __using__(_) do | |
quote location: :keep do | |
use GenServer | |
def handle_call(msg, from={_process, ref}, state) do | |
case proxy_call(msg, from, state) do | |
{:forward, server, new_state} -> | |
:erlang.send(server, {:"$gen_call", {self, ref}, msg}, [:noconnect]) | |
receive do | |
{_, reply} -> | |
{:reply, reply, new_state} | |
other -> | |
{:reply, :error, new_state} | |
end | |
other -> other | |
end | |
end | |
def handle_cast(msg, state) do | |
case proxy_cast(msg, state) do | |
{:forward, server, new_state} -> | |
:erlang.send(server, {:"$gen_cast", msg}, [:noconnect]) | |
{:noreply, new_state} | |
other -> other | |
end | |
end | |
def handle_info({:DOWN, ref, :process, _from, reason}, state=%{ref: ref}) do | |
{:stop, reason, state} | |
end | |
def handle_info(msg, state) do | |
case proxy_info(msg, state) do | |
{:forward, server, new_state} -> | |
:erlang.send(server, msg, [:noconnect]) | |
{:noreply, new_state} | |
other -> other | |
end | |
end | |
def proxy_call(msg, _from, state) do | |
{:stop, {:bad_call, msg}, state} | |
end | |
def proxy_cast(msg, state) do | |
{:stop, {:bad_cast, msg}, state} | |
end | |
def proxy_info(msg, state) do | |
{:stop, {:bad_info, msg}, state} | |
end | |
defoverridable proxy_call: 3, proxy_cast: 2, proxy_info: 2 | |
end | |
end | |
end | |
# MathServer: a simple server that supports addition | |
defmodule MathServer do | |
use GenServer | |
def start_link(opts \\ []) do | |
name = Keyword.get(opts, :name) | |
GenServer.start_link(__MODULE__, name, opts) | |
end | |
def add(server, a, b) do | |
GenServer.call(server, {:add, a, b}) | |
end | |
def handle_call(msg={:add, a, b}, _from, name) do | |
IO.puts("#{name} got #{inspect msg}") | |
{:reply, a + b, name} | |
end | |
end | |
# defines a proxy that delegates messages to an underlying server (i.e. MathServer) | |
# implementing a GenProxy is just like a GenServer except you handle proxy_call, proxy_cast, proxy_info | |
# you can return anything GenServer supports, i.e. {:reply, response, new_state} | |
# OR you can return {:forward, pid, new_state} | |
defmodule MathProxy do | |
use GenProxy | |
def start_link(server, opts) do | |
GenServer.start_link(__MODULE__, %{server: server}, opts) | |
end | |
def init(%{server: server}) do | |
ref = Process.monitor(server) | |
{:ok, %{server: server, ref: ref}} | |
end | |
# handle {:add, 20, 25} in a special way | |
def proxy_call(msg={:add, 20, 25}, _from, state) do | |
IO.puts("proxy got #{inspect msg}") | |
{:reply, :eureka, state} | |
end | |
# send all other messages to underlying server | |
def proxy_call(_msg, _from, state=%{server: server}) do | |
{:forward, server, state} | |
end | |
end | |
defmodule LoadBalancer.Random do | |
use GenProxy | |
def start_link(servers, opts) do | |
GenServer.start_link(__MODULE__, %{servers: servers}, opts) | |
end | |
def proxy_call(_msg, _from, state=%{servers: servers}) do | |
{:forward, random_server(servers), state} | |
end | |
defp random_server(servers) do | |
servers |> Enum.shuffle |> List.first | |
end | |
end | |
# start up a math server with the names :math1 & :math2 | |
MathServer.start_link(name: :math1) | |
MathServer.start_link(name: :math2) | |
# start a "MathProxy" called :math_proxy for the server named :math1 | |
# it will proxy all messages except {:add, 20, 25} | |
MathProxy.start_link(:math1, name: :math_proxy) | |
# Both these calls result in 15 | |
IO.puts MathServer.add(:math1, 10, 5) # direct call, returns 15 | |
IO.puts MathServer.add(:math_proxy, 10, 5) # proxied call, returns 15 | |
# The message {:add, 20, 25} is handled by the proxy and returns :eureka | |
IO.puts MathServer.add(:math_proxy, 20, 25) # returns :eureka | |
# start a random load balancer for the :math_proxy and :math2 | |
LoadBalancer.Random.start_link [:math_proxy, :math2], name: :load_balancer | |
IO.puts MathServer.add(:load_balancer, 10, 5) # direct call, returns 15, executed on :math2 | |
IO.puts MathServer.add(:load_balancer, 10, 5) # direct call, returns 15, executed on :math1 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment