Plugin statuses and dependencies

v0.1.14

Every plugin record carries a status, and the status decides whether the plugin is part of its event's chain. This page explains the five statuses, the two decisions behind them, how dependencies hold and release plugins, how the library orders a chain, and what each lifecycle call does to a status.

The five statuses

StatusMeaningReached by
:registeredStored, and not started yet.The first registration.
:startedRunning, and part of its event's chain.start
:restartedRunning, and part of its event's chain.restart, enable, or the release of a held plugin.
:stoppedAn operator switched it off. It survives restarts and dependency changes.stop
:heldAt least one dependency is not active. The library releases it when they are.Registration, enable, or a change to a dependency.

When the library rebuilds an event, it keeps each plugin that is not :stopped or :held and whose dependencies are all active. A dependency is active when its status is :started or :restarted.

Two decisions that never override each other

Only two statuses record a decision. :stopped is the decision of an operator. :held is the decision of the library, taken because a dependency is not active.

  • Releasing a dependency never switches on a plugin an operator stopped.
  • start and restart refuse a stopped plugin. Only enable brings it back, so no reload or reset path can undo the operator.
  • A held plugin can still be stopped, because being blocked by a dependency and being switched off are different things.
  • A boot never starts a stopped plugin on its own.

Status writes are durable. The library commits them synchronously and flushes the log, so a plugin an operator stopped stays stopped after an unclean shutdown.

Dependencies

depends: [OtherPlugin] means "do not run me unless OtherPlugin runs". The list holds plugin modules, and a dependency may belong to another event. All of them must be active.

  • Stopping, unregistering or holding a plugin holds every plugin that depends on it. The hold is transitive, so a chain of dependents is held as a unit, and it crosses events. The held plugins leave their compiled chains in the same call.
  • Starting, restarting or enabling the plugin releases what it was holding. A plugin with two dependencies stays held until both are active.
  • Nothing keeps a list of dependents. The library reads the reverse edges from the stored depends of every plugin, which Event.dependents/1 returns.
  • A plugin whose dependency is not active when it registers is stored as :held. On boot, a plugin held because a dependency on another event has not started yet is released when that dependency starts.
  • The library rejects a dependency cycle at registration, so a plugin can never stay held forever behind itself. The error has the action :register and the field :depends.

Order

A chain runs in this order:

  1. A plugin runs after every plugin it depends on, whatever their priorities.
  2. Among the plugins that are ready, the smaller priority runs first. Priority is an integer from 0 to 100, and the default is 100.
  3. Equal priorities run in order of the plugin's module name.

What each call does

CallRefused whenResult
registerThe module is not loaded, the priority is out of range, or the dependencies form a cycle.A new plugin is stored as :registered, or :held. An existing plugin takes the priority, extra data and dependencies from the code and keeps its status, except that it moves into or out of :held as its dependencies require.
startThe plugin has no record, is stopped, or has a dependency that is not active.:started
restartThe plugin has no record, is stopped, or has a dependency that is not active.:restarted
stopThe plugin has no record, or is already stopped.:stopped. Dependents are held.
enableThe plugin has no record.:restarted, or :held when a dependency is not active. Dependents are released.
unregisterThe plugin has no record.The record is deleted and the process stops. Dependents are held.

Each call that changes a chain rebuilds the events it touches, so the stored statuses and the compiled chains agree.

Example: a dependency that stops

Take Root, Mid and Leaf, where Mid depends on Root and Leaf depends on Mid. Each row starts with all three plugins started, each on its own event.

StepRootMidLeaf
Stop Root:stopped:held:held
Enable Root:restarted:restarted:restarted
Stop Mid, stop Root, enable Root:restarted:stopped:held

In the last row Mid stays stopped, because an operator stopped it, and Leaf stays held, because Mid is not active. The library's dependency tests check these sequences.