ROS 2 rclcpp + rcl - lyrical  lyrical
ROS 2 C++ Client Library with ROS Client Library
intra_process_manager.cpp
1 // Copyright 2015 Open Source Robotics Foundation, Inc.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "rclcpp/experimental/intra_process_manager.hpp"
16 
17 #include <atomic>
18 #include <memory>
19 #include <mutex>
20 
21 namespace rclcpp
22 {
23 namespace experimental
24 {
25 
26 static std::atomic<uint64_t> _next_unique_id {1};
27 
28 IntraProcessManager::IntraProcessManager()
29 {}
30 
31 IntraProcessManager::~IntraProcessManager()
32 {}
33 
34 uint64_t
36  const rclcpp::PublisherBase::SharedPtr & publisher,
37  const rclcpp::experimental::buffers::IntraProcessBufferBase::SharedPtr & buffer)
38 {
39  std::unique_lock<std::shared_timed_mutex> lock(mutex_);
40 
41  uint64_t pub_id = IntraProcessManager::get_next_unique_id();
42 
43  publishers_[pub_id] = publisher;
44  if (publisher->is_durability_transient_local()) {
45  if (buffer) {
46  publisher_buffers_[pub_id] = buffer;
47  } else {
48  throw std::runtime_error(
49  "transient_local publisher needs to pass"
50  "a valid publisher buffer ptr when calling add_publisher()");
51  }
52  }
53 
54  // Add GID to publisher info mapping for fast lookups (stores both ID and weak_ptr)
55  gid_to_publisher_info_[publisher->get_gid()] = {pub_id, publisher};
56 
57  // Initialize the subscriptions storage for this publisher.
58  pub_to_subs_[pub_id] = SplittedSubscriptions();
59 
60  // create an entry for the publisher id and populate with already existing subscriptions
61  for (auto & pair : subscriptions_) {
62  auto subscription = pair.second.lock();
63  if (!subscription) {
64  continue;
65  }
66  if (can_communicate(publisher, subscription)) {
67  uint64_t sub_id = pair.first;
68  insert_sub_id_for_pub(sub_id, pub_id, subscription->use_take_shared_method());
69  }
70  }
71 
72  return pub_id;
73 }
74 
75 void
76 IntraProcessManager::remove_subscription(uint64_t intra_process_subscription_id)
77 {
78  std::unique_lock<std::shared_timed_mutex> lock(mutex_);
79 
80  subscriptions_.erase(intra_process_subscription_id);
81 
82  for (auto & pair : pub_to_subs_) {
83  pair.second.take_shared_subscriptions.erase(
84  std::remove(
85  pair.second.take_shared_subscriptions.begin(),
86  pair.second.take_shared_subscriptions.end(),
87  intra_process_subscription_id),
88  pair.second.take_shared_subscriptions.end());
89 
90  pair.second.take_ownership_subscriptions.erase(
91  std::remove(
92  pair.second.take_ownership_subscriptions.begin(),
93  pair.second.take_ownership_subscriptions.end(),
94  intra_process_subscription_id),
95  pair.second.take_ownership_subscriptions.end());
96  }
97 }
98 
99 void
100 IntraProcessManager::remove_publisher(uint64_t intra_process_publisher_id)
101 {
102  std::unique_lock<std::shared_timed_mutex> lock(mutex_);
103 
104  // Remove GID to publisher info mapping.
105  // First try via the publisher's own GID (fast path).
106  auto pub_it = publishers_.find(intra_process_publisher_id);
107  if (pub_it != publishers_.end()) {
108  auto publisher = pub_it->second.lock();
109  if (publisher) {
110  gid_to_publisher_info_.erase(publisher->get_gid());
111  } else {
112  // Publisher weak_ptr already expired, fall back to linear scan by pub_id.
113  for (auto git = gid_to_publisher_info_.begin(); git != gid_to_publisher_info_.end(); ++git) {
114  if (git->second.pub_id == intra_process_publisher_id) {
115  gid_to_publisher_info_.erase(git);
116  break;
117  }
118  }
119  }
120  }
121 
122  publishers_.erase(intra_process_publisher_id);
123  publisher_buffers_.erase(intra_process_publisher_id);
124  pub_to_subs_.erase(intra_process_publisher_id);
125 }
126 
127 bool
129 {
130  std::shared_lock<std::shared_timed_mutex> lock(mutex_);
131 
132  // Single O(1) hash map lookup - struct contains both ID and weak_ptr
133  auto it = gid_to_publisher_info_.find(*id);
134  if (it == gid_to_publisher_info_.end()) {
135  return false;
136  }
137 
138  // Verify the publisher still exists by checking the weak_ptr
139  auto publisher = it->second.publisher.lock();
140  return publisher != nullptr;
141 }
142 
143 size_t
144 IntraProcessManager::get_subscription_count(uint64_t intra_process_publisher_id) const
145 {
146  std::shared_lock<std::shared_timed_mutex> lock(mutex_);
147 
148  auto publisher_it = pub_to_subs_.find(intra_process_publisher_id);
149  if (publisher_it == pub_to_subs_.end()) {
150  // Publisher is either invalid or no longer exists.
151  RCLCPP_WARN(
152  rclcpp::get_logger("rclcpp"),
153  "Calling get_subscription_count for invalid or no longer existing publisher id");
154  return 0;
155  }
156 
157  auto count =
158  publisher_it->second.take_shared_subscriptions.size() +
159  publisher_it->second.take_ownership_subscriptions.size();
160 
161  return count;
162 }
163 
164 SubscriptionIntraProcessBase::SharedPtr
165 IntraProcessManager::get_subscription_intra_process(uint64_t intra_process_subscription_id)
166 {
167  std::shared_lock<std::shared_timed_mutex> lock(mutex_);
168 
169  auto subscription_it = subscriptions_.find(intra_process_subscription_id);
170  if (subscription_it == subscriptions_.end()) {
171  return nullptr;
172  } else {
173  auto subscription = subscription_it->second.lock();
174  if (subscription) {
175  return subscription;
176  } else {
177  subscriptions_.erase(subscription_it);
178  return nullptr;
179  }
180  }
181 }
182 
183 uint64_t
184 IntraProcessManager::get_next_unique_id()
185 {
186  auto next_id = _next_unique_id.fetch_add(1, std::memory_order_relaxed);
187  // Check for rollover (we started at 1).
188  if (0 == next_id) {
189  // This puts a technical limit on the number of times you can add a publisher or subscriber.
190  // But even if you could add (and remove) them at 1 kHz (very optimistic rate)
191  // it would still be a very long time before you could exhaust the pool of id's:
192  // 2^64 / 1000 times per sec / 60 sec / 60 min / 24 hours / 365 days = 584,942,417 years
193  // So around 585 million years. Even at 1 GHz, it would take 585 years.
194  // I think it's safe to avoid trying to handle overflow.
195  // If we roll over then it's most likely a bug.
196  // *INDENT-OFF* (prevent uncrustify from making unnecessary indents here)
197  throw std::overflow_error(
198  "exhausted the unique id's for publishers and subscribers in this process "
199  "(congratulations your computer is either extremely fast or extremely old)");
200  // *INDENT-ON*
201  }
202  return next_id;
203 }
204 
205 void
206 IntraProcessManager::insert_sub_id_for_pub(
207  uint64_t sub_id,
208  uint64_t pub_id,
209  bool use_take_shared_method)
210 {
211  if (use_take_shared_method) {
212  pub_to_subs_[pub_id].take_shared_subscriptions.push_back(sub_id);
213  } else {
214  pub_to_subs_[pub_id].take_ownership_subscriptions.push_back(sub_id);
215  }
216 }
217 
218 bool
219 IntraProcessManager::can_communicate(
220  const rclcpp::PublisherBase::SharedPtr & pub,
221  const rclcpp::experimental::SubscriptionIntraProcessBase::SharedPtr & sub) const
222 {
223  // publisher and subscription must be on the same topic
224  if (strcmp(pub->get_topic_name(), sub->get_topic_name()) != 0) {
225  return false;
226  }
227 
228  auto check_result = rclcpp::qos_check_compatible(pub->get_actual_qos(), sub->get_actual_qos());
229  if (check_result.compatibility == rclcpp::QoSCompatibility::Error) {
230  return false;
231  }
232 
233  return true;
234 }
235 
236 size_t
237 IntraProcessManager::lowest_available_capacity(const uint64_t intra_process_publisher_id) const
238 {
239  size_t capacity = std::numeric_limits<size_t>::max();
240 
241  auto publisher_it = pub_to_subs_.find(intra_process_publisher_id);
242  if (publisher_it == pub_to_subs_.end()) {
243  // Publisher is either invalid or no longer exists.
244  RCLCPP_WARN(
245  rclcpp::get_logger("rclcpp"),
246  "Calling lowest_available_capacity for invalid or no longer existing publisher id");
247  return 0u;
248  }
249 
250  if (publisher_it->second.take_shared_subscriptions.empty() &&
251  publisher_it->second.take_ownership_subscriptions.empty())
252  {
253  // no subscriptions available
254  return 0u;
255  }
256 
257  auto available_capacity = [this, &capacity](const uint64_t intra_process_subscription_id)
258  {
259  auto subscription_it = subscriptions_.find(intra_process_subscription_id);
260  if (subscription_it != subscriptions_.end()) {
261  auto subscription = subscription_it->second.lock();
262  if (subscription) {
263  capacity = std::min(capacity, subscription->available_capacity());
264  }
265  } else {
266  // Subscription is either invalid or no longer exists.
267  RCLCPP_WARN(
268  rclcpp::get_logger("rclcpp"),
269  "Calling available_capacity for invalid or no longer existing subscription id");
270  }
271  };
272 
273  for (const auto sub_id : publisher_it->second.take_shared_subscriptions) {
274  available_capacity(sub_id);
275  }
276 
277  for (const auto sub_id : publisher_it->second.take_ownership_subscriptions) {
278  available_capacity(sub_id);
279  }
280 
281  return capacity;
282 }
283 } // namespace experimental
284 } // namespace rclcpp
RCLCPP_PUBLIC bool matches_any_publishers(const rmw_gid_t *id) const
Return true if the given rmw_gid_t matches any stored Publishers.
RCLCPP_PUBLIC void remove_subscription(uint64_t intra_process_subscription_id)
Unregister a subscription using the subscription's unique id.
RCLCPP_PUBLIC void remove_publisher(uint64_t intra_process_publisher_id)
Unregister a publisher using the publisher's unique id.
RCLCPP_PUBLIC size_t get_subscription_count(uint64_t intra_process_publisher_id) const
Return the number of intraprocess subscriptions that are matched with a given publisher id.
RCLCPP_PUBLIC size_t lowest_available_capacity(const uint64_t intra_process_publisher_id) const
Return the lowest available capacity for all subscription buffers for a publisher id.
RCLCPP_PUBLIC uint64_t add_publisher(const rclcpp::PublisherBase::SharedPtr &publisher, const rclcpp::experimental::buffers::IntraProcessBufferBase::SharedPtr &buffer=rclcpp::experimental::buffers::IntraProcessBufferBase::SharedPtr())
Register a publisher with the manager, returns the publisher unique id.
Versions of rosidl_typesupport_cpp::get_message_type_support_handle that handle adapted types.
RCLCPP_PUBLIC QoSCheckCompatibleResult qos_check_compatible(const QoS &publisher_qos, const QoS &subscription_qos)
Check if two QoS profiles are compatible.
Definition: qos.cpp:357
RCLCPP_PUBLIC Logger get_logger(const std::string &name)
Return a named logger.
Definition: logger.cpp:34