CARLA
Loading...
Searching...
No Matches
secondary.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
9
12
13#include "carla/BufferPool.h"
14#include "carla/Debug.h"
15#include "carla/Exception.h"
16#include "carla/Logging.h"
17#include "carla/Time.h"
18
19#include <boost/asio/connect.hpp>
20#include <boost/asio/read.hpp>
21#include <boost/asio/write.hpp>
22#include <boost/asio/post.hpp>
23#include <boost/asio/bind_executor.hpp>
24
25#include <exception>
26
27namespace carla {
28namespace multigpu {
29
31 boost::asio::ip::tcp::endpoint ep,
33 _pool(),
34 _socket(_pool.io_context()),
35 _endpoint(ep),
36 _strand(_pool.io_context()),
37 _connection_timer(_pool.io_context()),
38 _buffer_pool(std::make_shared<BufferPool>()) {
39
40 _commander.set_callback(callback);
41 }
42
43
45 std::string ip,
46 uint16_t port,
48 _pool(),
49 _socket(_pool.io_context()),
50 _strand(_pool.io_context()),
51 _connection_timer(_pool.io_context()),
52 _buffer_pool(std::make_shared<BufferPool>()) {
53
54 boost::asio::ip::address ip_address = boost::asio::ip::make_address(ip);
55 _endpoint = boost::asio::ip::tcp::endpoint(ip_address, port);
56 _commander.set_callback(callback);
57 }
58
62
64 std::call_once(_run_flag, [this]() { AsyncRun(2u); });
65
66 _commander.set_secondary(shared_from_this());
67
68 std::weak_ptr<Secondary> weak = shared_from_this();
69 boost::asio::post(_strand, [weak]() {
70 auto self = weak.lock();
71 if (!self) return;
72
73 if (self->_done) {
74 return;
75 }
76
77 if (self->_socket.is_open()) {
78 // As Disconnect(): what is queued belongs to the connection closed here.
79 self->Disconnect();
80 }
81
82 auto handle_connect = [weak](boost::system::error_code ec) {
83 auto self = weak.lock();
84 if (!self) return;
85 if (ec) {
86 log_error("secondary server: connection failed:", ec.message());
87 if (!self->_done)
88 self->Reconnect();
89 return;
90 }
91
92 if (self->_done) {
93 return;
94 }
95
96 // This forces not using Nagle's algorithm.
97 // Improves the sync mode velocity on Linux by a factor of ~3.
98 self->_socket.set_option(boost::asio::ip::tcp::no_delay(true));
99 EnableKeepAlive(self->_socket);
100 self->_connected = true;
101
102 log_info("secondary server: connected to ", self->_endpoint);
103
104 self->ReadData();
105 };
106
107 self->_socket.async_connect(self->_endpoint, boost::asio::bind_executor(self->_strand, handle_connect));
108 });
109 }
110
111 void Secondary::Stop() {
112 _done = true;
113 _connected = false;
114 // Joins the io threads, so no command callback runs after Stop() returns;
115 // the socket and timer are then only touched from this thread.
116 _pool.Stop();
117 _connection_timer.cancel();
118 boost::system::error_code ec;
119 _socket.close(ec);
120 }
121
122 void Secondary::Reconnect() {
123 std::weak_ptr<Secondary> weak = shared_from_this();
124 _connection_timer.expires_after(time_duration::seconds(1u).to_chrono());
125 _connection_timer.async_wait([weak](boost::system::error_code ec) {
126 auto self = weak.lock();
127 if (!self) return;
128 if (!ec) {
129 self->Connect();
130 }
131 });
132 }
133
134 void Secondary::AsyncRun(size_t worker_threads) {
135 _pool.AsyncRun(worker_threads);
136 }
137
138 void Secondary::Write(std::shared_ptr<const carla::streaming::detail::tcp::Message> message) {
139 DEBUG_ASSERT(message != nullptr);
140 DEBUG_ASSERT(!message->empty());
141 std::weak_ptr<Secondary> weak = shared_from_this();
142 boost::asio::post(_strand, [weak, message]() {
143 auto self = weak.lock();
144 if (!self) return;
145 if (!self->_socket.is_open()) {
146 return;
147 }
148 self->_write_queue.push_back(message);
149 if (!self->_is_writing) {
150 self->WriteNext();
151 }
152 });
153 }
154
155 void Secondary::WriteNext() {
156 if (_write_queue.empty() || !_socket.is_open()) {
157 _write_queue.clear();
158 _is_writing = false;
159 return;
160 }
161 _is_writing = true;
162 const message_type message = _write_queue.front();
163 const uint64_t generation = _write_generation;
164 std::weak_ptr<Secondary> weak = shared_from_this();
165 auto handle_sent = [weak, message, generation](const boost::system::error_code &ec, size_t) {
166 auto self = weak.lock();
167 if (!self) return;
168 // Disconnect() dropped the queue this write was the front of: what the
169 // queue holds now, if anything, came after.
170 if (generation != self->_write_generation) {
171 return;
172 }
173 if (ec) {
174 log_error("error sending data: ", ec.message());
175 self->_write_queue.clear();
176 self->_is_writing = false;
177 return;
178 }
179 self->_write_queue.pop_front();
180 self->WriteNext();
181 };
182
183 boost::asio::async_write(
184 _socket,
185 message->GetBufferSequence(),
186 boost::asio::bind_executor(_strand, handle_sent));
187 }
188
189 void Secondary::Write(Buffer buffer) {
190 Write(Secondary::MakeMessage(carla::BufferView::CreateFrom(std::move(buffer))));
191 }
192
193 void Secondary::Write(std::string text) {
194 // One message, size prefix included, through the same ordered queue.
195 Write(Buffer(reinterpret_cast<const unsigned char *>(text.data()), text.size()));
196 }
197
198 void Secondary::Disconnect() {
199 _connected = false;
200 _write_queue.clear();
201 _is_writing = false;
202 ++_write_generation;
203 boost::system::error_code ignored;
204 _socket.close(ignored);
205 }
206
207 void Secondary::ReadData() {
208 std::weak_ptr<Secondary> weak = shared_from_this();
209 boost::asio::post(_strand, [weak]() {
210 auto self = weak.lock();
211 if (!self) return;
212 if (self->_done) {
213 return;
214 }
215
216 auto message = std::make_shared<IncomingMessage>(self->_buffer_pool->Pop());
217
218 auto handle_read_data = [weak, message](boost::system::error_code ec, size_t DEBUG_ONLY(bytes)) {
219 auto self = weak.lock();
220 if (!self) return;
221 if (!ec) {
222 DEBUG_ASSERT_EQ(bytes, message->size());
223 DEBUG_ASSERT_NE(bytes, 0u);
224 // Move the buffer to the callback function and start reading the next
225 // piece of data.
226 self->GetCommander().process_command(message->pop());
227 self->ReadData();
228 } else {
229 // The primary is gone. Established sessions are not resumed: close
230 // ours, so the primary's side sees it closed too.
231 if (!self->_done) {
232 log_error("secondary server: failed to read data: ", ec.message());
233 }
234 self->Disconnect();
235 }
236 };
237
238 auto handle_read_header = [weak, message, handle_read_data](
239 boost::system::error_code ec,
240 size_t DEBUG_ONLY(bytes)) {
241 auto self = weak.lock();
242 if (!self) return;
243 if (!ec && (message->size() > kMaxMultiGpuMessageBytes)) {
244 log_error("secondary server: the primary announced a", message->size(),
245 "byte message, larger than any valid one; disconnecting");
246 self->Disconnect();
247 } else if (!ec && (message->size() > 0u)) {
249 if (self->_done) {
250 return;
251 }
252 // Now that we know the size of the coming buffer, we can allocate our
253 // buffer and start putting data into it.
254 boost::asio::async_read(
255 self->_socket,
256 message->buffer(),
257 boost::asio::bind_executor(self->_strand, handle_read_data));
258 } else {
259 if (!self->_done) {
260 log_error("secondary server: failed to read header: ", ec.message());
261 }
262 self->Disconnect();
263 }
264 };
265
266 // Read the size of the buffer that is coming.
267 boost::asio::async_read(
268 self->_socket,
269 message->size_as_buffer(),
270 boost::asio::bind_executor(self->_strand, handle_read_header));
271 });
272 }
273
274} // namespace multigpu
275} // namespace carla
#define DEBUG_ASSERT(predicate)
Definition Debug.h:66
#define DEBUG_ASSERT_EQ(lhs, rhs)
Definition Debug.h:76
#define DEBUG_ASSERT_NE(lhs, rhs)
Definition Debug.h:77
#define DEBUG_ONLY(code)
Definition Debug.h:55
A pool of Buffer.
Definition BufferPool.h:29
static std::shared_ptr< BufferView > CreateFrom(Buffer &&buffer)
Definition BufferView.h:56
A piece of raw data.
void Stop()
Stop the ThreadPool and join all its threads.
Definition ThreadPool.h:79
std::function< void(MultiGPUCommand, carla::Buffer)> callback_type
void set_secondary(std::shared_ptr< Secondary > secondary)
void set_callback(callback_type callback)
boost::asio::ip::tcp::endpoint _endpoint
Definition secondary.h:97
std::once_flag _run_flag
Definition secondary.h:110
Secondary(boost::asio::ip::tcp::endpoint ep, SecondaryCommands::callback_type callback)
Definition secondary.cpp:30
std::shared_ptr< const carla::streaming::detail::tcp::Message > message_type
Definition secondary.h:83
boost::asio::io_context::strand _strand
Definition secondary.h:98
SecondaryCommands _commander
Definition secondary.h:111
void AsyncRun(size_t worker_threads)
static time_duration seconds(size_t timeout)
Definition Time.h:23
void EnableKeepAlive(boost::asio::ip::tcp::socket &socket)
Lets either end notice a peer that vanished without closing the connection (a killed host,...
Definition keepAlive.h:21
constexpr std::size_t kMaxMultiGpuMessageBytes
Largest message either end accepts: a frame (capped at 1 GiB by the primary) plus room for its header...
Definition commands.h:37
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