Module#

A module is the core entity of libmodule: it is a single and indipendent logical unit that reacts to events.
It can be seen as an Actor with the ability of managing non-PubSub events.
It requires some callbacks that are used by libmodule to manage its life.

Source#

Each module’s listens to events by registering event sources.
A source can be a timer, a topic (for PubSub communications between modules), a signal, a socket, etc etc.
You can find the list of supported sources in <module/mod.h>.

Source Priorities#

Users can also attach priorities to sources;
these priorities are set as m_src_flags passed as source registration parameter.
They are:

  • M_SRC_PRIO_LOW -> never notify events generated by the source alone; instead, notify them as soon as an higher prio event is received
  • M_SRC_PRIO_NORM (default if unspecified) -> notify events generated by the source only if either batch size or batch timeout are reached
  • M_SRC_PRIO_HIGH -> always notify events generated by the source immediately

For FD sources, M_SRC_PRIO_HIGH is implicitly set, because you don’t want to miss reading fd data,
otherwise the ctx loop would ramp up cpu usage.

Lifecycle#

Callbacks#

First of all, module lifecycle is automatically managed by libmodule; moreover, when using <module/mod_easy> API, module registration/deregistration is completely automated and transparent to developer.
This means that when using it, you will only have to declare a source file as a module and define needed functions.

First function to be called is m_on_boot(); it is called right after libmodule gets linked.
This function is useful to set a custom memhook for libmodule, through m_set_memhook() API.

EASY API: each module’s m_mod_on_boot() function is called. At this stage, no module is registered yet.

Finally, libmodule will register every module.
Once a module is registered, it will be initially set to M_MOD_IDLE state. Idle means that the module is not started yet, thus it cannot receive any event.

As soon as its context starts looping, m_mod_on_eval() function will be called on every idle module, trying to start it right away.
That function will be called at each state machine change, for each idle module, until it returns true.

As soon as m_mod_on_eval() returns true, a module is started. It means its m_mod_on_start() function is finally called and its state is set to M_MOD_RUNNING.
When a module reaches RUNNING state, its context loop will finally receive events for its registered sources.

Whenever an event triggers a module’s wakeup, its m_mod_on_evt() callback can be called (depending on the event source priority) with a m_queue_t<m_evt_t *> argument.

Finally, when stopping a module, its m_mod_on_stop() function is called.
Note that a module is automatically stopped during the process of module’s deregistration.

Thus, for Easy API, you should implement m_mod_on_eval() to return true when the module is ready to be started,
then eventually register event sources in m_mod_on_start(), and manage events in m_mod_on_evt().
If you need to cleanup any data, manage it in m_mod_on_stop().

States#

As previously mentioned, a registered module, before being started, is inM_MOD_IDLE state.
It means that it has never been started before; it won’t receive any event thus its event callback will never be called.
When module is started, thus reaching M_MOD_RUNNING state, all its registered sources will finally start to receive events. Sources registered while in M_MOD_RUNNING state are automatically polled.
If a module is paused, reaching M_MOD_PAUSED state, it will stop polling on its event sources, but event sources will still be kept alive. Thus, as soon as module is resumed, all events received during paused state will trigger m_evt_cb.
If a module gets stopped, reaching M_MOD_STOPPED state, it will destroy any registered source and it will be resetted to its initial state.
If you instead wish to stop a module letting it manage any already-enqueued event, you need to send a POISONPILL message to it, through m_mod_poisonpill() API.
The difference between M_MOD_IDLE and M_MOD_STOPPED states is that idle modules will be evaluated to be started during context loop, while stopped modules won’t.
When a module is deregistered, it will reach a final M_MOD_ZOMBIE state. It means that the module is no more reachable neither usable, but it can still be referenced by any previously sent message (or any m_mod_ref()).
After all module’s ref count drops to 0 (ie: all sent messages are received by respective recipients and there are no pending m_mod_unref()) module will finally get destroyed and its memory freed.
You can call only m_mod_is(), m_mod_name() and m_mod_ctx() on a zombie module.

To summarize:

  • m_mod_start() needs to be called on an M_MOD_IDLE or M_MOD_STOPPED module.
  • m_mod_pause() needs to be called on a M_MOD_RUNNING module.
  • m_mod_resume() needs to be called on a M_MOD_PAUSED module.
  • m_mod_stop() needs to be called on a M_MOD_RUNNING or M_MOD_PAUSED module.