CARLA
Loading...
Searching...
No Matches
primaryCommands.cpp
Go to the documentation of this file.
1// Copyright (c) 2026 Computer Vision Center (CVC) at the Universitat Autonoma
2// de Barcelona (UAB).
3//
4// This work is licensed under the terms of the MIT license.
5// For a copy, see <https://opensource.org/licenses/MIT>.
6
8
9// #include "carla/Logging.h"
16
17#include <algorithm>
18#include <cstring>
19#include <string_view>
20#include <vector>
21
22namespace carla {
23namespace multigpu {
24
27
28PrimaryCommands::PrimaryCommands(std::shared_ptr<Router> router) :
29 _router(router) {
30}
31
32void PrimaryCommands::set_router(std::shared_ptr<Router> router) {
33 _router = router;
34}
35
36// broadcast to all secondary servers the frame data
38 _router->Write(MultiGPUCommand::SEND_FRAME, std::move(buffer));
39 // log_info("sending frame command");
40}
41
42// broadcast to all secondary servers the map to load
43void PrimaryCommands::SendLoadMap(std::string map, const LoadMapExtras &extras) {
44 // Held across the broadcast so no GetToken() can cache a token of the old
45 // episode between the purge and the secondaries receiving LOAD_MAP.
46 std::scoped_lock<std::mutex> lock(_mutex);
47 _tokens.clear();
48 _servers.clear();
49 _router->WriteLoadMap(map, extras);
50}
51
52// send to who the router wants the request for a token
53std::optional<token_type> PrimaryCommands::SendGetToken(
54 stream_id sensor_id,
55 actor_id sensor_actor_id,
56 const std::weak_ptr<Primary> *target,
57 std::weak_ptr<Primary> &out_session,
58 TokenError &error) {
59 log_info("asking for a token");
60 error = TokenError::None;
61 const GetTokenRequest request{sensor_id, sensor_actor_id};
62 carla::Buffer buf(reinterpret_cast<const carla::Buffer::value_type *>(&request), sizeof(request));
63 auto fut = (target != nullptr) ?
64 _router->WriteToOne(*target, MultiGPUCommand::GET_TOKEN, std::move(buf)) :
65 _router->WriteToNext(MultiGPUCommand::GET_TOKEN, std::move(buf));
66
67 auto response = fut.get();
68 if (response.session == nullptr) {
69 log_error("multigpu: no secondary provided a token for sensor ", sensor_id);
70 error = TokenError::NoReply;
71 return std::nullopt;
72 }
73 const std::string_view payload(reinterpret_cast<const char *>(response.buffer.data()), response.buffer.size());
74 if (payload == kTokenNotReadyMarker) {
75 log_error("multigpu: secondary cannot serve sensor ", sensor_id, " (actor ", sensor_actor_id, ")");
76 error = TokenError::Refused;
77 return std::nullopt;
78 }
79 if (response.buffer.size() < sizeof(carla::streaming::detail::token_data)) {
80 log_error("multigpu: malformed token reply for sensor ", sensor_id, ": ", response.buffer.size(), " bytes");
81 error = TokenError::Refused;
82 return std::nullopt;
83 }
84 out_session = response.session;
86 std::memcpy(&data, response.buffer.data(), sizeof(data));
87 token_type new_token(data);
88 // Clients key and unsubscribe streams by the primary's stream id only.
89 if (new_token.get_stream_id() != sensor_id) {
90 log_warning("multigpu: secondary answered sensor ", sensor_id, " with stream id ", new_token.get_stream_id());
91 new_token.set_stream_id(sensor_id);
92 }
93 log_info("got a token: ", new_token.get_stream_id(), ", ", new_token.get_port());
94 return new_token;
95}
96
97// send to know if a connection is alive
99 std::scoped_lock<std::mutex> lock(_mutex);
100 std::string msg("Are you alive?");
101 carla::Buffer buf(reinterpret_cast<const unsigned char *>(msg.c_str()), static_cast<size_t>(msg.size()));
102 log_info("sending is alive command");
103 auto fut = _router->WriteToNext(MultiGPUCommand::YOU_ALIVE, std::move(buf));
104 auto response = fut.get();
105 if (response.session == nullptr) {
106 log_error("multigpu: is-alive command got no reply");
107 return;
108 }
109 log_info("response from alive command: ", response.buffer.data());
110}
111
113 // search if the sensor has been activated in any secondary server
114 auto it = _servers.find(sensor_id);
115 if (it != _servers.end()) {
116 carla::Buffer buf(reinterpret_cast<carla::Buffer::value_type *>(&sensor_id),
117 static_cast<size_t>(sizeof(stream_id)));
118 auto fut = _router->WriteToOne(it->second.session, MultiGPUCommand::ENABLE_ROS, std::move(buf));
119
120 auto response = fut.get();
121 if ((response.session == nullptr) || response.buffer.empty()) {
122 log_error("enable_for_ros for sensor ", sensor_id, " got no reply from its secondary server");
123 return false;
124 }
125 if (!ReadBoolReply(response.buffer)) {
126 log_error("enable_for_ros for sensor ", sensor_id, " was refused by its secondary server");
127 return false;
128 }
129 return true;
130 } else {
131 log_error("enable_for_ros for sensor", sensor_id, " not found on any server");
132 return false;
133 }
134}
135
137 // search if the sensor has been activated in any secondary server
138 auto it = _servers.find(sensor_id);
139 if (it != _servers.end()) {
140 carla::Buffer buf(reinterpret_cast<carla::Buffer::value_type *>(&sensor_id),
141 static_cast<size_t>(sizeof(stream_id)));
142 auto fut = _router->WriteToOne(it->second.session, MultiGPUCommand::DISABLE_ROS, std::move(buf));
143
144 auto response = fut.get();
145 if ((response.session == nullptr) || response.buffer.empty()) {
146 log_error("disable_for_ros for sensor ", sensor_id, " got no reply from its secondary server");
147 } else if (!ReadBoolReply(response.buffer)) {
148 log_error("disable_for_ros for sensor ", sensor_id, " was refused by its secondary server");
149 }
150 } else {
151 log_error("disable_for_ros for sensor", sensor_id, " not found on any server");
152 }
153}
154
156 // search if the sensor has been activated in any secondary server
157 auto it = _servers.find(sensor_id);
158 if (it != _servers.end()) {
159 carla::Buffer buf(reinterpret_cast<carla::Buffer::value_type *>(&sensor_id),
160 static_cast<size_t>(sizeof(stream_id)));
161 auto fut = _router->WriteToOne(it->second.session, MultiGPUCommand::IS_ENABLED_ROS, std::move(buf));
162
163 auto response = fut.get();
164 if ((response.session == nullptr) || response.buffer.empty()) {
165 log_error("is_enabled_for_ros for sensor ", sensor_id, " got no reply from its secondary server");
166 return false;
167 }
168 return ReadBoolReply(response.buffer);
169 } else {
170 log_error("is_enabled_for_ros for sensor", sensor_id, " not found on any server");
171 return false;
172 }
173}
174
175std::optional<token_type> PrimaryCommands::GetToken(stream_id sensor_id, actor_id sensor_actor_id) {
176 std::scoped_lock<std::mutex> lock(_mutex);
177 if (IsRoutedToLiveSecondary(sensor_id)) {
178 auto it = _tokens.find(sensor_id);
179 if (it != _tokens.end()) {
180 log_debug("Using token from already activated sensor: ", it->second.get_stream_id(), ", ", it->second.get_port());
181 return it->second;
182 }
183 }
184 const bool lost = _servers.contains(sensor_id);
185 if (lost) {
186 log_warning("multigpu: secondary of sensor ", sensor_id, " is gone, routing it again");
187 }
188 // enable the sensor on one secondary server; the session that actually
189 // answers comes back from the round trip itself, not from a separate
190 // (and potentially stale) "next server" lookup.
191 std::weak_ptr<Primary> server;
193 auto token = SendGetToken(sensor_id, sensor_actor_id, nullptr, server, error);
194 if (!token) {
195 if (error == TokenError::Refused) {
196 _servers.erase(sensor_id);
197 _tokens.erase(sensor_id);
198 }
199 return std::nullopt;
200 }
201 _tokens.erase(sensor_id);
202 _tokens.emplace(sensor_id, *token);
203 auto route = _servers.try_emplace(sensor_id, Route{server, sensor_actor_id}).first;
204 route->second.session = server;
205 route->second.actor = sensor_actor_id;
206 if (lost) {
207 RestoreRosAfterReroute(sensor_id);
208 }
209 log_debug("Using token from new activated sensor: ", token->get_stream_id(), ", ", token->get_port());
210 return token;
211}
212
213bool PrimaryCommands::EnableForROS(stream_id sensor_id, actor_id sensor_actor_id) {
214 {
215 std::scoped_lock<std::mutex> lock(_mutex);
216 if (IsRoutedToLiveSecondary(sensor_id)) {
217 const bool enabled = SendEnableForROS(sensor_id);
218 if (enabled) {
219 _servers[sensor_id].ros_enabled = true;
220 }
221 return enabled;
222 }
223 }
224 // The sensor has not been routed to a live secondary yet. GetToken()
225 // performs that routing under its own critical section, so the lock above
226 // must be released first to avoid a self-deadlock on the recursive call
227 // below. On failure nothing was routed, so recursing would retry forever.
228 if (!GetToken(sensor_id, sensor_actor_id)) {
229 log_error("enable_for_ros for sensor ", sensor_id, " failed: no secondary server accepted it");
230 return false;
231 }
232 return EnableForROS(sensor_id, sensor_actor_id);
233}
234
236 std::scoped_lock<std::mutex> lock(_mutex);
237 auto it = _servers.find(sensor_id);
238 if (it == _servers.end()) {
239 return;
240 }
241 it->second.ros_enabled = false;
242 if (_router->IsConnected(it->second.session)) {
243 SendDisableForROS(sensor_id);
244 }
245}
246
248 std::scoped_lock<std::mutex> lock(_mutex);
249 if (IsRoutedToLiveSecondary(sensor_id)) {
250 return SendIsEnabledForROS(sensor_id);
251 }
252 return false;
253}
254
256 std::scoped_lock<std::mutex> lock(_mutex);
257 std::size_t rerouted = 0u;
258 for (const auto &session : _router->TakeNewSessions()) {
259 std::vector<stream_id> lost;
260 for (const auto &[sensor_id, route] : _servers) {
261 if (!_router->IsConnected(route.session)) {
262 lost.push_back(sensor_id);
263 }
264 }
265 std::sort(lost.begin(), lost.end());
266 for (const stream_id sensor_id : lost) {
267 if (!_router->IsConnected(session)) {
268 break;
269 }
270 auto route = _servers.find(sensor_id);
271 std::weak_ptr<Primary> server;
273 auto token = SendGetToken(sensor_id, route->second.actor, &session, server, error);
274 if (!token) {
275 if ((error == TokenError::NoReply) || !_router->IsConnected(session)) {
276 log_warning("multigpu: the new secondary left while routing sensor ", sensor_id, "; it stays lost");
277 break;
278 }
279 log_warning("multigpu: could not route sensor ", sensor_id, " to the new secondary; its listeners must listen() again");
280 _servers.erase(route);
281 _tokens.erase(sensor_id);
282 continue;
283 }
284 auto old_token = _tokens.find(sensor_id);
285 if ((old_token != _tokens.end()) && (old_token->second.get_port() != token->get_port())) {
286 log_warning("multigpu: sensor ", sensor_id, " moved from port ", old_token->second.get_port(),
287 " to port ", token->get_port(), "; existing listeners cannot follow it");
288 }
289 _tokens.erase(sensor_id);
290 _tokens.emplace(sensor_id, *token);
291 route->second.session = server;
292 log_info("multigpu: routed sensor ", sensor_id, " to the new secondary at port ", token->get_port());
293 ++rerouted;
294 RestoreRosAfterReroute(sensor_id);
295 }
296 }
297 return rerouted;
298}
299
301 std::scoped_lock<std::mutex> lock(_mutex);
302 _servers.erase(sensor_id);
303 _tokens.erase(sensor_id);
304}
305
307 auto it = _servers.find(sensor_id);
308 return (it != _servers.end()) && _router->IsConnected(it->second.session);
309}
310
312 auto it = _servers.find(sensor_id);
313 if ((it == _servers.end()) || !it->second.ros_enabled) {
314 return;
315 }
316 if (!SendEnableForROS(sensor_id)) {
317 log_warning("multigpu: could not enable ROS again for sensor ", sensor_id, " on its new secondary");
318 }
319}
320
322 bool value = false;
323 std::memcpy(&value, buffer.data(), sizeof(value));
324 return value;
325}
326
327} // namespace multigpu
328} // namespace carla
long long first
Definition LaneGraph.cpp:34
A piece of raw data.
const value_type * data() const noexcept
Direct access to the allocated memory or nullptr if no memory is allocated.
unsigned char value_type
std::unordered_map< stream_id, token_type > _tokens
void set_router(std::shared_ptr< Router > router)
std::optional< token_type > GetToken(stream_id sensor_id, actor_id sensor_actor_id)
Returns std::nullopt if no secondary could provide the token (e.g.
std::size_t RerouteLostSensors()
Routes every sensor whose secondary disconnected to the secondaries connected since the previous call...
void DisableForROS(stream_id sensor_id)
bool SendIsEnabledForROS(stream_id sensor_id)
void SendFrameData(carla::Buffer buffer)
bool SendEnableForROS(stream_id sensor_id)
std::optional< token_type > SendGetToken(stream_id sensor_id, actor_id sensor_actor_id, const std::weak_ptr< Primary > *target, std::weak_ptr< Primary > &out_session, TokenError &error)
bool IsRoutedToLiveSecondary(stream_id sensor_id)
Whether sensor_id is routed to a secondary that is still connected.
bool EnableForROS(stream_id sensor_id, actor_id sensor_actor_id)
Returns false if no secondary accepted this sensor or the secondary holding it did not reply; ROS ena...
std::unordered_map< stream_id, Route > _servers
void SendDisableForROS(stream_id sensor_id)
void ForgetSensor(stream_id sensor_id)
Drops the routing of a destroyed sensor so it is never routed again.
static bool ReadBoolReply(const carla::Buffer &buffer)
Reads a one-bool reply; buffer must not be empty.
void RestoreRosAfterReroute(stream_id sensor_id)
Enables ROS again on the new secondary of a re-routed sensor if it was enabled before.
bool IsEnabledForROS(stream_id sensor_id)
std::shared_ptr< Router > _router
void SendLoadMap(std::string map, const LoadMapExtras &extras={})
Serializes a stream endpoint.
void set_stream_id(stream_id_type id)
constexpr std::string_view kTokenNotReadyMarker
GET_TOKEN reply when the secondary cannot resolve the sensor to a stream (or the request is malformed...
Definition commands.h:159
carla::streaming::detail::stream_id_type stream_id
This file contains definitions of common data structures used in traffic manager.
static void log_error(Args &&... args)
Definition Logging.h:110
static void log_info(Args &&... args)
Definition Logging.h:82
static void log_warning(Args &&... args)
Definition Logging.h:96
static void log_debug(Args &&... args)
Definition Logging.h:68
GET_TOKEN request payload.
Definition commands.h:151
What a LOAD_MAP carries besides the map: for a world the primary generated from OpenDRIVE (generate_o...
Definition commands.h:51