Event reference
v0.1.16
Reference for MishkaInstaller.Event.Event, the plugin registry, and for the modules that build event chains: MishkaInstaller.Event.EventHandler and MishkaInstaller.Event.ModuleStateCompiler. It lists the fields of a plugin record, every registry function, the errors they return, and the functions of a compiled event module. The plugin-side functions in Hook reference call these.
The plugin record
%MishkaInstaller.Event.Event{} is stored in a Mnesia table and built with Event.builder/1.
| Field | Type | Default | Rule |
|---|---|---|---|
id | string | generated | A version 4 UUID. |
name | module | none | Required. The plugin module. |
event | string | none | Required, not empty. |
priority | integer | 100 | 0 to 100. A smaller number runs first. |
status | atom | :registered | One of :registered, :started, :stopped, :restarted, :held. |
extension | atom | none | Required. The OTP application of the plugin. register/3 sets it from name.config(:app). |
depends | list | [] | Plugin modules this plugin needs. |
extra | list of maps | [] | Data stored with the plugin. The library does not read it. |
inserted_at | integer | now | Unix time in seconds. |
updated_at | integer | now | Unix time in seconds, set on every change. |
The table is indexed by name, event and extension. It uses disc copies outside the test environment and RAM copies in it.
Lifecycle functions
queue is a boolean. With true the rebuild of the event is queued and the function returns before it ends. With false it returns after the rebuild.
| Function | Returns | What it does |
|---|---|---|
register(name, event, initial) | {:ok, record} or an error | Stores a plugin. If the plugin already has a record, it updates priority, extra and depends, keeps id, event and inserted_at, and keeps status unless a changed dependency moves the plugin into or out of :held. It rebuilds the event only for such a changed record, never for a new plugin. |
start(:name, name, queue) | {:ok, record} or an error | Writes :started, rebuilds the event, and releases dependents. |
start(:event, event, queue) | {:ok, records} or an error | Starts every plugin of an event except stopped ones. A plugin with inactive dependencies becomes :held. Records are sorted by priority and name. |
start(queue \\ true) | {:ok, results}, or an error when the registry cannot be read | The same for every event in the registry. |
restart(:name, name, queue) | {:ok, record} or an error | Writes :restarted. Refuses a stopped plugin and a plugin with inactive dependencies. |
restart(:event, event, queue) | {:ok, records} or an error | Restarts every plugin of an event that can restart. Others are skipped. |
restart(queue \\ true) | {:ok, results}, or an error when the registry cannot be read | The same for every event. |
stop(:name, name, queue) | {:ok, record} or an error | Writes :stopped from any status except :stopped. Holds dependents. |
stop(:event, event, queue) | {:ok, records} or an error | Stops every plugin of an event that is not stopped. |
stop(queue \\ true) | {:ok, results}, or an error when the registry cannot be read | The same for every event. |
enable(name, queue \\ true) | {:ok, record} or an error | Writes :restarted, or :held when a dependency is inactive. The only way out of :stopped. |
unregister(:name, name, queue) | {:ok, record} or an error | Deletes the record, rebuilds the event, holds dependents, and stops the plugin's process if it runs. |
unregister(:event, event, queue) | {:ok, records} or an error | Unregisters every plugin of an event. |
unregister(queue \\ true) | {:ok, results}, or an error when the registry cannot be read | The same for every event. |
unregister_gone(queue \\ true) | list of records | Unregisters each plugin whose application is loaded, does not list the plugin's module, and whose module is not loaded either. Returns the records it removed. The event handler runs it each time it starts every event: on boot, after the libraries installed at runtime are loaded, and again whenever the Mnesia store announces that it is ready again. At no other time does anything run it. Calling it by hand after a library was upgraded or uninstalled at runtime finds nothing: an upgrade leaves the old module loaded, and an uninstall unloads the application, which the check reads as a library that is still on its way in. See the documentation on plugins in installed libraries for how to remove those plugins. |
Queries
| Function | Returns |
|---|---|
get() | Every record. |
get(id) | One record by id, or nil. |
get(:name, module) | One record, or nil. |
get(:event, event) | The records of an event, or []. |
get(:extension, app) | The records of an application, or []. |
dirty_get(:name, module) | The same read as get(:name, module). Since 0.1.16 every read of the registry (get/0, get/1, get/2 and group_events/1) is a dirty read: an ETS lookup on this node's copy of the table, with no lock. It sees each record as last committed, so a read never waits for a status write. |
ids() | Every id. |
group_events(key \\ [:event]) | {:ok, events}, the distinct event names. |
unique(field, value) | :ok when no record matches, else an error. Use it with :name. With :event or :extension it always answers the error, because get/2 returns a list for those fields and a list is never nil. |
unique?(field, value) | A boolean. The same rule applies: with :event or :extension it is always false. |
Writes
| Function | Returns | What it does |
|---|---|---|
write(map) | {:ok, record} or an error | Validates and stores a whole record. |
write(field, value, changes) | {:ok, record} or an error | Merges changes into the record found by :id or :name. It reads the row under a write lock, so two changes to different fields do not overwrite each other. It does not rebuild any event or touch dependents. |
delete(field, value) | {:ok, record} or an error | Deletes by :id or :name. |
drop() | {:ok, :atomic} or {:error, source, description} | Empties the table. It is the one function here whose error is not a list of maps. |
Status writes commit synchronously and flush the log, so they survive an unclean shutdown.
Dependency functions
| Function | Returns | What it does |
|---|---|---|
dependents(name) | list of records | Every plugin that lists name in depends, on any event. |
sync_dependents(names, queue \\ true) | list of events | Re-evaluates what depends on names, holds or releases it, rebuilds each event once, and returns the events rebuilt. Lifecycle functions call it. Call it yourself only after you change a status with write/3. |
no_dependency_cycle(name, depends) | :ok or an error | Checks that depends adds no cycle back to name. |
connections() | a libgraph Graph | One edge from each plugin to each dependency. |
dependency_order(plugins) | list | Sorts plugins so each follows its dependencies, ties by priority and name. |
Errors
Every error is {:error, [%{action: action, field: field, message: message}]}, except the one of drop/0.
action | field | Message |
|---|---|---|
:ensure_loaded | :global | This module is not loaded in the system. |
:register | :depends | Dependency cycle detected: <module> ultimately depends on itself. |
:exist_record? | :global | The ID of the record you want is incorrect or has already been deleted. |
:plugin_status | :global | This plugin is stopped, use `enable/2` to switch it back on. Or: This plugin is already stopped. |
:hold_statuses | :global | This plugin has dependencies that are not yet activated in the system |
:start_event, :restart_event, :stop_event, :unregister_event | :global | There are no plugins in the database that can be started, stopped or unregistered for this event. |
:write | :global | The ID of the record you want to update is incorrect or has already been deleted. |
:delete | :global | The ID of the record you want to delete is incorrect or has already been deleted. |
:unique | :global | This event already exists in the database. |
:database | :global | Unfortunately, there is a problem in reading, storing or deleting data in the database. The description of the Mnesia error follows, such as "Node not running". |
:compile | :event | The module of the event could not be compiled. Only ModuleStateCompiler.create/3 and purge_create/3 return it. Before 0.1.15 the message was the compiler's description, or Unexpected error. |
:start, :restart, :stop, :enable, :unregister | :global | An error occurred in the compile of an event. |
A field that fails validation, such as a priority above 100, returns an error whose field names it.
The :database error has a fourth key, source, which holds the raw Mnesia result, such as {:aborted, {:node_not_running, node}}. write/1, write/3, delete/2 and group_events/1 return it when their transaction aborts, and so do the lifecycle functions that call them. get/0, get/1 and get/2 log the same failure and answer [] or nil instead.
With queue set to false, start/3, restart/3, stop/3 and unregister/3 by :name, and enable/2, return an error when the rebuild of the event fails. Its action is the name of the function. The new status is already stored when the error returns, and the plugins that depend on this one are not re-evaluated.
A module that cannot be built fails the rebuild. The handler logs compile failed for event and emits the :compile telemetry event with result: :error. The Telemetry and log reference lists both.
Rebuild an event
| Function | Returns | What it does |
|---|---|---|
EventHandler.do_compile(event, status, queue \\ true) | :ok, or an error when queue is false and the rebuild fails | Rebuilds the compiled module of an event from the registry. status is an atom naming the reason, such as :register. With false it waits, and it must not be called from the event handler's own process. |
The compiled event module
Each event with plugins has a module named MishkaInstaller.Event.ModuleStateCompiler.State.<Name>. Its functions:
| Function | Returns |
|---|---|
call(state, args \\ []) | The chain's result. In :error mode, the :cannot_run argument or state. When a plugin raises, the plugins after it do not run, and the answer is the :raised argument or state. |
mode() | :ok, or :error when a plugin of the chain is started but not loaded. |
initialize?() | true. |
initialize() | %{module: module, plugins: [record]}, the chain in order. |
is_changed?(plugins) | Whether plugins differs from the compiled chain. |
is_initialized?(plugin) | Whether the record is in the chain. |
Functions of ModuleStateCompiler:
| Function | What it does |
|---|---|
create(plugins, event, inaccessible \\ []) | Builds the module and returns :ok. It never stops a process that still runs an earlier version: the new version goes into the event's own module name when no process runs the old one, and otherwise into the next free slot (State1.<Name> and so on). A build drops every other slot that no process runs. With a non-empty inaccessible list it builds the stub for an event that cannot run. The module is built in its own process, so a failure never raises in the caller. When the module cannot be built it returns {:error, [%{action: :compile, field: :event, message: message}]}. |
purge_create(plugins, event, inaccessible \\ []) | The same as create/3. Nothing is purged hard. |
purge(event_or_events) | Takes one event or a list of events out of event_module/1 and drops their versions that no process runs. A process that still runs a version finishes on it, and the version is dropped once nothing runs it. From then on Hook.call/3 answers the data it is given. |
module_event_name(event) | The module name for an event. For a name it has not seen before, it creates an atom and a :persistent_term key. The library removes them only when purge/1 finds that no process runs any version. It is the event's own module name, which runs the event unless a caller outlived two builds, so use event_module/1 to find the module that runs it. |
event_module(event) | The module that runs the event now, or nil when no module was built or purge/1 removed it. It is one :persistent_term read, and it creates no atom and no key, so it is the function to call with a name that may not exist. |
initialize?(event) | Whether the module is initialized. Raises when it does not exist. |
rescue_initialize?(event) | The same, and false instead of raising. |
compile_initialize?(event) | Whether the module is loaded. |
safe_initialize?(event) | Whether the module exports initialize?/0. |