Asterisk - The Open Source Telephony Project  18.5.0
Stasis Implementation Notes
Reference counting

Stasis introduces a number of objects, which are tightly related to one another. Because we rely on ref-counting for memory management, understanding these relationships is important to understanding this code.

^ ^
\ /
\ /
dispatch
|
|
v
|
|
v

The most troubling thing in this chart is the cyclic reference between stasis_topic and stasis_subscription. This is both unfortunate, and necessary. Topics need the subscription in order to dispatch messages; subscriptions need the topics to unsubscribe and check subscription status.

The cycle is broken by stasis_unsubscribe(). The unsubscribe will remove the topic's reference to a subscription. When the subcription is destroyed, it will remove its reference to the topic.

This means that until a subscription has be explicitly unsubscribed, it will not be destroyed. Neither will a topic be destroyed while it has subscribers. The destructors of both have assertions regarding this to catch ref-counting problems where a subscription or topic has had an extra ao2_cleanup().

The dispatch object is a transient object, which is posted to a subscription's taskprocessor to send a message to the subscriber. They have short life cycles, allocated on one thread, destroyed on another.

During shutdown, or the deletion of a domain object, there are a flurry of ao2_cleanup()s on subscriptions and topics, as the final in-flight messages are processed. Any one of these cleanups could be the one to actually destroy a given object, so care must be taken to ensure that an object isn't referenced after an ao2_cleanup(). This includes the implicit ao2_unlock() that might happen when a RAII_VAR() goes out of scope.

Typical life cycles
Subscriber shutdown sequencing

Subscribers are sensitive to shutdown sequencing, specifically in how the reference message types. This is fully detailed on the wiki at https://wiki.asterisk.org/wiki/x/K4BqAQ.

In short, the lifetime of the data (and callback, if in a module) must be held until the stasis_subscription_final_message() has been received. Depending on the structure of the subscriber code, this can be handled by using stasis_subscription_final_message() to free resources on the final message, or using stasis_subscription_join()/stasis_unsubscribe_and_join() to block until the unsubscribe has completed.