CARLA
Loading...
Searching...
No Matches
streaming/detail/tcp/Client.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/BufferPool.h"
10#include "carla/Debug.h"
11#include "carla/Exception.h"
12#include "carla/Logging.h"
13#include "carla/Time.h"
14
15#include <boost/asio/connect.hpp>
16#include <boost/asio/post.hpp>
17#include <boost/asio/read.hpp>
18#include <boost/asio/write.hpp>
19#include <boost/asio/bind_executor.hpp>
20
21#include <exception>
22
23namespace carla {
24namespace streaming {
25namespace detail {
26namespace tcp {
27
28 // ===========================================================================
29 // -- IncomingMessage --------------------------------------------------------
30 // ===========================================================================
31
32 /// Helper for reading incoming TCP messages. Allocates the whole message in
33 /// a single buffer.
35 public:
36
38
39 boost::asio::mutable_buffer size_as_buffer() {
40 return boost::asio::buffer(&_size, sizeof(_size));
41 }
42
43 boost::asio::mutable_buffer buffer() {
44 DEBUG_ASSERT(_size > 0u);
46 return _message.buffer();
47 }
48
49 auto size() const {
50 return _size;
51 }
52
53 auto pop() {
54 return std::move(_message);
55 }
56
57 private:
58
60
62 };
63
64 // ===========================================================================
65 // -- Client -----------------------------------------------------------------
66 // ===========================================================================
67
69 boost::asio::io_context &io_context,
70 const token_type &token,
73 std::string("tcp client ") + std::to_string(token.get_stream_id())),
74 _token(token),
75 _callback(std::move(callback)),
76 _socket(io_context),
77 _strand(io_context),
78 _connection_timer(io_context),
79 _buffer_pool(std::make_shared<BufferPool>()) {
80 if (!_token.protocol_is_tcp()) {
81 throw_exception(std::invalid_argument("invalid token, only TCP tokens supported"));
82 }
83 }
84
85 Client::~Client() = default;
86
88 auto self = shared_from_this();
89 if (_done) {
90 return;
91 }
92
93 using boost::system::error_code;
94
95 if (_socket.is_open()) {
96 _socket.close();
97 }
98
101 const auto ep = _token.to_tcp_endpoint();
102
103 auto handle_connect = [this, self, ep](error_code ec) {
104 if (!ec) {
105 if (_done) {
106 return;
107 }
108 // This forces not using Nagle's algorithm.
109 // Improves the sync mode velocity on Linux by a factor of ~3.
110 _socket.set_option(boost::asio::ip::tcp::no_delay(true));
111 log_debug("streaming client: connected to", ep);
112 // Send the stream id to subscribe to the stream.
113 const auto &stream_id = _token.get_stream_id();
114 log_debug("streaming client: sending stream id", stream_id);
115 boost::asio::async_write(
116 _socket,
117 boost::asio::buffer(&stream_id, sizeof(stream_id)),
118 boost::asio::bind_executor(_strand, [=](error_code ec, size_t DEBUG_ONLY(bytes)) {
119 // Ensures to stop the execution once the connection has been stopped.
120 if (_done) {
121 return;
122 }
123 if (!ec) {
124 DEBUG_ASSERT_EQ(bytes, sizeof(stream_id));
125 // If succeeded start reading data.
126 ReadData();
127 } else {
128 // Else try again.
129 log_debug("streaming client: failed to send stream id:", ec.message());
130 Connect();
131 }
132 }));
133 } else {
134 log_info("streaming client: connection failed:", ec.message());
135 Reconnect();
136 }
137 };
138
139 log_debug("streaming client: connecting to", ep);
140 _socket.async_connect(ep, boost::asio::bind_executor(_strand, handle_connect));
141 }
142
144 _connection_timer.cancel();
145 auto self = shared_from_this();
146 // Serialize the shutdown with the completion handlers: every read/connect
147 // handler runs on _strand, so mutating _done and closing the socket from
148 // the caller's thread (game thread via sensor destruction) races a
149 // concurrent async_read initiation on an io_context worker -- asio sockets
150 // are not thread-safe, and the race crashed the client inside ReadData
151 // when switching sensors. self keeps the client alive until this runs.
152 boost::asio::post(_strand, [this, self]() {
153 _done = true;
154 if (_socket.is_open()) {
155 _socket.close();
156 }
157 });
158 }
159
161 auto self = shared_from_this();
162 _connection_timer.expires_after(time_duration::seconds(1u).to_chrono());
163 _connection_timer.async_wait([this, self](boost::system::error_code ec) {
164 if (!ec) {
165 Connect();
166 }
167 });
168 }
169
171 auto self = shared_from_this();
172 if (_done) {
173 return;
174 }
175
176 // log_debug("streaming client: Client::ReadData");
177
178 auto message = std::make_shared<IncomingMessage>(_buffer_pool->Pop());
179
180 auto handle_read_data = [this, self, message](boost::system::error_code ec, size_t DEBUG_ONLY(bytes)) {
181 DEBUG_ONLY(log_debug("streaming client: Client::ReadData.handle_read_data", bytes, "bytes"));
182 // A read that completed while Stop() was being processed must not
183 // deliver its payload: the subscriber that _callback points into is
184 // already being torn down (sensor destruction), and calling into it
185 // is a use-after-free (observed as a manual_control segfault when
186 // switching sensors).
187 if (_done) {
188 return;
189 }
190 if (!ec) {
191 DEBUG_ASSERT_EQ(bytes, message->size());
192 DEBUG_ASSERT_NE(bytes, 0u);
193 // Move the buffer to the callback function and start reading the next
194 // piece of data.
195 // log_debug("streaming client: success reading data, calling the callback");
196 // An exception leaving this handler would escape io_context::run()
197 // on a worker thread and call std::terminate, killing the client
198 // process; log it and keep reading the stream instead.
199#ifndef LIBCARLA_NO_EXCEPTIONS
200 try {
201#endif // LIBCARLA_NO_EXCEPTIONS
202 _callback(message->pop());
203#ifndef LIBCARLA_NO_EXCEPTIONS
204 } catch (const std::exception &e) {
205 log_error("streaming client: exception in stream callback:", e.what());
206 }
207#endif // LIBCARLA_NO_EXCEPTIONS
208 ReadData();
209 } else {
210 // As usual, if anything fails start over from the very top.
211 log_debug("streaming client: failed to read data:", ec.message());
212 Connect();
213 }
214 };
215
216 auto handle_read_header = [this, self, message, handle_read_data](
217 boost::system::error_code ec,
218 size_t DEBUG_ONLY(bytes)) {
219 DEBUG_ONLY(log_debug("streaming client: Client::ReadData.handle_read_header", bytes, "bytes"));
220 if (!ec && (message->size() > 0u)) {
221 DEBUG_ASSERT_EQ(bytes, sizeof(message_size_type));
222 if (_done) {
223 return;
224 }
225 // Now that we know the size of the coming buffer, we can allocate our
226 // buffer and start putting data into it.
227 boost::asio::async_read(
228 _socket,
229 message->buffer(),
230 boost::asio::bind_executor(_strand, handle_read_data));
231 } else if (!_done) {
232 log_debug("streaming client: failed to read header:", ec.message());
233 DEBUG_ONLY(log_debug("size = ", message->size()));
234 DEBUG_ONLY(log_debug("bytes = ", bytes));
235 Connect();
236 }
237 };
238
239 // Read the size of the buffer that is coming.
240 boost::asio::async_read(
241 _socket,
242 message->size_as_buffer(),
243 boost::asio::bind_executor(_strand, handle_read_header));
244 }
245
246} // namespace tcp
247} // namespace detail
248} // namespace streaming
249} // 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
#define LIBCARLA_INITIALIZE_LIFETIME_PROFILER(display_name)
A pool of Buffer.
Definition BufferPool.h:29
A piece of raw data.
void reset(size_type size)
Reset the size of this buffer.
boost::asio::const_buffer buffer() const noexcept
Make a boost::asio::buffer from this buffer.
std::function< void(Buffer)> callback_function_type
Serializes a stream endpoint.
boost::asio::ip::tcp::endpoint to_tcp_endpoint() const
static time_duration seconds(size_t timeout)
Definition Time.h:23
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
void throw_exception(const ExceptionT &e)
Templated on the static type so the thrown object keeps its dynamic type: taking a const std::excepti...
Definition Exception.h:44
static void log_debug(Args &&... args)
Definition Logging.h:68