Packages
ace
0.16.5
0.19.0
0.18.10
0.18.9
0.18.8
0.18.7
0.18.6
0.18.5
0.18.4
0.18.3
0.18.2
0.18.1
0.18.0
0.17.1
0.17.0
0.16.8
0.16.7
0.16.6
0.16.5
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.11
0.15.10
0.15.9
0.15.8
0.15.7
0.15.6
retired
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.8
0.14.7
0.14.6
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.13.1
0.13.0
0.12.1
0.12.0
retired
0.11.1
0.11.0
0.10.0
0.9.3
0.9.2
0.9.1
0.9.0
0.8.1
0.8.0
0.7.1
0.7.0
0.6.3
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.0
0.3.0
0.2.0
HTTP web server and client, supports http1 and http2
Current section
Files
Jump to
Current section
Files
lib/ace/governor.ex
defmodule Ace.Governor do
@moduledoc """
A governor maintains servers ready to handle clients.
A governor process starts with a reference to supervision that can start servers.
It will then wait until the server has accepted a connection.
Once it's server has accepted a connection the governor will start a new server.
"""
use GenServer
@enforce_keys [:server_supervisor, :listen_socket, :server, :monitor]
defstruct @enforce_keys
def start_link(server_supervisor, listen_socket) when is_pid(server_supervisor) do
initial_state = %{
server_supervisor: server_supervisor,
listen_socket: listen_socket,
server: nil,
monitor: nil
}
GenServer.start_link(__MODULE__, initial_state)
end
def child_spec({endpoint_supervisor, listen_socket}) do
# DEBT is module previously checked to implement Raxx.Application or Raxx.Server
%{
id: __MODULE__,
start: {__MODULE__, :start_link, [endpoint_supervisor, listen_socket]},
type: :worker,
restart: :transient,
shutdown: 500
}
end
@impl GenServer
def init(initial_state) do
new_state = start_server(initial_state)
{:ok, new_state}
end
@impl GenServer
# DEBT should match response are `{:ok, server}` or `{:error, reason}`
def handle_info({monitor, _response}, state = %{monitor: monitor, server: server}) do
true = Process.unlink(server)
true = Process.demonitor(monitor)
new_state = start_server(%{state | monitor: nil, server: nil})
{:noreply, new_state}
end
def handle_info({:DOWN, monitor, :process, _server, :normal}, state = %{monitor: monitor}) do
# Server process has terminated so existing references are irrelevant
new_state = start_server(%{state | monitor: nil, server: nil})
{:noreply, new_state}
end
# Messages from previously monitored process can arrive when the connection response quickly and exits normally.
def handle_info({:DOWN, _, :process, _, :normal}, state) do
{:noreply, state}
end
# function head ensures that only one server is being monitored at a time
defp start_server(state = %{server: nil, monitor: nil}) do
# Starting a server process must always succeed, before accepting on a connection it has no external influences.
{:ok, server} = Supervisor.start_child(state.server_supervisor, [])
# The behaviour of a server is to always after creation, therefore linking should always succeed.
true = Process.link(server)
# Creates a unique reference that we can also use to correlate the call to accept on a given socket.
monitor = Process.monitor(server)
# Simulate a `GenServer.call` but without blocking on a receive loop.
send(server, {:"$gen_call", {self(), monitor}, {:accept, state.listen_socket}})
%{state | monitor: monitor, server: server}
end
end