libxr
Embedded C++ framework for bare-metal, RTOS and Linux
Loading...
Searching...
No Matches
linux_shared_topic_impl.hpp
1#pragma once
2
3#if defined(LIBXR_SYSTEM_POSIX_HOST)
4
5#include <fcntl.h>
6#include <linux/futex.h>
7#include <signal.h>
8#include <sys/mman.h>
9#include <sys/stat.h>
10#include <sys/syscall.h>
11#include <time.h>
12#include <unistd.h>
13
14#include <atomic>
15#include <cerrno>
16#include <chrono>
17#include <cinttypes>
18#include <cstddef>
19#include <cstdint>
20#include <cstdio>
21#include <cstring>
22#include <fstream>
23#include <memory>
24#include <sstream>
25#include <string>
26#include <type_traits>
27
28#include "crc.hpp"
29#include "libxr_def.hpp"
30#include "message.hpp"
31
32namespace LibXR
33{
38enum class LinuxSharedSubscriberMode : uint8_t
39{
40 BROADCAST_FULL = 0,
41 BROADCAST_DROP_OLD = 1,
43 BALANCE_RR = 2,
44};
45
50struct LinuxSharedTopicConfig
51{
52 uint32_t slot_num = 64;
53 uint32_t subscriber_num = 8;
54 uint32_t queue_num =
55 64;
56};
57
73template <typename TopicData>
74class LinuxSharedTopic : public Topic
75{
76 static_assert(std::is_trivially_copyable<TopicData>::value,
77 "LinuxSharedTopic requires trivially copyable data");
78 static_assert(std::atomic<uint32_t>::is_always_lock_free,
79 "LinuxSharedTopic requires lock-free 32-bit atomics");
80 static_assert(std::atomic<uint64_t>::is_always_lock_free,
81 "LinuxSharedTopic requires lock-free 64-bit atomics");
82
83 enum class SharedDataState : uint8_t
84 {
85 EMPTY = 0,
86 PUBLISHER = 1,
87 SUBSCRIBER = 2,
88 };
89
90 public:
91 class SharedData;
92 using Data = SharedData;
93 static constexpr const char* DEFAULT_DOMAIN_NAME = "libxr_def_domain";
94
101 class Subscriber
102 {
103 public:
104 using Data = SharedData;
105
110 Subscriber() = default;
111
120 explicit Subscriber(const char* name, LinuxSharedSubscriberMode mode =
121 LinuxSharedSubscriberMode::BROADCAST_FULL)
122 : owned_topic_(new LinuxSharedTopic(name))
123 {
124 if (Attach(*owned_topic_, mode) != ErrorCode::OK)
125 {
126 delete owned_topic_;
127 owned_topic_ = nullptr;
128 }
129 }
130
131 Subscriber(const char* name, const char* domain_name,
132 LinuxSharedSubscriberMode mode = LinuxSharedSubscriberMode::BROADCAST_FULL)
133 : owned_topic_(new LinuxSharedTopic(name, domain_name))
134 {
135 if (Attach(*owned_topic_, mode) != ErrorCode::OK)
136 {
137 delete owned_topic_;
138 owned_topic_ = nullptr;
139 }
140 }
141
142 Subscriber(const char* name, Topic::Domain& domain,
143 LinuxSharedSubscriberMode mode = LinuxSharedSubscriberMode::BROADCAST_FULL)
144 : owned_topic_(new LinuxSharedTopic(name, domain))
145 {
146 if (Attach(*owned_topic_, mode) != ErrorCode::OK)
147 {
148 delete owned_topic_;
149 owned_topic_ = nullptr;
150 }
151 }
152
159 explicit Subscriber(
160 LinuxSharedTopic& topic,
161 LinuxSharedSubscriberMode mode = LinuxSharedSubscriberMode::BROADCAST_FULL)
162 {
163 (void)Attach(topic, mode);
164 }
165
169 ~Subscriber() { Reset(); }
170
171 Subscriber(const Subscriber&) = delete;
172 Subscriber& operator=(const Subscriber&) = delete;
173
174 Subscriber(Subscriber&& other) noexcept { *this = std::move(other); }
175
176 Subscriber& operator=(Subscriber&& other) noexcept
177 {
178 if (this == &other)
179 {
180 return *this;
181 }
182
183 Reset();
184
185 topic_ = other.topic_;
186 owned_topic_ = other.owned_topic_;
187 subscriber_index_ = other.subscriber_index_;
188 current_slot_index_ = other.current_slot_index_;
189 current_sequence_ = other.current_sequence_;
190 current_timestamp_ = other.current_timestamp_;
191
192 other.topic_ = nullptr;
193 other.owned_topic_ = nullptr;
194 other.subscriber_index_ = INVALID_INDEX;
195 other.current_slot_index_ = INVALID_INDEX;
196 other.current_sequence_ = 0;
197 other.current_timestamp_ = MicrosecondTimestamp();
198 return *this;
199 }
200
206 bool Valid() const { return topic_ != nullptr && subscriber_index_ != INVALID_INDEX; }
207
215 ErrorCode Wait(uint32_t timeout_ms = UINT32_MAX)
216 {
217 if (!Valid())
218 {
219 return ErrorCode::STATE_ERR;
220 }
221
222 const uint64_t deadline_ms =
223 (timeout_ms == UINT32_MAX) ? 0 : (NowMonotonicMs() + timeout_ms);
224
225 Descriptor desc = {};
226 while (true)
227 {
228 ErrorCode pop_ans = topic_->TryPopDescriptor(subscriber_index_, desc);
229 if (pop_ans == ErrorCode::OK)
230 {
231 Release();
232 topic_->HoldSlot(subscriber_index_, desc.slot_index);
233 current_slot_index_ = desc.slot_index;
234 current_sequence_ = desc.sequence;
235 current_timestamp_ = topic_->SlotTimestamp(desc.slot_index);
236 return ErrorCode::OK;
237 }
238
239 uint32_t wait_ms = UINT32_MAX;
240 if (timeout_ms != UINT32_MAX)
241 {
242 const uint64_t now_ms = NowMonotonicMs();
243 if (now_ms >= deadline_ms)
244 {
245 return ErrorCode::TIMEOUT;
246 }
247 wait_ms = static_cast<uint32_t>(deadline_ms - now_ms);
248 }
249
250 const ErrorCode wait_ans =
251 topic_->WaitReady(topic_->subscribers_[subscriber_index_], wait_ms);
252 if (wait_ans == ErrorCode::OK)
253 {
254 continue;
255 }
256 return wait_ans;
257 }
258 }
259
268 ErrorCode Wait(SharedData& data, uint32_t timeout_ms = UINT32_MAX)
269 {
270 data.Reset();
271
272 if (!Valid())
273 {
274 return ErrorCode::STATE_ERR;
275 }
276
277 const uint64_t deadline_ms =
278 (timeout_ms == UINT32_MAX) ? 0 : (NowMonotonicMs() + timeout_ms);
279
280 Descriptor desc = {};
281 while (true)
282 {
283 ErrorCode pop_ans = topic_->TryPopDescriptor(subscriber_index_, desc);
284 if (pop_ans == ErrorCode::OK)
285 {
286 data.topic_ = topic_;
287 data.slot_index_ = desc.slot_index;
288 data.sequence_ = desc.sequence;
289 data.state_ = SharedDataState::SUBSCRIBER;
290 data.subscriber_index_ = subscriber_index_;
291 topic_->HoldSlot(subscriber_index_, desc.slot_index);
292 return ErrorCode::OK;
293 }
294
295 uint32_t wait_ms = UINT32_MAX;
296 if (timeout_ms != UINT32_MAX)
297 {
298 const uint64_t now_ms = NowMonotonicMs();
299 if (now_ms >= deadline_ms)
300 {
301 return ErrorCode::TIMEOUT;
302 }
303 wait_ms = static_cast<uint32_t>(deadline_ms - now_ms);
304 }
305
306 const ErrorCode wait_ans =
307 topic_->WaitReady(topic_->subscribers_[subscriber_index_], wait_ms);
308 if (wait_ans == ErrorCode::OK)
309 {
310 continue;
311 }
312 return wait_ans;
313 }
314 }
315
321 TopicData* GetData() const
322 {
323 if (!Valid() || current_slot_index_ == INVALID_INDEX)
324 {
325 return nullptr;
326 }
327
328 return &topic_->payloads_[current_slot_index_];
329 }
330
334 uint64_t GetSequence() const { return current_sequence_; }
335
339 MicrosecondTimestamp GetTimestamp() const { return current_timestamp_; }
340
344 uint32_t GetPendingNum() const
345 {
346 if (!Valid())
347 {
348 return 0;
349 }
350
351 const SubscriberControl& control = topic_->subscribers_[subscriber_index_];
352 const uint32_t head = control.queue_head.load(std::memory_order_acquire);
353 const uint32_t tail = control.queue_tail.load(std::memory_order_acquire);
354 if (tail >= head)
355 {
356 return tail - head;
357 }
358 return topic_->queue_capacity_ - (head - tail);
359 }
360
365 uint64_t GetDropNum() const
366 {
367 if (!Valid())
368 {
369 return 0;
370 }
371
372 return topic_->subscribers_[subscriber_index_].dropped_messages.load(
373 std::memory_order_acquire);
374 }
375
379 void Release()
380 {
381 if (!Valid() || current_slot_index_ == INVALID_INDEX)
382 {
383 return;
384 }
385
386 topic_->ClearHeldSlot(subscriber_index_, current_slot_index_);
387 topic_->ReleaseSlot(current_slot_index_);
388 current_slot_index_ = INVALID_INDEX;
389 current_sequence_ = 0;
390 current_timestamp_ = MicrosecondTimestamp();
391 }
392
397 void Reset()
398 {
399 if (!Valid())
400 {
401 return;
402 }
403
404 topic_->UnregisterBalancedSubscriber(subscriber_index_);
405 topic_->subscribers_[subscriber_index_].active.store(0, std::memory_order_release);
406 topic_->subscribers_[subscriber_index_].owner_pid.store(0,
407 std::memory_order_release);
408 topic_->subscribers_[subscriber_index_].owner_starttime.store(
409 0, std::memory_order_release);
410
411 Descriptor desc = {};
412 while (topic_->TryPopDescriptor(subscriber_index_, desc) == ErrorCode::OK)
413 {
414 topic_->ReleaseSlot(desc.slot_index);
415 }
416
417 Release();
418
419 topic_ = nullptr;
420 delete owned_topic_;
421 owned_topic_ = nullptr;
422 subscriber_index_ = INVALID_INDEX;
423 current_slot_index_ = INVALID_INDEX;
424 current_sequence_ = 0;
425 current_timestamp_ = MicrosecondTimestamp();
426 }
427
428 private:
429 ErrorCode Attach(LinuxSharedTopic& topic, LinuxSharedSubscriberMode mode)
430 {
431 Reset();
432
433 if (!topic.Valid())
434 {
435 return ErrorCode::STATE_ERR;
436 }
437
438 if (topic.self_identity_.starttime == 0)
439 {
440 return ErrorCode::STATE_ERR;
441 }
442
443 for (uint32_t i = 0; i < topic.subscriber_capacity_; ++i)
444 {
445 uint32_t expected = 0;
446 auto& active = topic.subscribers_[i].active;
447 if (active.compare_exchange_strong(expected, 1, std::memory_order_acq_rel,
448 std::memory_order_relaxed))
449 {
450 topic.subscribers_[i].queue_head.store(0, std::memory_order_release);
451 topic.subscribers_[i].queue_tail.store(0, std::memory_order_release);
452 topic.subscribers_[i].ready_signal.store(0, std::memory_order_release);
453 topic.subscribers_[i].dropped_messages.store(0, std::memory_order_release);
454 topic.subscribers_[i].owner_pid.store(topic.self_identity_.pid,
455 std::memory_order_release);
456 topic.subscribers_[i].owner_starttime.store(topic.self_identity_.starttime,
457 std::memory_order_release);
458 topic.subscribers_[i].held_slot.store(INVALID_INDEX, std::memory_order_release);
459 topic.subscribers_[i].mode.store(static_cast<uint32_t>(mode),
460 std::memory_order_release);
461 if (mode == LinuxSharedSubscriberMode::BALANCE_RR)
462 {
463 const ErrorCode join_ans = topic.RegisterBalancedSubscriber(i);
464 if (join_ans != ErrorCode::OK)
465 {
466 topic.subscribers_[i].active.store(0, std::memory_order_release);
467 topic.subscribers_[i].owner_pid.store(0, std::memory_order_release);
468 topic.subscribers_[i].owner_starttime.store(0, std::memory_order_release);
469 topic.subscribers_[i].mode.store(
470 static_cast<uint32_t>(LinuxSharedSubscriberMode::BROADCAST_FULL),
471 std::memory_order_release);
472 return join_ans;
473 }
474 }
475 topic_ = &topic;
476 subscriber_index_ = i;
477 current_slot_index_ = INVALID_INDEX;
478 current_sequence_ = 0;
479 current_timestamp_ = MicrosecondTimestamp();
480 return ErrorCode::OK;
481 }
482 }
483
484 return ErrorCode::FULL;
485 }
486
487 LinuxSharedTopic* topic_ = nullptr;
488 LinuxSharedTopic* owned_topic_ = nullptr;
489 uint32_t subscriber_index_ = INVALID_INDEX;
490 uint32_t current_slot_index_ = INVALID_INDEX;
491 uint64_t current_sequence_ = 0;
492 MicrosecondTimestamp current_timestamp_;
493 };
494
504 class SharedData
505 {
506 public:
510 SharedData() = default;
511
516 ~SharedData() { Reset(); }
517
518 SharedData(const SharedData&) = delete;
519 SharedData& operator=(const SharedData&) = delete;
520
521 SharedData(SharedData&& other) noexcept { *this = std::move(other); }
522
526 SharedData& operator=(SharedData&& other) noexcept
527 {
528 if (this == &other)
529 {
530 return *this;
531 }
532
533 Reset();
534
535 topic_ = other.topic_;
536 slot_index_ = other.slot_index_;
537 sequence_ = other.sequence_;
538 state_ = other.state_;
539 subscriber_index_ = other.subscriber_index_;
540
541 other.topic_ = nullptr;
542 other.slot_index_ = INVALID_INDEX;
543 other.sequence_ = 0;
544 other.state_ = SharedDataState::EMPTY;
545 other.subscriber_index_ = INVALID_INDEX;
546 return *this;
547 }
548
552 bool Valid() const { return topic_ != nullptr && slot_index_ != INVALID_INDEX; }
553
557 bool Empty() const { return !Valid(); }
558
562 uint64_t GetSequence() const { return sequence_; }
563
567 MicrosecondTimestamp GetTimestamp() const
568 {
569 if (!Valid() || state_ != SharedDataState::SUBSCRIBER)
570 {
571 return MicrosecondTimestamp();
572 }
573 return topic_->SlotTimestamp(slot_index_);
574 }
575
581 TopicData* GetData()
582 {
583 if (!Valid())
584 {
585 return nullptr;
586 }
587 return &topic_->payloads_[slot_index_];
588 }
589
595 TopicData* GetData() const
596 {
597 if (!Valid())
598 {
599 return nullptr;
600 }
601 return &topic_->payloads_[slot_index_];
602 }
603
607 void Reset()
608 {
609 if (!Valid())
610 {
611 return;
612 }
613
614 if (state_ == SharedDataState::PUBLISHER)
615 {
616 topic_->RecycleSlot(slot_index_);
617 }
618 else if (state_ == SharedDataState::SUBSCRIBER)
619 {
620 topic_->ClearHeldSlot(subscriber_index_, slot_index_);
621 topic_->ReleaseSlot(slot_index_);
622 }
623 topic_ = nullptr;
624 slot_index_ = INVALID_INDEX;
625 sequence_ = 0;
626 state_ = SharedDataState::EMPTY;
627 subscriber_index_ = INVALID_INDEX;
628 }
629
630 private:
631 friend class LinuxSharedTopic<TopicData>;
632 friend class Subscriber;
633
634 LinuxSharedTopic* topic_ = nullptr;
635 uint32_t slot_index_ = INVALID_INDEX;
636 uint64_t sequence_ = 0;
637 SharedDataState state_ = SharedDataState::EMPTY;
638 uint32_t subscriber_index_ = INVALID_INDEX;
639 };
640
645 explicit LinuxSharedTopic(const char* topic_name)
646 : LinuxSharedTopic(topic_name, DEFAULT_DOMAIN_NAME)
647 {
648 }
649
650 LinuxSharedTopic(const char* topic_name, const char* domain_name)
651 : create_(false),
652 publisher_(false),
653 config_(),
654 domain_crc32_(ResolveDomainKey(domain_name)),
655 topic_name_(ResolveTopicName(topic_name)),
656 name_key_(BuildNameKey(domain_crc32_, topic_name_)),
657 shm_name_(BuildShmName(name_key_))
658 {
659 (void)ReadProcessIdentity(static_cast<uint32_t>(getpid()), self_identity_);
660 Open();
661 }
662
663 LinuxSharedTopic(const char* topic_name, Topic::Domain& domain)
664 : create_(false),
665 publisher_(false),
666 config_(),
667 domain_crc32_(domain.node_ != nullptr ? domain.node_->key : 0),
668 topic_name_(ResolveTopicName(topic_name)),
669 name_key_(BuildNameKey(domain_crc32_, topic_name_)),
670 shm_name_(BuildShmName(name_key_))
671 {
672 (void)ReadProcessIdentity(static_cast<uint32_t>(getpid()), self_identity_);
673 Open();
674 }
675
682 LinuxSharedTopic(const char* topic_name, const LinuxSharedTopicConfig& config)
683 : LinuxSharedTopic(topic_name, DEFAULT_DOMAIN_NAME, config)
684 {
685 }
686
687 LinuxSharedTopic(const char* topic_name, const char* domain_name,
688 const LinuxSharedTopicConfig& config)
689 : create_(true),
690 publisher_(true),
691 config_(config),
692 domain_crc32_(ResolveDomainKey(domain_name)),
693 topic_name_(ResolveTopicName(topic_name)),
694 name_key_(BuildNameKey(domain_crc32_, topic_name_)),
695 shm_name_(BuildShmName(name_key_))
696 {
697 (void)ReadProcessIdentity(static_cast<uint32_t>(getpid()), self_identity_);
698 Open();
699 }
700
701 LinuxSharedTopic(const char* topic_name, Topic::Domain& domain,
702 const LinuxSharedTopicConfig& config)
703 : create_(true),
704 publisher_(true),
705 config_(config),
706 domain_crc32_(domain.node_ != nullptr ? domain.node_->key : 0),
707 topic_name_(ResolveTopicName(topic_name)),
708 name_key_(BuildNameKey(domain_crc32_, topic_name_)),
709 shm_name_(BuildShmName(name_key_))
710 {
711 (void)ReadProcessIdentity(static_cast<uint32_t>(getpid()), self_identity_);
712 Open();
713 }
714
719 using SyncSubscriber = Subscriber;
720
724 ~LinuxSharedTopic() { Close(); }
725
726 LinuxSharedTopic(const LinuxSharedTopic&) = delete;
727 LinuxSharedTopic& operator=(const LinuxSharedTopic&) = delete;
728
729 LinuxSharedTopic(LinuxSharedTopic&&) = delete;
730 LinuxSharedTopic& operator=(LinuxSharedTopic&&) = delete;
731
736 bool Valid() const { return open_ok_; }
737
741 ErrorCode GetError() const { return open_status_; }
742
746 uint32_t GetSubscriberNum() const
747 {
748 if (!Valid())
749 {
750 return 0;
751 }
752
753 uint32_t count = 0;
754 for (uint32_t i = 0; i < subscriber_capacity_; ++i)
755 {
756 if (subscribers_[i].active.load(std::memory_order_acquire) != 0)
757 {
758 ++count;
759 }
760 }
761 return count;
762 }
763
770 ErrorCode CreateData(SharedData& data)
771 {
772 if (!Valid())
773 {
774 return ErrorCode::STATE_ERR;
775 }
776
777 if (!PublisherValid())
778 {
779 return ErrorCode::STATE_ERR;
780 }
781
782 data.Reset();
783
784 uint32_t slot_index = INVALID_INDEX;
785 ErrorCode pop_ans = PopFreeSlot(slot_index);
786 if (pop_ans != ErrorCode::OK)
787 {
788 ScavengeDeadSubscribers();
789 pop_ans = PopFreeSlot(slot_index);
790 if (pop_ans != ErrorCode::OK)
791 {
792 return pop_ans;
793 }
794 }
795
796 slots_[slot_index].refcount.store(0, std::memory_order_release);
797 slots_[slot_index].sequence.store(0, std::memory_order_release);
798 slots_[slot_index].timestamp_us = 0;
799
800 data.topic_ = this;
801 data.slot_index_ = slot_index;
802 data.sequence_ = 0;
803 data.state_ = SharedDataState::PUBLISHER;
804 data.subscriber_index_ = INVALID_INDEX;
805 return ErrorCode::OK;
806 }
807
813 ErrorCode Publish(const TopicData& data)
814 {
815 SharedData topic_data;
816 const ErrorCode acquire_ans = CreateData(topic_data);
817 if (acquire_ans != ErrorCode::OK)
818 {
819 return acquire_ans;
820 }
821
822 *topic_data.GetData() = data;
823 return Publish(topic_data);
824 }
825
826 ErrorCode Publish(const TopicData& data, MicrosecondTimestamp timestamp)
827 {
828 SharedData topic_data;
829 const ErrorCode acquire_ans = CreateData(topic_data);
830 if (acquire_ans != ErrorCode::OK)
831 {
832 return acquire_ans;
833 }
834
835 *topic_data.GetData() = data;
836 return Publish(topic_data, timestamp);
837 }
838
842 ErrorCode Publish(SharedData&& data) { return PublishData<false>(data); }
843
844 ErrorCode Publish(SharedData&& data, MicrosecondTimestamp timestamp)
845 {
846 return PublishData<true>(data, timestamp);
847 }
848
867 ErrorCode Publish(SharedData& data) { return PublishData<false>(data); }
868
869 ErrorCode Publish(SharedData& data, MicrosecondTimestamp timestamp)
870 {
871 return PublishData<true>(data, timestamp);
872 }
873
877 uint64_t GetPublishFailedNum() const
878 {
879 if (!Valid())
880 {
881 return 0;
882 }
883 return header_->publish_failures.load(std::memory_order_acquire);
884 }
885
891 static ErrorCode Remove(const char* topic_name)
892 {
893 return Remove(topic_name, DEFAULT_DOMAIN_NAME);
894 }
895
896 static ErrorCode Remove(const char* topic_name, const char* domain_name)
897 {
898 const std::string shm_name = BuildShmName(
899 BuildNameKey(ResolveDomainKey(domain_name), ResolveTopicName(topic_name)));
900 if (shm_unlink(shm_name.c_str()) == 0 || errno == ENOENT)
901 {
902 return ErrorCode::OK;
903 }
904 return ErrorCode::FAILED;
905 }
906
907 static ErrorCode Remove(const char* topic_name, Topic::Domain& domain)
908 {
909 const uint32_t domain_crc32 = (domain.node_ != nullptr) ? domain.node_->key : 0;
910 const std::string shm_name =
911 BuildShmName(BuildNameKey(domain_crc32, ResolveTopicName(topic_name)));
912 if (shm_unlink(shm_name.c_str()) == 0 || errno == ENOENT)
913 {
914 return ErrorCode::OK;
915 }
916 return ErrorCode::FAILED;
917 }
918
919 private:
920 struct alignas(LibXR::CONCURRENCY_ALIGNMENT) SharedHeader
921 {
922 uint64_t magic = 0;
923 uint64_t name_key = 0;
924 uint32_t domain_crc32 = 0;
925 uint32_t version = 0;
926 uint32_t data_size = 0;
927 uint32_t slot_count = 0;
928 uint32_t subscriber_capacity = 0;
929 uint32_t queue_capacity = 0;
930 uint32_t topic_name_len = 0;
931 std::atomic<uint32_t> init_state;
932 std::atomic<uint32_t> publisher_pid;
933 std::atomic<uint64_t> publisher_starttime;
934 std::atomic<uint64_t> free_queue_head;
935 std::atomic<uint64_t> free_queue_tail;
936 std::atomic<uint64_t> next_sequence;
937 std::atomic<uint64_t> publish_failures;
938 };
939
940 struct alignas(LibXR::CONCURRENCY_ALIGNMENT) SlotControl
941 {
942 std::atomic<uint32_t> refcount;
943 std::atomic<uint64_t> sequence;
944 uint64_t timestamp_us;
945 };
946
947 struct alignas(16) FreeSlotCell
948 {
949 std::atomic<uint64_t> sequence;
950 uint32_t slot_index = 0;
951 uint32_t reserved = 0;
952 };
953
954 struct Descriptor
955 {
956 uint32_t slot_index = INVALID_INDEX;
957 uint32_t reserved = 0;
958 uint64_t sequence = 0;
959 };
960
961 struct alignas(LibXR::CONCURRENCY_ALIGNMENT) SubscriberControl
962 {
963 std::atomic<uint32_t> active;
964 std::atomic<uint32_t> mode;
965 std::atomic<uint32_t> queue_head;
966 std::atomic<uint32_t> queue_tail;
967 std::atomic<uint32_t>
968 ready_signal;
969 std::atomic<uint64_t> dropped_messages;
970 std::atomic<uint32_t> owner_pid;
971 std::atomic<uint64_t> owner_starttime;
972 std::atomic<uint32_t> held_slot;
973 };
974
975 struct alignas(LibXR::CONCURRENCY_ALIGNMENT) BalancedGroupControl
976 {
977 std::atomic<uint64_t> rr_cursor;
978 };
979
980 struct ProcessIdentity
981 {
982 uint32_t pid = 0;
983 uint64_t starttime = 0;
984 };
985
986 static constexpr uint64_t MAGIC = 0x4c58524950435348ULL;
987 static constexpr uint32_t VERSION = 3;
988 static constexpr uint32_t INIT_READY = 1;
989 static constexpr uint32_t INVALID_INDEX = UINT32_MAX;
990
991 static uint32_t ResolveDomainKey(const char* domain_name)
992 {
993 const std::string resolved = (domain_name == nullptr || domain_name[0] == '\0')
994 ? std::string(DEFAULT_DOMAIN_NAME)
995 : std::string(domain_name);
996 return CRC32::Calculate(resolved.data(), resolved.size());
997 }
998
999 static std::string ResolveTopicName(const char* topic_name)
1000 {
1001 return (topic_name != nullptr) ? std::string(topic_name) : std::string();
1002 }
1003
1004 static uint64_t BuildNameKey(uint32_t domain_crc32, const std::string& topic_name)
1005 {
1006 const uint32_t topic_len = static_cast<uint32_t>(topic_name.size());
1007 std::string key_material;
1008 key_material.reserve(sizeof(domain_crc32) + sizeof(topic_len) + topic_len);
1009 key_material.append(reinterpret_cast<const char*>(&domain_crc32),
1010 sizeof(domain_crc32));
1011 key_material.append(reinterpret_cast<const char*>(&topic_len), sizeof(topic_len));
1012 key_material.append(topic_name.data(), topic_name.size());
1013 return CRC64::Calculate(key_material.data(), key_material.size());
1014 }
1015
1016 static std::string BuildShmName(uint64_t name_key)
1017 {
1018 char buffer[64] = {};
1019 std::snprintf(buffer, sizeof(buffer), "/libxr_ipc_%016" PRIx64, name_key);
1020 return std::string(buffer);
1021 }
1022
1023 static size_t AlignUp(size_t value, size_t alignment)
1024 {
1025 return (value + alignment - 1U) & ~(alignment - 1U);
1026 }
1027
1028 static uint64_t NowMonotonicMs() { return MonotonicTime::NowMilliseconds(); }
1029
1030 static MicrosecondTimestamp NowMessageTimestamp() { return Topic::NowTimestamp(); }
1031
1032 static uint64_t ToSharedTimestamp(MicrosecondTimestamp timestamp)
1033 {
1034 return MonotonicTime::XrToSharedMicroseconds(static_cast<uint64_t>(timestamp));
1035 }
1036
1037 static MicrosecondTimestamp FromSharedTimestamp(uint64_t timestamp_us)
1038 {
1039 return MicrosecondTimestamp(MonotonicTime::SharedToXrMicroseconds(timestamp_us));
1040 }
1041
1042 static bool ReadProcessIdentity(uint32_t pid, ProcessIdentity& identity)
1043 {
1044 identity = {};
1045 if (pid == 0)
1046 {
1047 return false;
1048 }
1049
1050 char path[64] = {};
1051 std::snprintf(path, sizeof(path), "/proc/%u/stat", pid);
1052
1053 std::ifstream file(path);
1054 if (!file.is_open())
1055 {
1056 return false;
1057 }
1058
1059 std::string line;
1060 std::getline(file, line);
1061 if (line.empty())
1062 {
1063 return false;
1064 }
1065
1066 const size_t rparen = line.rfind(')');
1067 if (rparen == std::string::npos || rparen + 2U >= line.size())
1068 {
1069 return false;
1070 }
1071
1072 std::istringstream iss(line.substr(rparen + 2U));
1073 std::string token;
1074 for (int field = 3; field <= 22; ++field)
1075 {
1076 if (!(iss >> token))
1077 {
1078 return false;
1079 }
1080
1081 if (field == 22)
1082 {
1083 identity.pid = pid;
1084 identity.starttime = std::strtoull(token.c_str(), nullptr, 10);
1085 return identity.starttime != 0;
1086 }
1087 }
1088
1089 return false;
1090 }
1091
1092 static int FutexWait(std::atomic<uint32_t>* word, uint32_t expected,
1093 uint32_t timeout_ms)
1094 {
1095 struct timespec timeout = {};
1096 struct timespec* timeout_ptr = nullptr;
1097 if (timeout_ms != UINT32_MAX)
1098 {
1099 timeout.tv_sec = static_cast<time_t>(timeout_ms / 1000U);
1100 timeout.tv_nsec = static_cast<long>(timeout_ms % 1000U) * 1000000L;
1101 timeout_ptr = &timeout;
1102 }
1103
1104 return static_cast<int>(syscall(SYS_futex, reinterpret_cast<uint32_t*>(word),
1105 FUTEX_WAIT, expected, timeout_ptr, nullptr, 0));
1106 }
1107
1108 static int FutexWake(std::atomic<uint32_t>* word)
1109 {
1110 return static_cast<int>(syscall(SYS_futex, reinterpret_cast<uint32_t*>(word),
1111 FUTEX_WAKE, INT32_MAX, nullptr, nullptr, 0));
1112 }
1113
1114 static size_t ComputeSharedBytes(uint32_t slot_count, uint32_t subscriber_capacity,
1115 uint32_t queue_capacity, uint32_t topic_name_len)
1116 {
1117 size_t offset = 0;
1118 offset = AlignUp(offset, alignof(SharedHeader));
1119 offset += sizeof(SharedHeader);
1120
1121 offset += static_cast<size_t>(topic_name_len) + 1U;
1122
1123 offset = AlignUp(offset, alignof(SlotControl));
1124 offset += sizeof(SlotControl) * slot_count;
1125
1126 offset = AlignUp(offset, alignof(SubscriberControl));
1127 offset += sizeof(SubscriberControl) * subscriber_capacity;
1128
1129 offset = AlignUp(offset, alignof(BalancedGroupControl));
1130 offset += sizeof(BalancedGroupControl);
1131
1132 offset = AlignUp(offset, alignof(std::atomic<uint32_t>));
1133 offset += sizeof(std::atomic<uint32_t>) * subscriber_capacity;
1134
1135 offset = AlignUp(offset, alignof(FreeSlotCell));
1136 offset += sizeof(FreeSlotCell) * slot_count;
1137
1138 offset = AlignUp(offset, alignof(Descriptor));
1139 offset += sizeof(Descriptor) * subscriber_capacity * queue_capacity;
1140
1141 offset = AlignUp(offset, alignof(TopicData));
1142 offset += sizeof(TopicData) * slot_count;
1143 return offset;
1144 }
1145
1146 void SetupPointers()
1147 {
1148 size_t offset = 0;
1149
1150 offset = AlignUp(offset, alignof(SharedHeader));
1151 header_ = reinterpret_cast<SharedHeader*>(base_ + offset);
1152 offset += sizeof(SharedHeader);
1153
1154 topic_name_ptr_ = reinterpret_cast<char*>(base_ + offset);
1155 offset += static_cast<size_t>(header_->topic_name_len) + 1U;
1156
1157 offset = AlignUp(offset, alignof(SlotControl));
1158 slots_ = reinterpret_cast<SlotControl*>(base_ + offset);
1159 offset += sizeof(SlotControl) * slot_count_;
1160
1161 offset = AlignUp(offset, alignof(SubscriberControl));
1162 subscribers_ = reinterpret_cast<SubscriberControl*>(base_ + offset);
1163 offset += sizeof(SubscriberControl) * subscriber_capacity_;
1164
1165 offset = AlignUp(offset, alignof(BalancedGroupControl));
1166 balanced_group_ = reinterpret_cast<BalancedGroupControl*>(base_ + offset);
1167 offset += sizeof(BalancedGroupControl);
1168
1169 offset = AlignUp(offset, alignof(std::atomic<uint32_t>));
1170 balanced_members_ = reinterpret_cast<std::atomic<uint32_t>*>(base_ + offset);
1171 offset += sizeof(std::atomic<uint32_t>) * subscriber_capacity_;
1172
1173 offset = AlignUp(offset, alignof(FreeSlotCell));
1174 free_slots_ = reinterpret_cast<FreeSlotCell*>(base_ + offset);
1175 offset += sizeof(FreeSlotCell) * slot_count_;
1176
1177 offset = AlignUp(offset, alignof(Descriptor));
1178 descriptors_ = reinterpret_cast<Descriptor*>(base_ + offset);
1179 offset += sizeof(Descriptor) * subscriber_capacity_ * queue_capacity_;
1180
1181 offset = AlignUp(offset, alignof(TopicData));
1182 payloads_ = reinterpret_cast<TopicData*>(base_ + offset);
1183 }
1184
1185 bool HeaderMatchesIdentity() const
1186 {
1187 if (header_->name_key != name_key_)
1188 {
1189 return false;
1190 }
1191 if (header_->domain_crc32 != domain_crc32_)
1192 {
1193 return false;
1194 }
1195 if (header_->topic_name_len != topic_name_.size())
1196 {
1197 return false;
1198 }
1199 if (std::memcmp(topic_name_ptr_, topic_name_.c_str(), topic_name_.size() + 1U) != 0)
1200 {
1201 return false;
1202 }
1203 return true;
1204 }
1205
1206 ErrorCode InitializeLayout()
1207 {
1208 if (config_.slot_num == 0 || config_.subscriber_num == 0 || config_.queue_num < 2)
1209 {
1210 return ErrorCode::ARG_ERR;
1211 }
1212
1213 const size_t bytes =
1214 ComputeSharedBytes(config_.slot_num, config_.subscriber_num, config_.queue_num,
1215 static_cast<uint32_t>(topic_name_.size()));
1216
1217 if (ftruncate(fd_, static_cast<off_t>(bytes)) != 0)
1218 {
1219 return ErrorCode::INIT_ERR;
1220 }
1221
1222 const struct stat st = GetStat();
1223 if (st.st_size <= 0)
1224 {
1225 return ErrorCode::INIT_ERR;
1226 }
1227
1228 mapping_size_ = static_cast<size_t>(st.st_size);
1229 mapping_ = mmap(nullptr, mapping_size_, PROT_READ | PROT_WRITE, MAP_SHARED, fd_, 0);
1230 if (mapping_ == MAP_FAILED)
1231 {
1232 mapping_ = nullptr;
1233 return ErrorCode::INIT_ERR;
1234 }
1235
1236 base_ = static_cast<uint8_t*>(mapping_);
1237 slot_count_ = config_.slot_num;
1238 subscriber_capacity_ = config_.subscriber_num;
1239 queue_capacity_ = config_.queue_num;
1240 header_ = reinterpret_cast<SharedHeader*>(base_ + AlignUp(0, alignof(SharedHeader)));
1241 header_->topic_name_len = static_cast<uint32_t>(topic_name_.size());
1242 SetupPointers();
1243
1244 header_->magic = MAGIC;
1245 header_->name_key = name_key_;
1246 header_->domain_crc32 = domain_crc32_;
1247 header_->version = VERSION;
1248 header_->data_size = sizeof(TopicData);
1249 header_->slot_count = slot_count_;
1250 header_->subscriber_capacity = subscriber_capacity_;
1251 header_->queue_capacity = queue_capacity_;
1252 std::memcpy(topic_name_ptr_, topic_name_.c_str(), topic_name_.size() + 1U);
1253 header_->publisher_pid.store(self_identity_.pid, std::memory_order_release);
1254 header_->publisher_starttime.store(self_identity_.starttime,
1255 std::memory_order_release);
1256 header_->free_queue_head.store(0, std::memory_order_release);
1257 header_->free_queue_tail.store(slot_count_, std::memory_order_release);
1258 header_->next_sequence.store(0, std::memory_order_release);
1259 header_->publish_failures.store(0, std::memory_order_release);
1260
1261 for (uint32_t i = 0; i < slot_count_; ++i)
1262 {
1263 slots_[i].refcount.store(0, std::memory_order_release);
1264 slots_[i].sequence.store(0, std::memory_order_release);
1265 slots_[i].timestamp_us = 0;
1266 std::construct_at(&payloads_[i], TopicData{});
1267 free_slots_[i].slot_index = i;
1268 free_slots_[i].sequence.store(static_cast<uint64_t>(i) + 1U,
1269 std::memory_order_release);
1270 }
1271
1272 for (uint32_t i = 0; i < subscriber_capacity_; ++i)
1273 {
1274 subscribers_[i].active.store(0, std::memory_order_release);
1275 subscribers_[i].mode.store(
1276 static_cast<uint32_t>(LinuxSharedSubscriberMode::BROADCAST_FULL),
1277 std::memory_order_release);
1278 subscribers_[i].queue_head.store(0, std::memory_order_release);
1279 subscribers_[i].queue_tail.store(0, std::memory_order_release);
1280 subscribers_[i].ready_signal.store(0, std::memory_order_release);
1281 subscribers_[i].dropped_messages.store(0, std::memory_order_release);
1282 subscribers_[i].owner_pid.store(0, std::memory_order_release);
1283 subscribers_[i].owner_starttime.store(0, std::memory_order_release);
1284 subscribers_[i].held_slot.store(INVALID_INDEX, std::memory_order_release);
1285 balanced_members_[i].store(INVALID_INDEX, std::memory_order_release);
1286 }
1287
1288 balanced_group_->rr_cursor.store(0, std::memory_order_release);
1289
1290 for (size_t i = 0; i < static_cast<size_t>(subscriber_capacity_) * queue_capacity_;
1291 ++i)
1292 {
1293 descriptors_[i] = Descriptor{};
1294 }
1295
1296 header_->init_state.store(INIT_READY, std::memory_order_release);
1297 return ErrorCode::OK;
1298 }
1299
1300 ErrorCode AttachLayout()
1301 {
1302 const struct stat st = GetStat();
1303 if (st.st_size <= 0)
1304 {
1305 return ErrorCode::NOT_FOUND;
1306 }
1307
1308 mapping_size_ = static_cast<size_t>(st.st_size);
1309 mapping_ = mmap(nullptr, mapping_size_, PROT_READ | PROT_WRITE, MAP_SHARED, fd_, 0);
1310 if (mapping_ == MAP_FAILED)
1311 {
1312 mapping_ = nullptr;
1313 return ErrorCode::INIT_ERR;
1314 }
1315
1316 base_ = static_cast<uint8_t*>(mapping_);
1317 header_ = reinterpret_cast<SharedHeader*>(base_);
1318
1319 while (header_->init_state.load(std::memory_order_acquire) != INIT_READY)
1320 {
1321 usleep(1000);
1322 }
1323
1324 if (header_->magic != MAGIC || header_->version != VERSION ||
1325 header_->data_size != sizeof(TopicData))
1326 {
1327 return ErrorCode::CHECK_ERR;
1328 }
1329
1330 slot_count_ = header_->slot_count;
1331 subscriber_capacity_ = header_->subscriber_capacity;
1332 queue_capacity_ = header_->queue_capacity;
1333 SetupPointers();
1334 if (!HeaderMatchesIdentity())
1335 {
1336 return ErrorCode::CHECK_ERR;
1337 }
1338 return ErrorCode::OK;
1339 }
1340
1341 bool TryReclaimStaleSegment()
1342 {
1343 int stale_fd = shm_open(shm_name_.c_str(), O_RDWR, 0600);
1344 if (stale_fd < 0)
1345 {
1346 return errno == ENOENT;
1347 }
1348
1349 struct stat st = {};
1350 if (fstat(stale_fd, &st) != 0)
1351 {
1352 close(stale_fd);
1353 return false;
1354 }
1355
1356 bool reclaim = false;
1357 if (st.st_size < static_cast<off_t>(sizeof(SharedHeader)))
1358 {
1359 reclaim = true;
1360 }
1361 else
1362 {
1363 void* mapping = mmap(nullptr, static_cast<size_t>(st.st_size),
1364 PROT_READ | PROT_WRITE, MAP_SHARED, stale_fd, 0);
1365 if (mapping != MAP_FAILED)
1366 {
1367 uint8_t* base = static_cast<uint8_t*>(mapping);
1368 auto* header = reinterpret_cast<SharedHeader*>(mapping);
1369 const uint32_t init_state = header->init_state.load(std::memory_order_acquire);
1370 bool identity_match = false;
1371 const size_t mapping_size = static_cast<size_t>(st.st_size);
1372 const size_t topic_name_bytes = topic_name_.size() + 1U;
1373 if (header->magic == MAGIC && header->version == VERSION &&
1374 header->domain_crc32 == domain_crc32_ &&
1375 header->topic_name_len == topic_name_.size())
1376 {
1377 size_t offset = AlignUp(0, alignof(SharedHeader));
1378 offset += sizeof(SharedHeader);
1379 if (offset <= mapping_size && topic_name_bytes <= (mapping_size - offset))
1380 {
1381 const char* mapped_topic = reinterpret_cast<const char*>(base + offset);
1382 identity_match =
1383 (header->name_key == name_key_) &&
1384 (std::memcmp(mapped_topic, topic_name_.c_str(), topic_name_bytes) == 0);
1385 }
1386 }
1387 const ProcessIdentity publisher_identity = {
1388 header->publisher_pid.load(std::memory_order_acquire),
1389 header->publisher_starttime.load(std::memory_order_acquire),
1390 };
1391
1392 if (!identity_match)
1393 {
1394 reclaim = false;
1395 }
1396 else if (init_state != INIT_READY)
1397 {
1398 reclaim = !ProcessAlive(publisher_identity);
1399 }
1400 else if (!ProcessAlive(publisher_identity))
1401 {
1402 reclaim = true;
1403 }
1404
1405 munmap(mapping, static_cast<size_t>(st.st_size));
1406 }
1407 }
1408
1409 close(stale_fd);
1410
1411 if (!reclaim)
1412 {
1413 return false;
1414 }
1415
1416 return shm_unlink(shm_name_.c_str()) == 0 || errno == ENOENT;
1417 }
1418
1419 void Open()
1420 {
1421 open_status_ = ErrorCode::STATE_ERR;
1422 open_ok_ = false;
1423
1424 if (create_)
1425 {
1426 for (int attempt = 0; attempt < 2; ++attempt)
1427 {
1428 fd_ = shm_open(shm_name_.c_str(), O_CREAT | O_EXCL | O_RDWR, 0600);
1429 if (fd_ >= 0)
1430 {
1431 break;
1432 }
1433
1434 if (errno != EEXIST || !TryReclaimStaleSegment())
1435 {
1436 break;
1437 }
1438 }
1439
1440 if (fd_ < 0)
1441 {
1442 open_status_ = (errno == EEXIST) ? ErrorCode::BUSY : ErrorCode::INIT_ERR;
1443 return;
1444 }
1445
1446 open_status_ = InitializeLayout();
1447 }
1448 else
1449 {
1450 fd_ = shm_open(shm_name_.c_str(), O_RDWR, 0600);
1451 if (fd_ < 0)
1452 {
1453 open_status_ = (errno == ENOENT) ? ErrorCode::NOT_FOUND : ErrorCode::INIT_ERR;
1454 return;
1455 }
1456
1457 open_status_ = AttachLayout();
1458 }
1459
1460 if (fd_ >= 0)
1461 {
1462 close(fd_);
1463 fd_ = -1;
1464 }
1465
1466 open_ok_ = (open_status_ == ErrorCode::OK);
1467 }
1468
1469 void Close()
1470 {
1471 if (mapping_ != nullptr)
1472 {
1473 munmap(mapping_, mapping_size_);
1474 }
1475
1476 mapping_ = nullptr;
1477 base_ = nullptr;
1478 header_ = nullptr;
1479 slots_ = nullptr;
1480 subscribers_ = nullptr;
1481 free_slots_ = nullptr;
1482 descriptors_ = nullptr;
1483 payloads_ = nullptr;
1484 mapping_size_ = 0;
1485 open_ok_ = false;
1486 open_status_ = ErrorCode::STATE_ERR;
1487 }
1488
1489 struct stat GetStat() const
1490 {
1491 struct stat st = {};
1492 fstat(fd_, &st);
1493 return st;
1494 }
1495
1496 Descriptor* DescriptorRing(uint32_t subscriber_index) const
1497 {
1498 return descriptors_ + static_cast<size_t>(subscriber_index) * queue_capacity_;
1499 }
1500
1501 static bool ProcessAlive(const ProcessIdentity& identity)
1502 {
1503 ProcessIdentity current = {};
1504 if (!ReadProcessIdentity(identity.pid, current))
1505 {
1506 return false;
1507 }
1508
1509 return current.starttime == identity.starttime;
1510 }
1511
1512 bool PublisherValid() const
1513 {
1514 if (!publisher_ || header_ == nullptr)
1515 {
1516 return false;
1517 }
1518
1519 const ProcessIdentity owner = {
1520 header_->publisher_pid.load(std::memory_order_acquire),
1521 header_->publisher_starttime.load(std::memory_order_acquire),
1522 };
1523 return owner.pid == self_identity_.pid && owner.starttime == self_identity_.starttime;
1524 }
1525
1526 void HoldSlot(uint32_t subscriber_index, uint32_t slot_index)
1527 {
1528 subscribers_[subscriber_index].held_slot.store(slot_index, std::memory_order_release);
1529 }
1530
1531 void ClearHeldSlot(uint32_t subscriber_index, uint32_t slot_index)
1532 {
1533 uint32_t expected = slot_index;
1534 subscribers_[subscriber_index].held_slot.compare_exchange_strong(
1535 expected, INVALID_INDEX, std::memory_order_acq_rel, std::memory_order_relaxed);
1536 }
1537
1538 MicrosecondTimestamp SlotTimestamp(uint32_t slot_index) const
1539 {
1540 return FromSharedTimestamp(slots_[slot_index].timestamp_us);
1541 }
1542
1543 ErrorCode RegisterBalancedSubscriber(uint32_t subscriber_index)
1544 {
1545 for (uint32_t i = 0; i < subscriber_capacity_; ++i)
1546 {
1547 uint32_t expected = INVALID_INDEX;
1548 if (balanced_members_[i].compare_exchange_strong(expected, subscriber_index,
1549 std::memory_order_acq_rel,
1550 std::memory_order_relaxed))
1551 {
1552 return ErrorCode::OK;
1553 }
1554 }
1555 return ErrorCode::FULL;
1556 }
1557
1558 void UnregisterBalancedSubscriber(uint32_t subscriber_index)
1559 {
1560 for (uint32_t i = 0; i < subscriber_capacity_; ++i)
1561 {
1562 uint32_t expected = subscriber_index;
1563 if (balanced_members_[i].compare_exchange_strong(expected, INVALID_INDEX,
1564 std::memory_order_acq_rel,
1565 std::memory_order_relaxed))
1566 {
1567 return;
1568 }
1569 }
1570 }
1571
1572 bool SelectBalancedSubscriber(uint32_t& subscriber_index)
1573 {
1574 const uint64_t base =
1575 balanced_group_->rr_cursor.fetch_add(1, std::memory_order_acq_rel);
1576 for (uint32_t offset = 0; offset < subscriber_capacity_; ++offset)
1577 {
1578 const uint32_t member_index =
1579 balanced_members_[(base + offset) % subscriber_capacity_].load(
1580 std::memory_order_acquire);
1581 if (member_index == INVALID_INDEX)
1582 {
1583 continue;
1584 }
1585 if (subscribers_[member_index].active.load(std::memory_order_acquire) == 0)
1586 {
1587 continue;
1588 }
1589 if (subscribers_[member_index].mode.load(std::memory_order_acquire) !=
1590 static_cast<uint32_t>(LinuxSharedSubscriberMode::BALANCE_RR))
1591 {
1592 continue;
1593 }
1594 const ProcessIdentity owner_identity = {
1595 subscribers_[member_index].owner_pid.load(std::memory_order_acquire),
1596 subscribers_[member_index].owner_starttime.load(std::memory_order_acquire),
1597 };
1598 if (owner_identity.pid == 0 || owner_identity.starttime == 0)
1599 {
1600 continue;
1601 }
1602 if (!ProcessAlive(owner_identity))
1603 {
1604 ReclaimSubscriber(member_index);
1605 continue;
1606 }
1607 if (!QueueHasSpace(member_index))
1608 {
1609 continue;
1610 }
1611 subscriber_index = member_index;
1612 return true;
1613 }
1614 return false;
1615 }
1616
1617 bool ReclaimSubscriber(uint32_t subscriber_index)
1618 {
1619 uint32_t expected = 1;
1620 if (!subscribers_[subscriber_index].active.compare_exchange_strong(
1621 expected, 0, std::memory_order_acq_rel, std::memory_order_relaxed))
1622 {
1623 return false;
1624 }
1625
1626 if (subscribers_[subscriber_index].mode.load(std::memory_order_acquire) ==
1627 static_cast<uint32_t>(LinuxSharedSubscriberMode::BALANCE_RR))
1628 {
1629 UnregisterBalancedSubscriber(subscriber_index);
1630 }
1631
1632 subscribers_[subscriber_index].owner_pid.store(0, std::memory_order_release);
1633 subscribers_[subscriber_index].owner_starttime.store(0, std::memory_order_release);
1634 subscribers_[subscriber_index].mode.store(
1635 static_cast<uint32_t>(LinuxSharedSubscriberMode::BROADCAST_FULL),
1636 std::memory_order_release);
1637
1638 const uint32_t held_slot = subscribers_[subscriber_index].held_slot.exchange(
1639 INVALID_INDEX, std::memory_order_acq_rel);
1640 if (held_slot != INVALID_INDEX)
1641 {
1642 ReleaseSlot(held_slot);
1643 }
1644
1645 Descriptor desc = {};
1646 while (TryPopDescriptor(subscriber_index, desc) == ErrorCode::OK)
1647 {
1648 ReleaseSlot(desc.slot_index);
1649 }
1650
1651 return true;
1652 }
1653
1654 static void PostReady(SubscriberControl& control)
1655 {
1656 control.ready_signal.store(1, std::memory_order_release);
1657 FutexWake(&control.ready_signal);
1658 }
1659
1660 static bool HasQueuedData(const SubscriberControl& control)
1661 {
1662 return control.queue_head.load(std::memory_order_acquire) !=
1663 control.queue_tail.load(std::memory_order_acquire);
1664 }
1665
1666 static ErrorCode WaitReady(SubscriberControl& control, uint32_t timeout_ms)
1667 {
1668 if (HasQueuedData(control))
1669 {
1670 return ErrorCode::OK;
1671 }
1672
1673 const bool infinite_wait = (timeout_ms == UINT32_MAX);
1674 const uint64_t deadline_ms = infinite_wait ? 0 : (NowMonotonicMs() + timeout_ms);
1675
1676 while (true)
1677 {
1678 // 先清提示再检查队列,覆盖检查与进入 futex 等待之间的发布。
1679 // Clear the hint before checking the queue so a later publication prevents sleep.
1680 control.ready_signal.exchange(0, std::memory_order_acq_rel);
1681 if (HasQueuedData(control))
1682 {
1683 return ErrorCode::OK;
1684 }
1685
1686 uint32_t wait_ms = UINT32_MAX;
1687 if (!infinite_wait)
1688 {
1689 wait_ms = MonotonicTime::RemainingMilliseconds(deadline_ms);
1690 if (wait_ms == 0)
1691 {
1692 return ErrorCode::TIMEOUT;
1693 }
1694 }
1695
1696 wait_ms = MonotonicTime::WaitSliceMilliseconds(wait_ms);
1697
1698 const int futex_ans = FutexWait(&control.ready_signal, 0, wait_ms);
1699 if (futex_ans == 0 || errno == EAGAIN || errno == EINTR)
1700 {
1701 continue;
1702 }
1703
1704 if (errno == ETIMEDOUT)
1705 {
1706 if (infinite_wait)
1707 {
1708 continue;
1709 }
1710 if (MonotonicTime::RemainingMilliseconds(deadline_ms) == 0 &&
1711 !HasQueuedData(control))
1712 {
1713 return ErrorCode::TIMEOUT;
1714 }
1715 continue;
1716 }
1717
1718 return ErrorCode::FAILED;
1719 }
1720 }
1721
1722 void ScavengeDeadSubscribers()
1723 {
1724 for (uint32_t i = 0; i < subscriber_capacity_; ++i)
1725 {
1726 if (subscribers_[i].active.load(std::memory_order_acquire) == 0)
1727 {
1728 continue;
1729 }
1730
1731 const ProcessIdentity owner_identity = {
1732 subscribers_[i].owner_pid.load(std::memory_order_acquire),
1733 subscribers_[i].owner_starttime.load(std::memory_order_acquire),
1734 };
1735 if (ProcessAlive(owner_identity))
1736 {
1737 continue;
1738 }
1739
1740 ReclaimSubscriber(i);
1741 }
1742 }
1743
1744 bool QueueHasSpace(uint32_t subscriber_index) const
1745 {
1746 const SubscriberControl& control = subscribers_[subscriber_index];
1747 const uint32_t head = control.queue_head.load(std::memory_order_acquire);
1748 const uint32_t tail = control.queue_tail.load(std::memory_order_relaxed);
1749 const uint32_t next_tail = (tail + 1U) % queue_capacity_;
1750 return next_tail != head;
1751 }
1752
1753 void PushDescriptor(uint32_t subscriber_index, const Descriptor& descriptor)
1754 {
1755 SubscriberControl& control = subscribers_[subscriber_index];
1756 Descriptor* ring = DescriptorRing(subscriber_index);
1757
1758 const uint32_t tail = control.queue_tail.load(std::memory_order_relaxed);
1759 ring[tail] = descriptor;
1760 const uint32_t next_tail = (tail + 1U) % queue_capacity_;
1761 control.queue_tail.store(next_tail, std::memory_order_release);
1762 PostReady(control);
1763 }
1764
1765 ErrorCode TryPopDescriptor(uint32_t subscriber_index, Descriptor& descriptor)
1766 {
1767 SubscriberControl& control = subscribers_[subscriber_index];
1768 Descriptor* ring = DescriptorRing(subscriber_index);
1769
1770 while (true)
1771 {
1772 uint32_t head = control.queue_head.load(std::memory_order_relaxed);
1773 const uint32_t tail = control.queue_tail.load(std::memory_order_acquire);
1774 if (head == tail)
1775 {
1776 return ErrorCode::EMPTY;
1777 }
1778
1779 descriptor = ring[head];
1780 const uint32_t next_head = (head + 1U) % queue_capacity_;
1781 if (control.queue_head.compare_exchange_weak(
1782 head, next_head, std::memory_order_acq_rel, std::memory_order_relaxed))
1783 {
1784 return ErrorCode::OK;
1785 }
1786 }
1787 }
1788
1789 ErrorCode DropDescriptor(uint32_t subscriber_index)
1790 {
1791 SubscriberControl& control = subscribers_[subscriber_index];
1792 Descriptor* ring = DescriptorRing(subscriber_index);
1793
1794 while (true)
1795 {
1796 uint32_t head = control.queue_head.load(std::memory_order_relaxed);
1797 const uint32_t tail = control.queue_tail.load(std::memory_order_acquire);
1798 if (head == tail)
1799 {
1800 return ErrorCode::EMPTY;
1801 }
1802
1803 const Descriptor descriptor = ring[head];
1804 const uint32_t next_head = (head + 1U) % queue_capacity_;
1805 if (control.queue_head.compare_exchange_weak(
1806 head, next_head, std::memory_order_acq_rel, std::memory_order_relaxed))
1807 {
1808 control.dropped_messages.fetch_add(1, std::memory_order_relaxed);
1809 ReleaseSlot(descriptor.slot_index);
1810 return ErrorCode::OK;
1811 }
1812 }
1813 }
1814
1815 ErrorCode PopFreeSlot(uint32_t& slot_index)
1816 {
1817 while (true)
1818 {
1819 uint64_t head = header_->free_queue_head.load(std::memory_order_relaxed);
1820 FreeSlotCell& cell = free_slots_[head % slot_count_];
1821 const uint64_t seq = cell.sequence.load(std::memory_order_acquire);
1822 const intptr_t diff = static_cast<intptr_t>(seq) - static_cast<intptr_t>(head + 1U);
1823
1824 if (diff == 0)
1825 {
1826 if (header_->free_queue_head.compare_exchange_weak(
1827 head, head + 1U, std::memory_order_acq_rel, std::memory_order_relaxed))
1828 {
1829 slot_index = cell.slot_index;
1830 cell.sequence.store(head + slot_count_, std::memory_order_release);
1831 return ErrorCode::OK;
1832 }
1833 }
1834 else if (diff < 0)
1835 {
1836 return ErrorCode::FULL;
1837 }
1838 }
1839 }
1840
1841 void RecycleSlot(uint32_t slot_index)
1842 {
1843 slots_[slot_index].sequence.store(0, std::memory_order_release);
1844 slots_[slot_index].timestamp_us = 0;
1845
1846 while (true)
1847 {
1848 uint64_t tail = header_->free_queue_tail.load(std::memory_order_relaxed);
1849 FreeSlotCell& cell = free_slots_[tail % slot_count_];
1850 const uint64_t seq = cell.sequence.load(std::memory_order_acquire);
1851 const intptr_t diff = static_cast<intptr_t>(seq) - static_cast<intptr_t>(tail);
1852
1853 if (diff == 0)
1854 {
1855 if (header_->free_queue_tail.compare_exchange_weak(
1856 tail, tail + 1U, std::memory_order_acq_rel, std::memory_order_relaxed))
1857 {
1858 cell.slot_index = slot_index;
1859 cell.sequence.store(tail + 1U, std::memory_order_release);
1860 return;
1861 }
1862 }
1863 }
1864 }
1865
1866 void ReleaseSlot(uint32_t slot_index)
1867 {
1868 const uint32_t prev =
1869 slots_[slot_index].refcount.fetch_sub(1, std::memory_order_acq_rel);
1870 DEV_ASSERT(prev > 0);
1871 if (prev == 1)
1872 {
1873 RecycleSlot(slot_index);
1874 }
1875 }
1876
1877 template <bool HAS_TIMESTAMP>
1878 ErrorCode PublishData(SharedData& data,
1879 MicrosecondTimestamp timestamp = MicrosecondTimestamp())
1880 {
1881 if (!data.Valid() || data.topic_ != this)
1882 {
1883 return ErrorCode::STATE_ERR;
1884 }
1885
1886 uint32_t active_count = 0;
1887 uint32_t balanced_target = INVALID_INDEX;
1888 bool has_balanced_subscriber = false;
1889 for (uint32_t i = 0; i < subscriber_capacity_; ++i)
1890 {
1891 if (subscribers_[i].active.load(std::memory_order_acquire) == 0)
1892 {
1893 continue;
1894 }
1895
1896 const LinuxSharedSubscriberMode mode = static_cast<LinuxSharedSubscriberMode>(
1897 subscribers_[i].mode.load(std::memory_order_acquire));
1898 if (mode == LinuxSharedSubscriberMode::BALANCE_RR)
1899 {
1900 has_balanced_subscriber = true;
1901 continue;
1902 }
1903
1904 if (!QueueHasSpace(i))
1905 {
1906 ScavengeDeadSubscribers();
1907 if (subscribers_[i].active.load(std::memory_order_acquire) == 0)
1908 {
1909 continue;
1910 }
1911
1912 if (mode == LinuxSharedSubscriberMode::BROADCAST_DROP_OLD)
1913 {
1914 const ErrorCode drop_ans = DropDescriptor(i);
1915 if (drop_ans == ErrorCode::EMPTY && QueueHasSpace(i))
1916 {
1917 // 消费者可能在 QueueHasSpace() 和 DropDescriptor() 之间弹走旧描述符。
1918 // 此时队列已经有空位,发布者继续写入即可,不能把竞态误报成 FULL。
1919 }
1920 else if (drop_ans != ErrorCode::OK)
1921 {
1922 header_->publish_failures.fetch_add(1, std::memory_order_relaxed);
1923 data.Reset();
1924 return ErrorCode::FULL;
1925 }
1926 }
1927 else
1928 {
1929 subscribers_[i].dropped_messages.fetch_add(1, std::memory_order_relaxed);
1930 header_->publish_failures.fetch_add(1, std::memory_order_relaxed);
1931 data.Reset();
1932 return ErrorCode::FULL;
1933 }
1934 }
1935
1936 ++active_count;
1937 }
1938
1939 if (has_balanced_subscriber)
1940 {
1941 if (!SelectBalancedSubscriber(balanced_target))
1942 {
1943 ScavengeDeadSubscribers();
1944 if (!SelectBalancedSubscriber(balanced_target))
1945 {
1946 header_->publish_failures.fetch_add(1, std::memory_order_relaxed);
1947 data.Reset();
1948 return ErrorCode::FULL;
1949 }
1950 }
1951 ++active_count;
1952 }
1953
1954 if (active_count == 0)
1955 {
1956 data.Reset();
1957 return ErrorCode::OK;
1958 }
1959
1960 const uint64_t sequence =
1961 header_->next_sequence.fetch_add(1, std::memory_order_acq_rel) + 1ULL;
1962 if constexpr (!HAS_TIMESTAMP)
1963 {
1964 timestamp = NowMessageTimestamp();
1965 }
1966 SlotControl& slot = slots_[data.slot_index_];
1967 slot.refcount.store(active_count, std::memory_order_release);
1968 slot.timestamp_us = ToSharedTimestamp(timestamp);
1969 slot.sequence.store(sequence, std::memory_order_release);
1970
1971 const Descriptor descriptor = {data.slot_index_, 0U, sequence};
1972 for (uint32_t i = 0; i < subscriber_capacity_; ++i)
1973 {
1974 if (subscribers_[i].active.load(std::memory_order_acquire) == 0)
1975 {
1976 continue;
1977 }
1978 const LinuxSharedSubscriberMode mode = static_cast<LinuxSharedSubscriberMode>(
1979 subscribers_[i].mode.load(std::memory_order_acquire));
1980 if (mode == LinuxSharedSubscriberMode::BALANCE_RR)
1981 {
1982 continue;
1983 }
1984 PushDescriptor(i, descriptor);
1985 }
1986
1987 if (balanced_target != INVALID_INDEX)
1988 {
1989 PushDescriptor(balanced_target, descriptor);
1990 }
1991
1992 data.topic_ = nullptr;
1993 data.slot_index_ = INVALID_INDEX;
1994 return ErrorCode::OK;
1995 }
1996
1997 bool create_ = false;
1998 bool publisher_ = false;
1999 LinuxSharedTopicConfig config_;
2000 uint32_t domain_crc32_ = 0;
2001 std::string topic_name_;
2002 uint64_t name_key_ = 0;
2003 std::string shm_name_;
2004 ProcessIdentity self_identity_ = {};
2005
2006 int fd_ = -1;
2007 void* mapping_ = nullptr;
2008 uint8_t* base_ = nullptr;
2009 size_t mapping_size_ = 0;
2010
2011 SharedHeader* header_ = nullptr;
2012 char* topic_name_ptr_ = nullptr;
2013 SlotControl* slots_ = nullptr;
2014 SubscriberControl* subscribers_ = nullptr;
2015 BalancedGroupControl* balanced_group_ = nullptr;
2016 std::atomic<uint32_t>* balanced_members_ = nullptr;
2017 FreeSlotCell* free_slots_ = nullptr;
2018 Descriptor* descriptors_ = nullptr;
2019 TopicData* payloads_ = nullptr;
2020
2021 uint32_t slot_count_ = 0;
2022 uint32_t subscriber_capacity_ = 0;
2023 uint32_t queue_capacity_ = 0;
2024
2025 bool open_ok_ = false;
2026 ErrorCode open_status_ = ErrorCode::STATE_ERR;
2027};
2028
2029} // namespace LibXR
2030
2031#endif
LibXR 命名空间 / LibXR namespace.
Definition ch32_can.hpp:14
ErrorCode
定义错误码枚举 / Defines an enumeration for error codes
@ INIT_ERR
初始化错误 / Initialization error
@ EMPTY
为空 / Empty