读一读冰羚代码(14)数据发布的通知机制之state

在前面的系列文章中,以iceoryx_examples/icehello/iox_subscriber_helloworld.cpp为例介绍subscriber的数据接收过程。在这个demo程序中,subscriber以100ms为周期轮询队列中是否有数据到来,这种设计在特定的业务场景下有其合理性,比如业务确实需要间隔一定的时间才需要去队列中获取数据。

很多时候,publisher一旦有数据发布,需要立即通知subscriber去队列中获取数据。

场景一:

subcriber分布在不同的进程,publisher与subscriber之间除了有专属的数据队列,还有专属的铃铛。publisher将数据放入队列后,敲响铃铛通知subscriber从队列中获取数据。

场景2:

同一个进程中有别的进程中的同一个publisher的多个subscriber,此时只需要一个铃铛,这个铃铛由调度中心持有。调度中心中还有一个调度员及等候的subscriber们。publisher由数据发布时,会敲响铃铛通知调度中心的调度员。

publisher会依次将数据放入每个subscriber的专属队列。将数据放入数据队列1后,敲一次铃铛通知调度员,接着将数据放入数据队列2,再敲一次铃铛通知调度员,重复操作,直到将数据放入所有的subscriber队列为止。

调度员听到铃声后,会通知subscriber去专属队列获取数据。那么问题来了,调度员怎么直到这次的铃声表示是哪个subscriber的队列被放入了数据,然后通知对应的subscriber去专属队列获取数据呢?答案是给subscriber赋予一个唯一的编号,编号会被publisher和调度中心共同持有。publisher在将数据放入subscriber专属队列敲响铃铛的同时,会将该subscriber对应的编号一并发送给调度中心,调度员拿到这个编号就知道该通知哪个subscriber去专属队列中获取数据了。

subscriber还可以分组,组内的subscriber可以决定收到数据后采取一致的定制化的操作。比如subscriber1和subscriber3分为一组,收到数据后向媒体展示publisher发布了什么数据,subscriber2比较任性,单独成团,收到数据后将其丢向空中随风飘散。

冰羚中的WaitSet可以用来应对上述两种场景。

先以iceoryx_examples/waitset/ice_waitset_grouping.cpp程序为例接收WaitSet的基本原理。这个程序创建了一个publisher对应的四个subscriber,四个subscriber两两分组(组ID分别为FIRST_GROUP_ID和SECOND_GROUP_ID,组ID的数值没有特殊要求,可以在取值范围内任意指定,但必须保证每个组ID是唯一的):

// create subscriber and subscribe them to our service
    //! [create subscribers]
    iox::vector<iox::popo::UntypedSubscriber, NUMBER_OF_SUBSCRIBERS> subscriberVector;
    for (auto i = 0U; i < NUMBER_OF_SUBSCRIBERS; ++i)
    {
        subscriberVector.emplace_back(iox::capro::ServiceDescription{"Radar", "FrontLeft", "Counter"});
    }
    //! [create subscribers]


//! [configure subscribers]
    // attach the first two subscribers to waitset with a id of FIRST_GROUP_ID
    for (auto i = 0U; i < NUMBER_OF_SUBSCRIBERS / 2; ++i)
    {
        waitset.attachState(subscriberVector[i], iox::popo::SubscriberState::HAS_DATA, FIRST_GROUP_ID)
            .or_else([&](auto) {
                std::cerr << "failed to attach subscriber" << i << std::endl;
                std::exit(EXIT_FAILURE);
            });
    }

    // attach the remaining subscribers to waitset with a id of SECOND_GROUP_ID
    for (auto i = NUMBER_OF_SUBSCRIBERS / 2; i < NUMBER_OF_SUBSCRIBERS; ++i)
    {
        waitset.attachState(subscriberVector[i], iox::popo::SubscriberState::HAS_DATA, SECOND_GROUP_ID)
            .or_else([&](auto) {
                std::cerr << "failed to attach subscriber" << i << std::endl;
                std::exit(EXIT_FAILURE);
            });
    }

先后运行三个冰羚代码编译后的可执行文件(iox-roudi、iox-cpp-waitset-publisher和iox-cpp-waitset-grouping,前两个没有使用WaitSet,仅发布数据使用),输出如下:

从输出结果可以看出,subscriber1和subscriber2分为一组,收到数据后打印出来。subscriber3和subscriber4分为一组,收到数据后直接丢弃不做处理。

以上代码构造完subscriber对象后,Roudi进程服务匹配过程会调用ChunkDistributor<ChunkDistributorDataType>::tryAddQueue将每个 subscriber的专属队列加入到publisher的subcriber队列缓存容器中,发布数据的时候会将数据push到队列缓存容器中存储的每个subscriber专属队列中,subscriber就可以接收到发布的数据了。这个函数的实现稍后会详细解释。

什么是WaitSet中的state呢?state由subscriber定义,比如可以定义一个标识队列中有数据的状态:

enum class SubscriberState : StateEnumIdentifier
{
    HAS_DATA
};

状态有枚举类型和枚举值,同一个枚举类型中定义的状态的枚举值不一样,但状态枚举类型的hash code一样。不同枚举类型中定义的状态的枚举值可能一样,但状态的枚举类型不一样,例如下面的程序:

#include <iostream>
#include <typeinfo>

enum class SubscriberState : uint64_t
{
    HAS_DATA,
    NO_DATA
};

enum class PublisherState : uint64_t
{
    HAS_DATA,
    NO_DATA
};

int main() {
    std::cout << static_cast<uint64_t>(SubscriberState::NO_DATA) << std::endl;
    std::cout << static_cast<uint64_t>(PublisherState::NO_DATA) << std::endl;
    std::cout << typeid(SubscriberState::NO_DATA).hash_code() << std::endl;
    std::cout << typeid(PublisherState::NO_DATA).hash_code() << std::endl;
    return 0;
}

输出为:

要唯一标识一个状态,至少需要类的对象(谁定义了这个状态)、状态枚举值及状态所属枚举类型的hash code。Trigger用来标识状态,一个状态对应一个Trigger对象。Trigger的数据成员:

class Trigger
{
  private:
    NotificationInfo m_notificationInfo;

    function<bool()> m_hasTriggeredCallback;
    function<void(uint64_t)> m_resetCallback;
    uint64_t m_uniqueId = INVALID_TRIGGER_ID;

    TriggerType m_triggerType = TriggerType::STATE_BASED;
    uint64_t m_originTriggerType = INVALID_TRIGGER_ID;
    uint64_t m_originTriggerTypeHash = INVALID_TRIGGER_ID;
};

m_notificationInfo:NotificationInfo对象

m_hasTriggeredCallback: 判断状态是否实际发生了的回调函数

m_resetCallback:重启Trigger的回调函数

m_uniqueId:状态的唯一索引值(与实际的枚举值区分开来)

m_triggerType:state还是event

m_originTriggerType:状态枚举值

m_originTriggerTypeHash:状态所属枚举类型的hash code

NotificationInfo的数据成员:

class NotificationInfo
{
protected:
    void* m_notificationOrigin = nullptr;
    void* m_userValue = nullptr;
    uint64_t m_notificationOriginTypeHash = 0U;
    uint64_t m_notificationId = INVALID_ID;

    internal::GenericCallbackPtr_t m_callbackPtr = nullptr;
    internal::TranslationCallbackPtr_t m_callback = nullptr;
};

m_notificationOrigin:状态属于哪个subscriber对象

m_userValue:用户数据,供用户回调函数使用

m_notificationOriginTypeHash:状态所属subscriber类的hash code

m_notificationId:用于subscriber分组的ID

m_callbackPtr/m_callback:用户回调函数相关

Trigger对象由WaitSet对象持有,TriggerHandle由subscriber对象持有,Trigger对象与TriggerHandle对象一一对应。TriggerHandle的构造函数:

TriggerHandle::TriggerHandle(ConditionVariableData& conditionVariableData,
                             const function<void(uint64_t)>& resetCallback,
                             const uint64_t uniqueTriggerId) noexcept
    : m_conditionVariableDataPtr(&conditionVariableData)
    , m_resetCallback(resetCallback)
    , m_uniqueTriggerId(uniqueTriggerId)
{
}

对应关系由状态的唯一索引值关联起来。示意图如下:

现在来看看iceoryx_examples/waitset/ice_waitset_grouping.cpp的main函数:

int main()
{
    // register sigHandler
    auto signalIntGuard =
        iox::registerSignalHandler(iox::PosixSignal::INT, sigHandler).expect("failed to register SIGINT");
    auto signalTermGuard =
        iox::registerSignalHandler(iox::PosixSignal::TERM, sigHandler).expect("failed to register SIGTERM");

    iox::runtime::PoshRuntime::initRuntime("iox-cpp-waitset-grouping");
    //! [create waitset]
    WaitSet waitset;
}

除了常规的subscriber运行时环境初始化,还定义了一个WaitSet对象。WaitSet的构造函数定义:

constexpr uint64_t NUMBER_OF_SUBSCRIBERS = 4U;
using WaitSet = iox::popo::WaitSet<NUMBER_OF_SUBSCRIBERS>;

template <uint64_t Capacity>
inline WaitSet<Capacity>::WaitSet() noexcept
    : WaitSet(*runtime::PoshRuntime::getInstance().getMiddlewareConditionVariable())
{
}

又看到了熟悉的runtime::PoshRuntime::getInstance().getMiddleware*系列接口,根据之前文章的解释,这种接口向Roudi进程请求在共享内存上分配相关数据结构。PoshRuntimeImpl::getMiddlewareConditionVariable()的定义如下:

popo::ConditionVariableData* PoshRuntimeImpl::getMiddlewareConditionVariable() noexcept
{
    IpcMessage sendBuffer;
    sendBuffer << IpcMessageTypeToString(IpcMessageType::CREATE_CONDITION_VARIABLE) << m_appName;
}

跟之前一样的讨论,先向Roudi进程发送特定类型的请求消息,这次的消息类型为IpcMessageType::CREATE_CONDITION_VARIABLE。Roudi进程收到消息后会调用PortPool::addConditionVariableData:

expected<popo::ConditionVariableData*, PortPoolError>
PortPool::addConditionVariableData(const RuntimeName_t& runtimeName) noexcept
{
    auto conditionVariableData = getConditionVariableDataList().emplace(runtimeName);
    if (conditionVariableData == getConditionVariableDataList().end())
    {
        IOX_LOG(Warn, "Out of condition variables! Requested by runtime '" << runtimeName << "'");
        IOX_REPORT(PoshError::PORT_POOL__CONDITION_VARIABLE_LIST_OVERFLOW, iox::er::RUNTIME_ERROR);
        return err(PortPoolError::CONDITION_VARIABLE_LIST_FULL);
    }
    return ok(conditionVariableData.to_ptr());
}

跟之前PublisherPortData和SubscriberPortData对象构造类似,这次是在FixedPositionContainer<popo::ConditionVariableData, MAX_NUMBER_OF_CONDITION_VARIABLES>上找出一个空闲位置构造ConditionVariableData对象:

struct PortPoolData
{
    PortPoolData(const roudi::UniqueRouDiId uniqueRouDiId) noexcept
        : m_uniqueRouDiId(uniqueRouDiId)
    {
    }

    using InterfaceContainer = FixedPositionContainer<popo::InterfacePortData, MAX_INTERFACE_NUMBER>;
    InterfaceContainer m_interfacePortMembers;

    using CondVarContainer = FixedPositionContainer<popo::ConditionVariableData, MAX_NUMBER_OF_CONDITION_VARIABLES>;
    CondVarContainer m_conditionVariableMembers;

    using PublisherContainer = FixedPositionContainer<iox::popo::PublisherPortData, MAX_PUBLISHERS>;
    PublisherContainer m_publisherPortMembers;

    using SubscriberContainer = FixedPositionContainer<iox::popo::SubscriberPortData, MAX_SUBSCRIBERS>;
    SubscriberContainer m_subscriberPortMembers;

    using ServerContainer = FixedPositionContainer<iox::popo::ServerPortData, MAX_SERVERS>;
    ServerContainer m_serverPortMembers;

    using ClientContainer = FixedPositionContainer<iox::popo::ClientPortData, MAX_CLIENTS>;
    ClientContainer m_clientPortMembers;

    const roudi::UniqueRouDiId m_uniqueRouDiId;
};

将ConditionVariableData的segment ID及偏移量传给subscriber进程,subcriber进程根据这两个值还原ConditionVariableData在subscriber进程地址空间中的实际地址。

ConditionVariableData的定义:

namespace iox
{
namespace popo
{
struct ConditionVariableData
{
    ConditionVariableData() noexcept;
    explicit ConditionVariableData(const RuntimeName_t& runtimeName) noexcept;

    ConditionVariableData(const ConditionVariableData& rhs) = delete;
    ConditionVariableData(ConditionVariableData&& rhs) = delete;
    ConditionVariableData& operator=(const ConditionVariableData& rhs) = delete;
    ConditionVariableData& operator=(ConditionVariableData&& rhs) = delete;
    ~ConditionVariableData() noexcept = default;

    optional<build::InterProcessSemaphore> m_semaphore;         (1)
    RuntimeName_t m_runtimeName;
    concurrent::Atomic<bool> m_toBeDestroyed{false};
    concurrent::Atomic<bool> m_activeNotifications[MAX_NUMBER_OF_NOTIFIERS];   (2)
    concurrent::Atomic<bool> m_wasNotified{false};
};

(1)使用linux的信号量接口创建信号量,所以WaitSet机制的底层逻辑本质上使用信号量完成进程之间的同步(linux系统)

(2)数组的索引值对应subscriber特定状态的索引,存储值标识哪个subscriber上的队列有数据到来

继续看WaitSet的构造函数:

template <uint64_t Capacity>
inline WaitSet<Capacity>::WaitSet(ConditionVariableData& condVarData) noexcept
    : m_conditionVariableDataPtr(&condVarData)
    , m_conditionListener(condVarData)   (1)
{
    for (uint64_t i = 0U; i < Capacity; ++i)
    {
        m_indexRepository.push(i);       (2)
    }
}

(1)用ConditionVariableData对象构造ConditionListener对象

(2)缓存索引值集合,从该集合中找出一个索引值赋给subscriber的特定状态

ConditionListener的构造只是简单的指针赋值:

ConditionListener::ConditionListener(ConditionVariableData& condVarData) noexcept
    : m_condVarDataPtr(&condVarData)
{
}

WaitSet对象构造完成,核心就是拿到一个ConditionVariableData对象用于进程间的数据同步(publisher进程和subscriber进程),接下来一个核心问题是怎么将该对象传递给publisher进程。

继续看main函数:

// create subscriber and subscribe them to our service
    //! [create subscribers]
    iox::vector<iox::popo::UntypedSubscriber, NUMBER_OF_SUBSCRIBERS> subscriberVector;
    for (auto i = 0U; i < NUMBER_OF_SUBSCRIBERS; ++i)
    {
        subscriberVector.emplace_back(iox::capro::ServiceDescription{"Radar", "FrontLeft", "Counter"});
    }
    //! [create subscribers]

    constexpr uint64_t FIRST_GROUP_ID = 123U;
    constexpr uint64_t SECOND_GROUP_ID = 456U;

    //! [configure subscribers]
    // attach the first two subscribers to waitset with a id of FIRST_GROUP_ID
    for (auto i = 0U; i < NUMBER_OF_SUBSCRIBERS / 2; ++i)
    {
        waitset.attachState(subscriberVector[i], iox::popo::SubscriberState::HAS_DATA, FIRST_GROUP_ID)
            .or_else([&](auto) {
                std::cerr << "failed to attach subscriber" << i << std::endl;
                std::exit(EXIT_FAILURE);
            });
    }

    // attach the remaining subscribers to waitset with a id of SECOND_GROUP_ID
    for (auto i = NUMBER_OF_SUBSCRIBERS / 2; i < NUMBER_OF_SUBSCRIBERS; ++i)
    {
        waitset.attachState(subscriberVector[i], iox::popo::SubscriberState::HAS_DATA, SECOND_GROUP_ID)
            .or_else([&](auto) {
                std::cerr << "failed to attach subscriber" << i << std::endl;
                std::exit(EXIT_FAILURE);
            });
    }

调用attachState接口将WaitSet对象和subscriber对象及特定状态关联起来。attachState接口的定义:

template <uint64_t Capacity>
template <typename T, typename StateType, typename ContextDataType, typename>
inline expected<void, WaitSetError>
WaitSet<Capacity>::attachState(T& stateOrigin,
                               const StateType stateType,
                               const uint64_t id,
                               const NotificationCallback<T, ContextDataType>& stateCallback) noexcept
{
    static_assert(IS_STATE_ENUM<StateType>, "Only enums with an underlying StateEnumIdentifier are allowed.");
    auto hasTriggeredCallback = NotificationAttorney::getCallbackForIsStateConditionSatisfied(stateOrigin, stateType);  (1)

    return attachImpl(stateOrigin,
                      hasTriggeredCallback,
                      id,
                      stateCallback,
                      static_cast<uint64_t>(stateType),
                      typeid(StateType).hash_code())
        .and_then([&](auto& uniqueId) {
            NotificationAttorney::enableState(
                stateOrigin,
                TriggerHandle(*m_conditionVariableDataPtr, {*this, &WaitSet::removeTrigger}, uniqueId),
                stateType);

            auto& trigger = m_triggerArray[uniqueId];
            if (trigger->isStateConditionSatisfied())
            {
                ConditionNotifier(*m_conditionVariableDataPtr, uniqueId).notify();
            }
        });   (2)
}

(1)得到一个被唤醒后检查状态是否满足的回调函数:

template <typename T, typename... Targs>
inline WaitSetIsConditionSatisfiedCallback
NotificationAttorney::getCallbackForIsStateConditionSatisfied(T& eventOrigin, Targs&&... args) noexcept
{
    return eventOrigin.getCallbackForIsStateConditionSatisfied(std::forward<Targs>(args)...);
}

NotificationAttorney只是Subscriber类的代理,继续调BaseSubscriber类的对应接口:

template <typename port_t>
inline WaitSetIsConditionSatisfiedCallback
BaseSubscriber<port_t>::getCallbackForIsStateConditionSatisfied(const SubscriberState subscriberState) const noexcept
{
    switch (subscriberState)
    {
    case SubscriberState::HAS_DATA:
        return WaitSetIsConditionSatisfiedCallback(in_place, *this, &SelfType::hasData);
    }
    return nullopt;
}

基于BaseSubscriber类成员函数hasData()构造一个类型为function<bool()>的可调用对象,hasData()的实现:

template <typename port_t>
inline bool BaseSubscriber<port_t>::hasData() const noexcept
{
    return m_port.hasNewChunks();
}

最终调用subscriber专属队列的empty()接口判断队列是否为空,是否有新的数据到来。

(2)attachImpl的实现:

template <uint64_t Capacity>
template <typename T, typename ContextDataType>
inline expected<uint64_t, WaitSetError>
WaitSet<Capacity>::attachImpl(T& eventOrigin,
                              const WaitSetIsConditionSatisfiedCallback& hasTriggeredCallback,
                              const uint64_t eventId,
                              const NotificationCallback<T, ContextDataType>& eventCallback,
                              const uint64_t originType,
                              const uint64_t originTypeHash) noexcept
{
    for (auto& currentTrigger : m_triggerArray)
    {
        if (currentTrigger && currentTrigger->isLogicalEqualTo(&eventOrigin, originType, originTypeHash))
        {
            return err(WaitSetError::ALREADY_ATTACHED);
        }
    }

    function<void(uint64_t)> invalidationCallback = NotificationAttorney::getInvalidateTriggerMethod(eventOrigin);
    auto index = m_indexRepository.pop();
    if (!index)
    {
        return err(WaitSetError::WAIT_SET_FULL);
    }


    if (hasTriggeredCallback)
    {
        m_triggerArray[*index].emplace(StateBasedTrigger,
                                       &eventOrigin,
                                       *hasTriggeredCallback,
                                       invalidationCallback,
                                       eventId,
                                       eventCallback,
                                       *index,
                                       originType,
                                       originTypeHash);
    }
    else
    {
        m_triggerArray[*index].emplace(EventBasedTrigger,
                                       &eventOrigin,
                                       invalidationCallback,
                                       eventId,
                                       eventCallback,
                                       *index,
                                       originType,
                                       originTypeHash);
    }

    return ok(*index);
}

auto index = m_indexRepository.pop();这行语句的作用是从缓存队列中(栈结构)pop出一个索引,将该索引赋给加入的subscriber对象的特定状态。根据demo程序,最新加入WaitSet的subscriber SubscriberState::HAS_DATA状态的索引为3,最后加入的索引为0(四个subscriber的同一个状态)。

m_triggerArray在WaitSet中的定义:

class WaitSet
{
  public:
    static constexpr uint64_t CAPACITY = Capacity;
    using TriggerArray = optional<Trigger>[Capacity];

  private:
    TriggerArray m_triggerArray;
};

在m_triggerArray对应的索引上构造Trigger对象,Trigger的构造函数:

template <typename T, typename ContextDataType>
inline Trigger::Trigger(T* const notificationOrigin,
                        const function<bool()>& hasTriggeredCallback,
                        const function<void(uint64_t)>& resetCallback,
                        const uint64_t notificationId,
                        const NotificationCallback<T, ContextDataType>& callback,
                        const uint64_t uniqueId,
                        const TriggerType triggerType,
                        const uint64_t originTriggerType,
                        const uint64_t originTriggerTypeHash) noexcept
    : m_notificationInfo(notificationOrigin, notificationId, callback)
    , m_hasTriggeredCallback(hasTriggeredCallback)
    , m_resetCallback(resetCallback)
    , m_uniqueId(uniqueId)
    , m_triggerType(triggerType)
    , m_originTriggerType(originTriggerType)
    , m_originTriggerTypeHash(originTriggerTypeHash)
{
}

各参数的含义:

stateOrigin:指向Subscriber对象的指针

hasTriggeredCallback:判断该trigger是否真的被triggered的回调函数

resetCallback:该trigger超出作用域时的回调函数

notificationId:subscriber对象该状态的组ID

callback:subscriber确认状态发生后的回调函数

uniqueId:该subscriber对象状态的唯一索引

stateType:该状态的枚举值

stateTypeHash:该状态的枚举类型

Trigger对象构造时还构造了NotificationInfo对象,构造函数:

template <typename T, typename ContextDataType>
inline NotificationInfo::NotificationInfo(T* const notificationOrigin,
                                          const uint64_t notificationId,
                                          const NotificationCallback<T, ContextDataType>& callback) noexcept
    : m_notificationOrigin(notificationOrigin)
    , m_userValue(callback.m_contextData)
    , m_notificationOriginTypeHash(typeid(T).hash_code())
    , m_notificationId(notificationId)
    , m_callbackPtr(reinterpret_cast<internal::GenericCallbackPtr_t>(callback.m_callback))
    , m_callback(internal::TranslateAndCallTypelessCallback<T, ContextDataType>::call)
{
}

不要忘了,得到Subscriber对象特定状态的索引值后还会传入回调函数中进行处理:

template <uint64_t Capacity>
template <typename T, typename StateType, typename ContextDataType, typename>
inline expected<void, WaitSetError>
WaitSet<Capacity>::attachState(T& stateOrigin,
                               const StateType stateType,
                               const uint64_t id,
                               const NotificationCallback<T, ContextDataType>& stateCallback) noexcept
{
    static_assert(IS_STATE_ENUM<StateType>, "Only enums with an underlying StateEnumIdentifier are allowed.");
    auto hasTriggeredCallback = NotificationAttorney::getCallbackForIsStateConditionSatisfied(stateOrigin, stateType);

    return attachImpl(stateOrigin,
                      hasTriggeredCallback,
                      id,
                      stateCallback,
                      static_cast<uint64_t>(stateType),
                      typeid(StateType).hash_code())
        .and_then([&](auto& uniqueId) {
            NotificationAttorney::enableState(
                stateOrigin,
                TriggerHandle(*m_conditionVariableDataPtr, {*this, &WaitSet::removeTrigger}, uniqueId),
                stateType);             (1)

            auto& trigger = m_triggerArray[uniqueId];
            if (trigger->isStateConditionSatisfied())
            {
                ConditionNotifier(*m_conditionVariableDataPtr, uniqueId).notify();  (2)
            }
        });
}

(1)构造了TriggerHandle对象,构造函数的参数有指向ConditionVariableData对象的指针、该Subscriber对象特定状态的索引等,似乎是要传给Publisher对象:

TriggerHandle::TriggerHandle(ConditionVariableData& conditionVariableData,
                             const function<void(uint64_t)>& resetCallback,
                             const uint64_t uniqueTriggerId) noexcept
    : m_conditionVariableDataPtr(&conditionVariableData)
    , m_resetCallback(resetCallback)
    , m_uniqueTriggerId(uniqueTriggerId)
{
}

调用NotificationAttorney::enableState,最终调用BaseSubscriber<port_t>::enableState:

template <typename port_t>
inline void BaseSubscriber<port_t>::enableState(iox::popo::TriggerHandle&& triggerHandle,
                                                [[maybe_unused]] const SubscriberState subscriberState) noexcept

{
    switch (subscriberState)
    {
    case SubscriberState::HAS_DATA:
        if (m_trigger)
        {
            IOX_LOG(
                Warn,
                "The subscriber is already attached with either the SubscriberState::HAS_DATA or "
                "SubscriberEvent::DATA_RECEIVED to a WaitSet/Listener. Detaching it from previous one and "
                "attaching it to the new one with SubscriberState::HAS_DATA. Best practice is to call detach first.");

            IOX_REPORT(
                PoshError::POPO__BASE_SUBSCRIBER_OVERRIDING_WITH_STATE_SINCE_HAS_DATA_OR_DATA_RECEIVED_ALREADY_ATTACHED,
                iox::er::RUNTIME_ERROR);
        }
        m_trigger = std::move(triggerHandle);
        m_port.setConditionVariable(*m_trigger.getConditionVariableData(), m_trigger.getUniqueId());
        break;
    }
}

m_port的类型为SubscriberPortUser:

void SubscriberPortUser::setConditionVariable(ConditionVariableData& conditionVariableData,
                                              const uint64_t notificationIndex) noexcept
{
    m_chunkReceiver.setConditionVariable(conditionVariableData, notificationIndex);
}

  m_chunkReceiver的类型为ChunkReceiver,ChunkReceiver继承自ChunkQueuePopper,所以实际调用ChunkQueuePopper<ChunkQueueDataType>::setConditionVariable:

template <typename ChunkQueueDataType>
inline void ChunkQueuePopper<ChunkQueueDataType>::setConditionVariable(ConditionVariableData& conditionVariableDataRef,
                                                                       const uint64_t notificationIndex) noexcept
{
    typename MemberType_t::LockGuard_t lock(*getMembers());

    getMembers()->m_conditionVariableDataPtr = &conditionVariableDataRef;
    getMembers()->m_conditionVariableNotificationIndex.emplace(notificationIndex);
}

ChunkQueuePopper的MemberType_t的实际类型为ChunkQueueData:

template <typename ChunkQueueDataProperties, typename LockingPolicy>
struct ChunkQueueData : public LockingPolicy
{
    RelativePointer<ConditionVariableData> m_conditionVariableDataPtr;
    optional<uint64_t> m_conditionVariableNotificationIndex;
};

即ConditionVariableData对象和Subscriber对象特定状态对应的Index被存储在Subscriber对象的专属队列中,貌似Publisher对象仍然没有拿到这两个值。其实不然,回忆一下Publisher对象发布数据的过程:

template <typename ChunkDistributorDataType>
inline uint64_t ChunkDistributor<ChunkDistributorDataType>::deliverToAllStoredQueues(mepoo::SharedChunk chunk) noexcept
{
    ------------------
    pushToQueue(queue.get(), chunk)
    ------------------
}

template <typename ChunkDistributorDataType>
inline bool ChunkDistributor<ChunkDistributorDataType>::pushToQueue(not_null<ChunkQueueData_t* const> queue,
                                                                    mepoo::SharedChunk chunk) noexcept
{
    return ChunkQueuePusher_t(queue).push(chunk);
}

template <typename ChunkQueueDataType>
inline bool ChunkQueuePusher<ChunkQueueDataType>::push(mepoo::SharedChunk chunk) noexcept
{
    auto pushRet = getMembers()->m_queue.push(chunk);
    bool hasQueueOverflow = false;

    // drop the chunk if one is returned by an overflow
    if (pushRet.has_value())
    {
        pushRet.value().releaseToSharedChunk();
        // tell the ChunkDistributor that we had an overflow and dropped a sample
        hasQueueOverflow = true;
    }

    {
        typename MemberType_t::LockGuard_t lock(*getMembers());
        if (getMembers()->m_conditionVariableDataPtr)
        {
            ConditionNotifier(*getMembers()->m_conditionVariableDataPtr.get(),
                              *getMembers()->m_conditionVariableNotificationIndex)
                .notify();
        }
    }

    return !hasQueueOverflow;
}

Publisher对象向Subscriber专属队列放置数据后,会用Subscriber专属队列的数据成员m_conditionVariableDataPtr和m_conditionVariableNotificationIndex构造一个ConditionNotifier对象,然后调用ConditionNotifier对象的notify()通知publisher进程有数据发布了。ConditionNotifier的构造函数:

ConditionNotifier::ConditionNotifier(ConditionVariableData& condVarDataRef, const uint64_t index) noexcept
    : m_condVarDataPtr(&condVarDataRef)
    , m_notificationIndex(index)
{
    if (index >= MAX_NUMBER_OF_NOTIFIERS)
    {
        IOX_LOG(Fatal,
                "The provided index " << index << " is too large. The index has to be in the range of [0, "
                                      << MAX_NUMBER_OF_NOTIFIERS << "[.");
        IOX_REPORT_FATAL(PoshError::POPO__CONDITION_NOTIFIER_INDEX_TOO_LARGE);
    }
}

简单的值拷贝。notify()的实现:

void ConditionNotifier::notify() noexcept
{
    getMembers()->m_activeNotifications[m_notificationIndex].store(true, std::memory_order_release);
    getMembers()->m_wasNotified.store(true, std::memory_order_relaxed);
    getMembers()->m_semaphore->post().or_else(
        [](auto) { IOX_REPORT_FATAL(PoshError::POPO__CONDITION_NOTIFIER_SEMAPHORE_CORRUPT_IN_NOTIFY); });
}

将ConditionVariableData对应的状态索引为置位,然后通知publisher进程该状态发生了。

继续看main()函数:

while (keepRunning)
    {
        auto notificationVector = waitset.wait();

        for (auto& notification : notificationVector)
        {
            //! [data path]
            // we print the received data for the first group
            if (notification->getNotificationId() == FIRST_GROUP_ID)
            {
                auto subscriber = notification->getOrigin<iox::popo::UntypedSubscriber>();
                subscriber->take().and_then([&](auto& userPayload) {
                    const CounterTopic* data = static_cast<const CounterTopic*>(userPayload);
                    auto flags = std::cout.flags();
                    std::cout << "received: " << std::dec << data->counter << std::endl;
                    std::cout.setf(flags);
                    subscriber->release(userPayload);
                });
            }
            // dismiss the received data for the second group
            else if (notification->getNotificationId() == SECOND_GROUP_ID)
            {
                std::cout << "dismiss data\n";
                auto subscriber = notification->getOrigin<iox::popo::UntypedSubscriber>();
                // We need to release the data to reset the trigger hasData
                // otherwise the WaitSet would notify us in 'waitset.wait()' again
                // instantly.
                subscriber->releaseQueuedData();
            }
            //! [data path]
        }

wait()的实现:

template <uint64_t Capacity>
inline typename WaitSet<Capacity>::NotificationInfoVector WaitSet<Capacity>::wait() noexcept
{
    return waitAndReturnTriggeredTriggers([this] { return this->m_conditionListener.wait(); });
}

ConditionListener::wait():

ConditionListener::NotificationVector_t ConditionListener::wait() noexcept
{
    return waitImpl([this]() -> bool {
        if (this->getMembers()->m_semaphore->wait().has_error())
        {
            IOX_REPORT_FATAL(PoshError::POPO__CONDITION_LISTENER_SEMAPHORE_CORRUPTED_IN_WAIT);
            return false;
        }
        return true;
    });
}

waitImpl的传入参数是一个可调用对象,该可调用对象调用信号量的wait()接口等待信号量。waitImpl的实现:

ConditionListener::NotificationVector_t ConditionListener::waitImpl(const function_ref<bool()> waitCall) noexcept
{
    using Type_t = iox::BestFittingType_t<iox::MAX_NUMBER_OF_EVENTS_PER_LISTENER>;
    NotificationVector_t activeNotifications;       (1)

    resetSemaphore();
    bool doReturnAfterNotificationCollection = false;
    while (!m_toBeDestroyed.load(std::memory_order_relaxed))
    {
        for (Type_t i = 0U; i < MAX_NUMBER_OF_NOTIFIERS; i++)
        {
            if (getMembers()->m_activeNotifications[i].load(std::memory_order_relaxed))
            {
                resetUnchecked(i);
                activeNotifications.emplace_back(i);      (2)
            }
        }
        if (!activeNotifications.empty() || doReturnAfterNotificationCollection)     (3)
        {
            return activeNotifications;
        }

        doReturnAfterNotificationCollection = !waitCall();             (4)
    }

    return activeNotifications;
}

(1)中NotificationVector_t 为一个存储索引值的容器(BestFittingType_t的设计请参考一种容器存储类型的自适应实现方法-CSDN博客):

using NotificationVector_t = vector<BestFittingType_t<MAX_NUMBER_OF_NOTIFIERS>, MAX_NUMBER_OF_NOTIFIERS>;

(2)收集状态发生时该状态对应的索引值

(3)如果有状态发生或者立即返回标志为置位立即返回

(4)如果信号量产生了错误,将立即返回标志为置位

waitAndReturnTriggeredTriggers()的实现:

template <uint64_t Capacity>
inline typename WaitSet<Capacity>::NotificationInfoVector
WaitSet<Capacity>::waitAndReturnTriggeredTriggers(const WaitFunction& wait) noexcept
{
    if (m_conditionListener.wasNotified())
    {
        this->acquireNotifications(wait);                                    
    }

    NotificationInfoVector triggers = createVectorWithTriggeredTriggers();   

    if (!triggers.empty())
    {
        return triggers;
    }

    acquireNotifications(wait);
    return createVectorWithTriggeredTriggers();
}

关键是createVectorWithTriggeredTriggers():

template <uint64_t Capacity>
inline typename WaitSet<Capacity>::NotificationInfoVector
WaitSet<Capacity>::createVectorWithTriggeredTriggers() noexcept
{
    NotificationInfoVector triggers;
    if (!m_activeNotifications.empty())
    {
        for (uint64_t i = m_activeNotifications.size() - 1U;; --i)
        {
            auto index = m_activeNotifications[i];
            auto& trigger = m_triggerArray[index];
            bool doRemoveNotificationId = !static_cast<bool>(trigger);

            if (!doRemoveNotificationId && trigger->isStateConditionSatisfied())  (1)
            {
                IOX_ENFORCE(triggers.push_back(&m_triggerArray[index]->getNotificationInfo()),
                            "Adding trigger to the notification vector!");
                doRemoveNotificationId = (trigger->getTriggerType() == TriggerType::EVENT_BASED);
            }

            if (doRemoveNotificationId)
            {
                m_activeNotifications.erase(m_activeNotifications.begin() + i);
            }

            if (i == 0U)
            {
                break;
            }
        }
    }

    return triggers;
}

(1)若状态置位且根据回调函数判断状态确实发生了,则根据该状态的trigger获取该状态对应的NotificationInfo指针。

最后看看main()函数针对不同组ID的处理:

while (keepRunning)
    {
        auto notificationVector = waitset.wait();

        for (auto& notification : notificationVector)
        {
            //! [data path]
            // we print the received data for the first group
            if (notification->getNotificationId() == FIRST_GROUP_ID)
            {
                auto subscriber = notification->getOrigin<iox::popo::UntypedSubscriber>();   (1)
                subscriber->take().and_then([&](auto& userPayload) {
                    const CounterTopic* data = static_cast<const CounterTopic*>(userPayload);
                    auto flags = std::cout.flags();
                    std::cout << "received: " << std::dec << data->counter << std::endl;
                    std::cout.setf(flags);
                    subscriber->release(userPayload);
                });
            }
            // dismiss the received data for the second group
            else if (notification->getNotificationId() == SECOND_GROUP_ID)
            {
                std::cout << "dismiss data\n";
                auto subscriber = notification->getOrigin<iox::popo::UntypedSubscriber>();
                // We need to release the data to reset the trigger hasData
                // otherwise the WaitSet would notify us in 'waitset.wait()' again
                // instantly.
                subscriber->releaseQueuedData();
            }
            //! [data path]
        }

        std::cout << std::endl;
    }

(1)根据指针NotificationInfo获取该状态对应的subscriber对象:

template <typename T>
inline T* NotificationInfo::getOrigin() const noexcept
{
    if (m_notificationOriginTypeHash != typeid(T).hash_code())
    {
        IOX_REPORT(PoshError::POPO__NOTIFICATION_INFO_TYPE_INCONSISTENCY_IN_GET_ORIGIN, iox::er::RUNTIME_ERROR);
        return nullptr;
    }

    return static_cast<T*>(m_notificationOrigin);
}

根据subscriber类的hash code进行匹配。获取到subscriber对象后,利用该对象获取发布的数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值