mirror of
https://github.com/team-alembic/ash_gen_server.git
synced 2024-09-20 05:12:59 +12:00
feat: add inactivity and maximum lifetime timeouts.
This commit is contained in:
parent
b28acaaa5b
commit
d510ca7d0b
3 changed files with 96 additions and 12 deletions
|
@ -1,9 +1,6 @@
|
||||||
%Doctor.Config{
|
%Doctor.Config{
|
||||||
ignore_modules: [
|
ignore_modules: [~r/^Inspect./],
|
||||||
Inspect.TimeTravel.Character,
|
ignore_paths: [~r/deps/],
|
||||||
Inspect.TimeTravel.Machine
|
|
||||||
],
|
|
||||||
ignore_paths: [],
|
|
||||||
min_module_doc_coverage: 40,
|
min_module_doc_coverage: 40,
|
||||||
min_module_spec_coverage: 0,
|
min_module_spec_coverage: 0,
|
||||||
min_overall_doc_coverage: 50,
|
min_overall_doc_coverage: 50,
|
||||||
|
|
|
@ -24,7 +24,36 @@ defmodule AshGenServer.DataLayer do
|
||||||
process in sequence.
|
process in sequence.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
use Ash.Dsl.Extension, transformers: [], sections: []
|
@gen_server %Ash.Dsl.Section{
|
||||||
|
name: :gen_server,
|
||||||
|
describe: """
|
||||||
|
Configuration for the underlying GenServer process.
|
||||||
|
""",
|
||||||
|
examples: [
|
||||||
|
"""
|
||||||
|
gen_server do
|
||||||
|
inactivity_timeout :timer.minutes(5)
|
||||||
|
maximum_lifetime :timer.minutes(120)
|
||||||
|
end
|
||||||
|
"""
|
||||||
|
],
|
||||||
|
schema: [
|
||||||
|
inactivity_timeout: [
|
||||||
|
type: :timeout,
|
||||||
|
default: :infinity,
|
||||||
|
doc:
|
||||||
|
"How long to wait before destroying the resource when it is inactive (in milliseconds). Defaults to `:infinity`."
|
||||||
|
],
|
||||||
|
maxmum_lifetime: [
|
||||||
|
type: :timeout,
|
||||||
|
default: :infinity,
|
||||||
|
doc:
|
||||||
|
"The maximum amount of time that the process can run (in milliseconds). Defaults to `:infinity`."
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
use Ash.Dsl.Extension, transformers: [], sections: [@gen_server]
|
||||||
alias Ash.{Actions, Api, Changeset, DataLayer, Filter, Resource, Sort}
|
alias Ash.{Actions, Api, Changeset, DataLayer, Filter, Resource, Sort}
|
||||||
alias AshGenServer.{Query, Registry, Server, Supervisor}
|
alias AshGenServer.{Query, Registry, Server, Supervisor}
|
||||||
@behaviour Ash.DataLayer
|
@behaviour Ash.DataLayer
|
||||||
|
|
|
@ -5,15 +5,20 @@ defmodule AshGenServer.Server do
|
||||||
This module is a `GenServer` which can create, read and update a single
|
This module is a `GenServer` which can create, read and update a single
|
||||||
resource stored within it's state by applying changesets.
|
resource stored within it's state by applying changesets.
|
||||||
"""
|
"""
|
||||||
defstruct ~w[primary_key resource record]a
|
defstruct ~w[primary_key resource record inactivity_timeout maximum_lifetime inactivity_timer lifetime_timer api]a
|
||||||
alias Ash.{Changeset, Resource}
|
alias Ash.{Changeset, Dsl.Extension, Resource}
|
||||||
alias AshGenServer.Registry
|
alias AshGenServer.Registry
|
||||||
use GenServer
|
use GenServer, restart: :transient
|
||||||
|
|
||||||
@type t :: %__MODULE__{
|
@type t :: %__MODULE__{
|
||||||
|
api: module,
|
||||||
primary_key: Registry.primary_key(),
|
primary_key: Registry.primary_key(),
|
||||||
resource: Resource.t(),
|
resource: Resource.t(),
|
||||||
record: Resource.record()
|
record: Resource.record(),
|
||||||
|
inactivity_timeout: pos_integer() | :infinity,
|
||||||
|
maximum_lifetime: pos_integer() | :infinity,
|
||||||
|
inactivity_timer: nil | reference,
|
||||||
|
lifetime_timer: nil | reference
|
||||||
}
|
}
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
|
@ -43,7 +48,22 @@ defmodule AshGenServer.Server do
|
||||||
with {:ok, _self} <- Registry.register({resource, primary_key}),
|
with {:ok, _self} <- Registry.register({resource, primary_key}),
|
||||||
{:ok, record} <- Changeset.apply_attributes(changeset) do
|
{:ok, record} <- Changeset.apply_attributes(changeset) do
|
||||||
record = unload_relationships(resource, record)
|
record = unload_relationships(resource, record)
|
||||||
{:ok, %__MODULE__{primary_key: primary_key, resource: resource, record: record}}
|
inactivity_timeout = get_config(resource, :inactivity_timeout, :infinity)
|
||||||
|
maximum_lifetime = get_config(resource, :maximum_lifetime, :infinity)
|
||||||
|
|
||||||
|
state =
|
||||||
|
%__MODULE__{
|
||||||
|
api: changeset.api,
|
||||||
|
primary_key: primary_key,
|
||||||
|
resource: resource,
|
||||||
|
record: record,
|
||||||
|
inactivity_timeout: inactivity_timeout,
|
||||||
|
maximum_lifetime: maximum_lifetime
|
||||||
|
}
|
||||||
|
|> maybe_set_inactivity_timer()
|
||||||
|
|> maybe_set_lifetime_timer()
|
||||||
|
|
||||||
|
{:ok, state}
|
||||||
else
|
else
|
||||||
{:error, {:already_registered, _}} -> {:stop, :already_exists}
|
{:error, {:already_registered, _}} -> {:stop, :already_exists}
|
||||||
{:error, reason} -> {:error, reason}
|
{:error, reason} -> {:error, reason}
|
||||||
|
@ -54,9 +74,15 @@ defmodule AshGenServer.Server do
|
||||||
@impl true
|
@impl true
|
||||||
@spec handle_call(:get | {:update, Resource.t(), Changeset.t()}, GenServer.from(), t) ::
|
@spec handle_call(:get | {:update, Resource.t(), Changeset.t()}, GenServer.from(), t) ::
|
||||||
{:reply, {:ok, Resource.record()} | {:error, any}, t}
|
{:reply, {:ok, Resource.record()} | {:error, any}, t}
|
||||||
def handle_call(:get, _from, state), do: {:reply, {:ok, state.record}, state}
|
def handle_call(:get, _from, state) do
|
||||||
|
state = maybe_set_inactivity_timer(state)
|
||||||
|
|
||||||
|
{:reply, {:ok, state.record}, state}
|
||||||
|
end
|
||||||
|
|
||||||
def handle_call({:update, resource, changeset}, _from, state) when state.resource == resource do
|
def handle_call({:update, resource, changeset}, _from, state) when state.resource == resource do
|
||||||
|
state = maybe_set_inactivity_timer(state)
|
||||||
|
|
||||||
case Changeset.apply_attributes(changeset) do
|
case Changeset.apply_attributes(changeset) do
|
||||||
{:ok, new_record} ->
|
{:ok, new_record} ->
|
||||||
primary_key_fields = state.resource |> Resource.Info.primary_key()
|
primary_key_fields = state.resource |> Resource.Info.primary_key()
|
||||||
|
@ -81,6 +107,20 @@ defmodule AshGenServer.Server do
|
||||||
def handle_call({:update, resource, _changeset}, _from, state),
|
def handle_call({:update, resource, _changeset}, _from, state),
|
||||||
do: {:reply, {:error, {:incorrect_resource, resource}}, state}
|
do: {:reply, {:error, {:incorrect_resource, resource}}, state}
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
@impl true
|
||||||
|
@spec handle_info(:inactivity_timeout | :lifetime_timeout, t) ::
|
||||||
|
{:noreply, t()} | {:stop, :normal, t()}
|
||||||
|
def handle_info(msg, state) when msg in ~w[inactivity_timeout lifetime_timeout]a do
|
||||||
|
with %{name: action_name} <- Resource.Info.primary_action(state.resource, :destroy),
|
||||||
|
changeset <- Changeset.for_destroy(state.record, action_name),
|
||||||
|
:ok <- state.api.destroy(changeset, return_destroyed?: false) do
|
||||||
|
{:noreply, state}
|
||||||
|
else
|
||||||
|
_ -> {:stop, :normal, state}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp primary_key_from_resource_and_changeset(resource, changeset) do
|
defp primary_key_from_resource_and_changeset(resource, changeset) do
|
||||||
resource
|
resource
|
||||||
|> Resource.Info.primary_key()
|
|> Resource.Info.primary_key()
|
||||||
|
@ -96,4 +136,22 @@ defmodule AshGenServer.Server do
|
||||||
Map.put(record, relationship.name, Map.get(empty, relationship.name))
|
Map.put(record, relationship.name, Map.get(empty, relationship.name))
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp get_config(resource, attr, default),
|
||||||
|
do: Extension.get_opt(resource, [:gen_server], attr, default)
|
||||||
|
|
||||||
|
defp maybe_set_inactivity_timer(%{inactivity_timeout: :infinity} = state), do: state
|
||||||
|
|
||||||
|
defp maybe_set_inactivity_timer(%{inactivity_timeout: ttl, inactivity_timer: nil} = state),
|
||||||
|
do: %{state | inactivity_timer: Process.send_after(self(), :inactivity_timeout, ttl)}
|
||||||
|
|
||||||
|
defp maybe_set_inactivity_timer(%{inactivity_timer: timer} = state) do
|
||||||
|
Process.cancel_timer(timer)
|
||||||
|
maybe_set_inactivity_timer(%{state | inactivity_timer: nil})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_set_lifetime_timer(%{maximum_lifetime: :infinity} = state), do: state
|
||||||
|
|
||||||
|
defp maybe_set_lifetime_timer(%{maximum_lifetime: ttl} = state),
|
||||||
|
do: %{state | lifetime_timer: Process.send_after(self(), :lifetime_timeout, ttl)}
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue