defmodule MishkaInstaller.PluginETS do
@moduledoc """
## ETS part to optimization state instead of dynamic supervisor and Genserver
`public` — Read/Write available to all processes.
`protected` — Read available to all processes. Only writable by owner process. This is the default.
`private` — Read/Write limited to owner process.
#### Essential functions in ETS
1. :ets.new
```elixir
:ets.new(@tab, [:set, :named_table, :public, read_concurrency: true, write_concurrency: true])
```
2. :ets.insert
3. :ets.insert_new
4. :ets.lookup
5. :ets.match
6. :ets.match_object
7. :ets.select
8. :ets.fun2ms
9. :ets.delete
10. :dets.open_file
11. :ets.update_counter
12. :ets.delete_all_objects
13. :ets.tab2list - to spy on the data in the table
14. :ets.update_counter
15. :ets.all
16. :ets.info
### Refs
* https://www.erlang.org/doc/man/ets.html
* https://dockyard.com/blog/2017/05/19/optimizing-elixir-and-phoenix-with-ets
* https://learnyousomeerlang.com/ets
* https://elixir-lang.org/getting-started/mix-otp/ets.html
* https://elixirschool.com/en/lessons/storage/ets
* https://github.com/TheFirstAvenger/ets
"""
use GenServer
require Logger
@ets_table :plugin_ets_state
@sync_with_database 100_000
def start_link(args) do
GenServer.start_link(__MODULE__, args, name: __MODULE__)
end
def child_spec(process_name) do
%{
id: __MODULE__,
start: {__MODULE__, :start_link, [process_name]},
restart: :transient,
max_restarts: 4
}
end
@impl true
def init(_state) do
Logger.info("The ETS state of plugin was staretd")
Process.send_after(self(), :sync_with_database, @sync_with_database)
{:ok,
%{
set:
ETS.Set.new!(
name: @ets_table,
protection: :public,
read_concurrency: true,
write_concurrency: true
)
}}
end
@impl true
def terminate(_reason, _state) do
Logger.warn("Your ETS state of plugin was restarted by a problem")
sync_with_database()
end
@impl true
def handle_info(:sync_with_database, state) do
Logger.info("Plugin ETS state was synced with database")
Process.send_after(self(), :sync_with_database, @sync_with_database)
{:noreply, state}
end
def push(%MishkaInstaller.PluginState{name: name, event: event} = state) do
ETS.Set.put!(table(), {String.to_atom(name), event, state})
end
def get(module: name) do
case ETS.Set.get(table(), String.to_atom(name)) do
{:ok, {_key, _event, data}} -> data
_ -> {:error, :get, :not_found}
end
end
def get_all(event: event_name) do
ETS.Set.match!(table(), {:_, event_name, :"$3"})
|> Enum.map(&List.first/1)
end
def delete(module: module_name) do
ETS.Set.delete(table(), String.to_atom(module_name))
end
def delete(event: event_name) do
ETS.Set.match_delete(table(), {:_, event_name, :_})
end
defp table() do
case ETS.Set.wrap_existing(@ets_table) do
{:ok, set} ->
set
_ ->
start_link([])
table()
end
end
def sync_with_database() do
MishkaInstaller.Plugin.plugins()
|> Enum.reject(&(&1.status in [:stopped]))
|> Enum.map(&push/1)
end
end