# Listener ## Summary The Listener is besides the WaitSet one of the building blocks to realize a push approach to detect and react to certain events. The Listener offers the user the ability to attach objects with a corresponding event and callback. Whenever the object receives the specified event the corresponding callback is called in the Listener background thread as a reaction. The two key differences to the WaitSet are that the Listener is event-driven and not event- and state-driven as the WaitSet and that the Listener creates a separate background thread in which the event-callbacks are executed in contrast to the WaitSet where the user has to call the event-callbacks explicitly. ## Terminology - [Condition Variable](https://en.wikipedia.org/wiki/Monitor_(synchronization)#Condition_variables_2) Used by an attached object to inform the Listener/WaitSet that an event has occurred. - **event** is changing the state of an object. - **event driven** a one time reaction which is caused directly by an event. Example: a new sample has been delivered to a subscriber. - **state** predefined values to which the members of an object are set. - **state driven** a repeating reaction which is continued as long as the state persists. Example: a subscriber has stored samples which were not inspected by the user. ## Design The Listener is a variation of the [reactor pattern](https://en.wikipedia.org/wiki/Reactor_pattern) and the usage should be similar to the _WaitSet_ with a key difference - it should be **event driven** and not a mixture of event and state driven, depending on which event is attached, like in the _WaitSet_. ### Requirements - Whenever an event occurs the corresponding callback should be called **once** as soon as possible. - If an event occurs multiple times before the callback was called, the callback should be called **once**. - If an event occurs while the callback is being executed the callback should be called again **once**. - Thread-safety: attaching an event at any time from any thread. - Thread-safety: detaching an event at any time from any thread. - If the callback is currently running `detachEvent` blocks until the callback is finished. - After the `detachEvent` call the event-callback is not called anymore, even when the event was signalled while `detachEvent` was running and the callback was not yet executed. - If the callback is detached from within the event-callback then `detachEvent` is non blocking. The event is detached right after the `detachEvent` call. - One can attach at most one callback to a specific event of a specific object. - Usually defined with an enum by the developer. One example is `SubscriberEvent::DATA_RECEIVED`. - Attaching a callback to an event where a callback has been already attached results in an error. - One can attach the same event to different objects at the same time. - One can attach multiple different events to a single object - When the Listener goes out of scope it detaches itself from every attached object via a callback provided by the attached object (like in the WaitSet). - When the class which is attached to the Listener goes out of scope it detaches itself from the Listener via a callback provided by the Listener (like in the WaitSet). ### Solution #### Class diagram ``` +---------------------------+ | ConditionVariableData | | - m_semaphore | | - m_runtimeName | | - m_toBeDestroyed | | - m_activeNotifications | +---------------------------+ | 1 | 1 | | | 1 | n +-----------------------------------------------+ +--------------------------------------------------+ | ConditionListener | | ConditionNotifier | | ConditionListener(ConditionVariableData & ) | | ConditionNotifier(ConditionVariableData &, | | | | uint64_t notificationIndex) | | bool wasNotified() | | | | void destroy() | | void notify() | | NotificationVector_t wait() | | | | NotificationVector_t timedWait() | | - m_condVarDataPtr : ConditionVariableData* | | | | - m_notificationIndex | | - m_condVarDataPtr : ConditionVariableData* | +--------------------------------------------------+ | - m_toBeDestroyed : std::atomic_bool | | 1 +-----------------------------------------------+ | | 1 | n | +--------------------------------+ | 1 | TriggerHandle | +-------------------------------------------------+ | bool isValid() | | Listener | | bool wasTriggered() | | attachEvent(Triggerable, EventType, Callback) | | void trigger() | | detachEvent(Triggerable, EventType) | | void reset() | | | | void invalidate() | | - m_events : Event_t[] | | void getUniqueId() | | - m_thread : std::thread | | | | - m_conditionListener : ConditionListener | | - m_conditionVariableDataPtr | | | | - m_resetCallback | | +----------------------------+ | | - m_uniqueTriggerId | | | Event_t | | +--------------------------------+ | | void executeCallback() | | | 1 | | bool reset() | | | | | bool init(...) | | | n | | | | +-------------------------------------------------------+ | | - m_origin | | | Triggerable (e.g. Subscriber) | | | - m_callback | | | | | | - m_invalidationCallback | | | void invalidateTrigger(const uint64_t triggerId) | | | - m_eventId | | | void enableEvent(TriggerHandle&&, const EventEnum ) | | +----------------------------+ | | void enableEvent(TriggerHandle&&) | +-------------------------------------------------+ | void disableEvent(const EventEnum) | | void disableEvent() | | | | - m_triggerHandle : TriggerHandle | +-------------------------------------------------------+ ``` The Triggerable does not need to implement all `enableEvent`, `disableEvent` variations, only the ones which are required by the use case. The `enableEvent` and `disableEvent`, without the distincting `EventEnum`, can be used when there is only a single event which can be triggered. #### Class Interactions - **Creating Listener:** a `ConditionVariableData` is created in the shared memory The `Listener` uses the `ConditionListener` to wait for incoming events. ``` PoshRuntime Listener | | getMiddlewareConditionVariable : var | | --------------------------------------> | | ConditionListener(var) | ConditionListener | ----------------------------------------+-----------------> | | wait() : vector | | | ----------------------------------------+-----------------> | ``` - **Attaching Triggerable Event (SubscriberEvent::DATA_RECEIVED) to Listener:** The Listener creates a TriggerHandle and provides it to the Triggerable (Subscriber) via `enableEvent` so that the Triggerable owns the handle. Whenever the event occurs the Triggerable can use the `trigger()` method of the TriggerHandle to notify the Listener. ``` User Listener Triggerable | attachEvent() | | | ------------------> | TriggerHandle | | | create | | | | ---------> | | | | enableEvent(std::move(TriggerHandle)) | | | -----------+--------------------------------------> | ``` - **Signal an event from Triggerable:** `TriggerHandle::trigger()` is called, the Listener is returning from the `ConditionListener.wait()` call and retrieves a list of all the signal notifications. The corresponding event-callbacks are called. ``` Triggerable TriggerHandle ConditionNotifier ConditionListener Listener Event_t | trigger() | | | wait() : notificationIds | | | -------------> | notify() | | <------------------------- | | | | -------------> | .... unblocks .... | blocks | exeuteCallback() | | | | | | ------------------------> | | | | | | m_events[notificationId] | ``` - **Triggerable goes out of scope:** The TriggerHandle is a member of the Triggerable, therefore the d'tor of the TriggerHandle is called which removes the trigger from the Listener via the `resetCallback` ``` Triggerable TriggerHandle Listener Event_t | ~TriggerHandle | | | | ----------------> | removeTrigger() | | | | ----------------> | reset() | | | via resetCallback | ------------> | ``` - **Listener goes out of scope:** The d'tor of the `Event_t` invalidates the Trigger inside the Triggerable via the `invalidationCallback` ``` Listener Event_t Triggerable | ~Event_t() | | | -----------> | invalidateTrigger() | | | -----------------------> | | | via invalidationCallback | ``` #### TriggerHandle - **Problem:** The Triggerable should be able to notify a Listener/WaitSet without having any knowledge about those class so that circular dependencies can be prevented. Furthermore, the Triggerable must be able to remove its attached events when it goes out of scope. - **Solution:** The dependency inversion principle, create an abstraction which is known by both, the TriggerHandle. Created by the Listener/WaitSet and attached to the Triggerable so that it can notify the Listener/WaitSet via the underlying `ConditionNotifier` with `TriggerHandle::notify()`. The cleanup task is performed by the `m_resetCallback` so that the Triggerable has no dependencies to any Notifyable. #### Condition Variable The `ConditionListener` and `ConditionNotifier` are two different interfaces to the same class which state is stored in the `ConditionVariableData` class. The intention of the separation is to provide one side (e.g. Triggerable) only an API to notify the Notifyable (e.g. Listener) whereas the Notifyable can only wait on events. So the contract is reflected in the design. - **Problem:** Since the Listener reacts on events and not states it requires the knowledge by whom it was notified. - **Solution:** - Every TriggerHandle has a unique id which is used as index in `ConditionNotifier`. - When `ConditionNotifier::notify` is called the Listener is informed via the `NotificationVector_t` return value from `ConditionListener::wait()` which index notified him. Hence it is the same as the unique id of the TriggerHandle the Listener knows which Triggerable notified him. #### Event_t ##### Concurrency The Listener must be able to attach, detach events concurrently. Additionally, it supports that a callback can attach or detach further events or detach its corresponding event concurrently. Furthermore, the Listener supports that a callback is called concurrently while events are being attached/detached. To realize this we created the `Event_t` abstraction which is stored in an array called `m_events`. If we would like to attach or detach an event we either initialize `Event_t::init()` or reset `Event_t::reset()` the corresponding entry in the `m_events` array. The array has the advantage that the data structure itself never changes during runtime therefore it does not have to be thread-safe. The thread-safety must then be ensured by the `Event_t` class itself. Since every concurrent action is contained in `Event_t` we can use `concurrent::smart_lock` in combination with a `std::recursive_mutex` to guarantee the thread-safe access. 1. Concurrent attach/detach event and callback execution, ensured by thread-safe `Event_t`. 2. Detaching itself from within a callback is ensured via the `std::recursive_mutex`. 3. Attaching/detaching arbitrary events from within a callback is ensured by securing every `Event_t` object in `m_events` with a `concurrent::smart_lock`. This would not be possible if the data structure itself had to be thread-safe. ##### Lifetime Since the event contains everything which is required to handle events it has the responsibility to ensure the life-time of the TriggerHandle. This is done by the `m_invalidationCallback` which is called in `Event_t::reset()` to invalidate the TriggerHandle in the corresponding Triggerable. This is either done when an event is detached or the Listener goes out of scope. #### Triggerable (e.g. Subscriber) The Triggerable is a set of classes of which events can be attached to the Listener. It is possible to either attach a specific event of a class to the Listener or attach the class without providing an event. The basic idea is that the Listener creates a TriggerHandle whenever an event is attached and provides that TriggerHandle to the corresponding Triggerable. The Triggerable uses then the TriggerHandle to notify the Listener about events. ##### Triggerable With Single Event Every Triggerable requires: 1. The private methods: ```cpp void enableEvent(iox::popo::TriggerHandle&& triggerHandle) noexcept; void disableEvent() noexcept; void invalidateTrigger(const uint64_t uniqueTriggerId) noexcept; ``` ##### Triggerable With Multiple Events Every Triggerable requires: 1. An `enum class` which uses `iox::popo::EventEnumIdentifier` as underlying type. ```cpp enum class EventEnum : iox::popo::EventEnumIdentifier { EVENT_IDENTIFIER, ANOTHER_EVENT_IDENTIFIER, }; ``` 2. The private methods: ```cpp void enableEvent(iox::popo::TriggerHandle&& triggerHandle, const EventEnum event) noexcept; void disableEvent(const EventEnum event) noexcept; void invalidateTrigger(const uint64_t uniqueTriggerId) noexcept; ``` The methods above are used by the Listener to transfer the ownership of the TriggerHandle to the Triggerable. The Triggerable should have one TriggerHandle member for every attachable event/state. The TriggerHandle is then used to notify the Listener by the Triggerable that a certain event has occurred. 3. It must be friend with `iox::popo::NotificationAttorney`. It is possible to provide public access to the previous methods but then the user has the ability to call methods which should only used by the Listener.