Hook reference
v0.1.16
Reference for MishkaInstaller.Event.Hook, the module that turns a module into a plugin and that calls events. It covers the options of use, the callbacks a plugin can define, the functions the macro adds, the functions of the module, the arguments of Hook.call/3, and the shapes of results and reports. For the same material as a task, see Write a plugin for an event, Call an event and use its result and Override what a plugin does on start, restart and stop.
Options of use
use MishkaInstaller.Event.Hook, opts takes a keyword list.
| Option | Default | Meaning |
|---|---|---|
event | none | The event name, a string. Compilation fails with should be dedicated to an event. if it is missing. |
initial | %{} | A map of registration fields: priority (integer, 0 to 100, default 100), depends (list of plugin modules, default []) and extra (list of maps, default []). Applied again on every boot. |
queue | true | With true, the lifecycle functions queue the rebuild of the event and return before it finishes. With false, they return after it. |
checking | 1000 | Milliseconds between the plugin process's copies of its status from its event's compiled chain, which get/0 reports. |
Callbacks
| Callback | Default | Meaning |
|---|---|---|
call(state) | none | Runs the plugin. Return {:reply, state} to continue or {:reply, :halt, state} to end the chain. |
call(state, meta) | none | The same, with the read-only meta of the call, or nil. When a plugin defines both, call/2 is used. A plugin must define call/1 or call/2, or compilation fails with should define a call/1 or call/2 function. |
health_check() | :ok | Returns :ok, {:degraded, reason} or {:error, reason}. |
on_dependency_error(error) | returns error | Runs when the plugin's registration at boot returns an error, such as a dependency cycle or a priority out of range. |
register(), start(), restart(), stop(), enable(), unregister(), get() | see below | Defined by the macro, and can be overridden. See Overriding the generated functions. |
Functions added to a plugin
| Function | Returns | Meaning |
|---|---|---|
config/0 | keyword list | The plugin's options: :__plugin__, :__event__, :__initial__, :__queue__ and :__checking__, merged with the values of :app, :version, :elixir, :deps, :name, :source_url, :homepage_url and :description from the Mix project at compile time that can be stored as plain data. |
config/1 | value | One key of config/0. |
register/0 | {:ok, record} or an error | Stores the plugin. See Event.register/3. |
start/0 | {:ok, record} or an error | Writes :started. |
restart/0 | {:ok, record} or an error | Writes :restarted. Refuses a stopped plugin. |
stop/0 | {:ok, record} or an error | Writes :stopped. |
enable/0 | {:ok, record} or an error | Switches a stopped plugin back on. |
unregister/0 | {:ok, record} or an error | Deletes the record and stops the process. |
get/0 | keyword list | The process's state: :name, :event and :status, and :depends once the plugin has registered. Right after the process starts it is [name: module, event: event, status: :starting] with no :depends. Calls the process, so it fails when the process is not running. |
health_check/0, on_dependency_error/1 | see callbacks | Defaults, overridable. |
start_link/1 | GenServer.on_start() | Starts the process under the module's name. The argument defaults to []. |
child_spec/1 | child spec | From use GenServer with restart: :transient. |
The macro also defines init/1, handle_continue/2, handle_call/3 and handle_info/2. Errors have the shape {:error, [%{action: atom(), field: atom(), message: String.t()}]}.
Overriding the generated functions
A plugin module can define any of these nine functions again, and its version can call the default with super. on_dependency_error/1 takes an argument, so its override calls super(error). The override documentation has a sample for each. All nine can be overridden in 0.1.13 and 0.1.14.
| Function | Default | Who calls it | An override returns | Overridable since |
|---|---|---|---|---|
register/0 | Event.register/3 with the plugin's event and initial | The plugin's process, when it starts. | {:ok, record} or an error. Any other answer counts as a failed registration. | 0.1.0 |
start/0 | Event.start/3 with the plugin's queue option | The plugin's process, when register/0 found a :registered plugin, and on its retry. | {:ok, record} or an error. The process reads status and depends from the record. | 0.1.0 |
restart/0 | Event.restart/3 | Your code only. | {:ok, record} or an error. | 0.1.0 |
stop/0 | Event.stop/3 | Your code only. | {:ok, record} or an error. | 0.1.0 |
enable/0 | Event.enable/2 | Your code only. | {:ok, record} or an error. | 0.1.11 |
unregister/0 | Event.unregister/3 | Your code only. | {:ok, record} or an error. | 0.1.0 |
get/0 | A call to the plugin's process | Your code only. | A keyword list. | 0.1.0 |
health_check/0 | Returns :ok | Hook.run_health/2, which plugin_health/2, event_health/2 and health/1 use. | :ok, {:degraded, reason} or {:error, reason}. Any other value is reported as {:error, {:bad_return, value}}. | 0.1.6 |
on_dependency_error/1 | Returns its argument | The plugin's process, when register/0 at start returns anything but {:ok, record}. | Any value. The library ignores it. | 0.1.6 |
The functions of Event do not call these. A call such as Event.stop(:name, plugin, queue), the start of every event on boot and the hold of a plugin whose dependency stopped change the record without running an override.
A plugin's process calls register/0 and start/0 when it starts. If the library has not finished its boot by then, the process tries once more, about one second later, without checking readiness again, and nothing schedules another try. The retry calls register/0 for a plugin with no record, start/0 for a record that the compiled event does not hold, and neither otherwise.
Functions of the Hook module
| Function | Returns | Meaning |
|---|---|---|
call(event, data, args \\ []) | the chain's result, or data | Calls the event. Returns data when the event has no compiled module. Raises ArgumentError when args is not a list. |
call!(event, data, args \\ []) | the chain's result | Skips the check for a compiled module. Raises UndefinedFunctionError for an event never compiled, and ArgumentError when args is not a list. |
profile(event, state) | {:ok, [%{plugin: module, microseconds: n}]} or {:error, :not_compiled} | Runs each plugin of the chain once and times it. A plugin that raises reports 0. Ignores :halt. |
plugin_health(plugin, timeout \\ 1000) | report map | Health of one plugin. |
event_health(event, timeout \\ 1000) | report map | Health of one event. The plugin probes run concurrently. |
health(timeout \\ 1000) | list of event reports | Health of every event in the registry. |
run_health(plugin, timeout \\ 1000) | probe result | Runs health_check/0 in a monitored process, bounded by timeout. |
Arguments of call
| Key | Meaning |
|---|---|
private | A map or a keyword list, merged into a map or keyword list result after the chain, also inside {:ok, _}. Plugins never see it. Anything else raises an ArgumentError that names the option. |
return | true makes the call return data as given. The plugins still run. false, or no option, returns the result of the plugins. Any other value raises an ArgumentError that names the option. Before 0.1.16 every value but nil acted as true. |
meta | Handed to the plugins that define call/2. Never merged into the result. nil when absent. |
cannot_run | The answer when the event cannot run, meaning a plugin of it is started but not loaded. Without it, the answer is data. An event whose plugins all run ignores it. Before 0.1.13 the answer was an error tuple, and the changes documentation shows how to update code that matched it. |
raised | The answer when a plugin of the event raises or replies with something other than {:reply, state} or {:reply, :halt, state}. The plugins after it do not run. Without it, the answer is data. It wins over return and private, and a call whose plugins all reply ignores it. An exit or a throw from a plugin is not caught. |
What a plugin may return
| Return value | Effect |
|---|---|
{:reply, state} | The next plugin receives state. After the last plugin, state is the chain's result. |
{:reply, :halt, state} | The chain ends and state is its result. |
| Anything else, or an exception | The chain ends and the plugins after it do not run. The library logs a plugin of event ... raised: with the stacktrace, and the call returns data, as it was given, or the value of raised. |
How the final state is shaped
These rules apply to the final state s when the call has no return: true. private is a map or a keyword list and merges into either shape, and a map merged into a keyword list needs atom keys.
| Final state | With private | Without |
|---|---|---|
{:ok, keyword_list} | {:ok, Keyword.merge(list, private)} | unchanged |
{:ok, list}, not a keyword list | unchanged | unchanged |
{:ok, map} | {:ok, Map.merge(map, private)} | unchanged |
{:ok, other} | unchanged | unchanged |
{:error, _} | unchanged | unchanged |
| a keyword list | Keyword.merge(s, private) | unchanged |
| a list that is not a keyword list | unchanged | unchanged |
| a map | Map.merge(s, private) | unchanged |
anything else, such as {:noreply, socket} | unchanged | unchanged |
Reports
A plugin report from plugin_health/2 has these keys:
| Key | Meaning |
|---|---|
plugin | The plugin module. |
status | The stored status, or nil without a record. |
alive? | Whether the plugin's process is registered under its module name. |
callable? | Whether the module is loaded and exports call/1 or call/2. |
probe | The result of health_check/0. |
healthy? | True when the process is alive, the module is callable, the probe is :ok and the status is :started or :restarted. |
An event report from event_health/2 has event, compiled?, mode (:ok, :error or :unknown), plugin_count, plugins (a list of plugin reports) and healthy?. An event is healthy when it is compiled, its mode is :ok and every plugin is healthy.
A probe result is one of:
| Probe | Cause |
|---|---|
:ok | The plugin reported itself healthy. |
{:degraded, reason} | The plugin returned it. |
{:error, reason} | The plugin returned it. |
{:error, :timeout} | The probe ran longer than the timeout and was killed. |
{:error, :no_health_check} | The module defines no health_check/0. |
{:error, {:exit, reason}} | The probe process exited. A probe that raised gives {exception, stacktrace} as the reason. Before 0.1.15 that was {:error, {:raised, exception}}. |
{:error, {:bad_return, value}} | The probe returned another value. |
Event names
The compiled module is MishkaInstaller.Event.ModuleStateCompiler.State.<Name>. The name is the event with surrounding whitespace removed, spaces replaced by underscores, leading digits removed, and the result camelized with Macro.camelize/1. A caller that still runs an older version while the event is rebuilt keeps it, and the new version is loaded into MishkaInstaller.Event.ModuleStateCompiler.State1.<Name> or the next free slot number. ModuleStateCompiler.event_module/1 returns the module that runs the event now.