在前面的系列文章中,以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对象后,利用该对象获取发布的数据。