Asterisk - The Open Source Telephony Project GIT-master-3dee037
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.

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_exec_sync 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
  • stasis_subscription - Subscriptions have a similar mix of lifetimes as topics, for similar reasons.
  • dispatch - Very short lived; just long enough to post a message to a subscriber.
  • stasis_message - Short to intermediate lifetimes, but that is mostly irrelevant. Messages are strictly data and have no behavior associated with them, so it doesn't really matter if/when they are destroyed. By design, a component could hold a ref to a message forever without any ill consequences (aside from consuming more memory).
  • stasis_message_type - Long life cycles, typically only destroyed on module unloading or clean process exit.
Subscriber shutdown sequencing

Subscribers are sensitive to shutdown sequencing, specifically in how the reference message types. This is fully detailed in the documentation at https://docs.asterisk.org/Development/Roadmap/Asterisk-12-Projects/Asterisk-12-API-Improvements/Stasis-Message-Bus/Using-the-Stasis-Message-Bus/Stasis-Subscriber-Shutdown-Problem/.

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.