18#include <boost/asio/io_context.hpp>
19#include <boost/asio/executor_work_guard.hpp>
20#include <boost/asio/ip/tcp.hpp>
21#include <boost/asio/read.hpp>
22#include <boost/asio/write.hpp>
44 class MultiGpuRouterTest :
public ::testing::Test {
46 boost::asio::io_context _io_context;
47 boost::asio::executor_work_guard<boost::asio::io_context::executor_type> _work_guard{
48 boost::asio::make_work_guard(_io_context)};
49 std::thread _io_thread{[
this]() { _io_context.run(); }};
50 boost::asio::ip::tcp::endpoint _dummy_endpoint{
51 boost::asio::ip::make_address(
"127.0.0.1"), 0};
54 std::shared_ptr<carla::multigpu::Primary> MakeFakeSession() {
55 return std::make_shared<carla::multigpu::Primary>(
59 void TearDown()
override {
66 bool IsRejected(std::future<carla::multigpu::SessionInfo> &future) {
67 return future.get().session ==
nullptr;
70 bool WaitUntilPending(
72 const std::shared_ptr<carla::multigpu::Primary> &session) {
73 const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(2);
74 while (std::chrono::steady_clock::now() < deadline) {
75 if (router.TestHasPendingRequest(session)) {
78 std::this_thread::sleep_for(std::chrono::milliseconds(1));
84 return carla::Buffer(
reinterpret_cast<const unsigned char *
>(text.data()), text.size());
88 return std::string(
reinterpret_cast<const char *
>(buffer.
data()), buffer.
size());
97 data.
address.v4 = boost::asio::ip::make_address_v4(
"127.0.0.1").to_bytes();
98 return carla::Buffer(
reinterpret_cast<const unsigned char *
>(&data),
sizeof(data));
102 return carla::Buffer(
reinterpret_cast<const unsigned char *
>(&value),
sizeof(value));
110 const std::shared_ptr<carla::multigpu::Primary> &session,
114 auto routed = std::async(std::launch::async, [&commander, sensor_id, actor]() {
115 return commander.
GetToken(sensor_id, actor);
117 if (!WaitUntilPending(router, session)) {
120 router.TestHandleResponse(session, MakeTokenReply(sensor_id, port));
121 return (routed.wait_for(std::chrono::seconds(2)) == std::future_status::ready) && routed.get().has_value();
125 std::optional<uint16_t> CachedPort(
129 auto cached = std::async(std::launch::async, [&commander, sensor_id]() {
130 return commander.
GetToken(sensor_id, 0u);
132 if (cached.wait_for(std::chrono::milliseconds(200)) != std::future_status::ready) {
134 static_cast<void>(cached.get());
137 const auto token = cached.get();
138 return token ? std::optional<uint16_t>(token->get_port()) :
std::nullopt;
144 std::vector<unsigned char> payload;
151 router.TestSetRequestObserver([state = _state](
155 std::scoped_lock<std::mutex> lock(state->mutex);
156 state->sent.push_back(SentRequest{
157 session, command, std::vector<unsigned char>(payload.
data(), payload.
data() + payload.
size())});
161 std::vector<SentRequest> Take() {
162 std::scoped_lock<std::mutex> lock(_state->mutex);
163 return std::exchange(_state->sent, {});
169 std::vector<SentRequest> sent;
171 std::shared_ptr<State> _state = std::make_shared<State>();
174 ::testing::AssertionResult IsGetToken(
175 const SentRequest &sent,
176 const std::shared_ptr<carla::multigpu::Primary> &session,
179 if (sent.session != session.get()) {
180 return ::testing::AssertionFailure() <<
"sent to another session";
183 return ::testing::AssertionFailure() <<
"command " <<
static_cast<uint32_t
>(sent.command);
186 if (sent.payload.size() !=
sizeof(request)) {
187 return ::testing::AssertionFailure() <<
"payload of " << sent.payload.size() <<
" bytes";
189 std::memcpy(&request, sent.payload.data(),
sizeof(request));
190 if ((request.stream_id != sensor_id) || (request.actor_id != actor)) {
191 return ::testing::AssertionFailure() <<
"stream " << request.stream_id <<
", actor " << request.actor_id;
193 return ::testing::AssertionSuccess();
196 ::testing::AssertionResult IsEnableRos(
197 const SentRequest &sent,
198 const std::shared_ptr<carla::multigpu::Primary> &session,
200 if (sent.session != session.get()) {
201 return ::testing::AssertionFailure() <<
"sent to another session";
204 return ::testing::AssertionFailure() <<
"command " <<
static_cast<uint32_t
>(sent.command);
207 if (sent.payload.size() !=
sizeof(payload_id)) {
208 return ::testing::AssertionFailure() <<
"payload of " << sent.payload.size() <<
" bytes";
210 std::memcpy(&payload_id, sent.payload.data(),
sizeof(payload_id));
211 if (payload_id != sensor_id) {
212 return ::testing::AssertionFailure() <<
"stream " << payload_id;
214 return ::testing::AssertionSuccess();
220 const std::shared_ptr<carla::multigpu::Primary> &session,
222 if (!WaitUntilPending(router, session)) {
225 router.TestHandleResponse(session, std::move(reply));
233 const std::shared_ptr<carla::multigpu::Primary> &session,
235 auto routed = std::async(std::launch::async, [&commander]() {
return commander.
GetToken(42u, 9u); });
236 if (!WaitUntilPending(router, session)) {
239 router.TestHandleResponse(session, MakeTokenReply(42u, port));
240 return (routed.wait_for(std::chrono::seconds(2)) == std::future_status::ready) && routed.get().has_value();
245TEST_F(MultiGpuRouterTest, write_to_next_with_no_sessions_rejects_instead_of_hanging) {
251 ASSERT_EQ(future.wait_for(std::chrono::seconds(2)), std::future_status::ready);
256 EXPECT_TRUE(IsRejected(future));
259TEST_F(MultiGpuRouterTest, write_to_one_with_expired_session_rejects_instead_of_hanging) {
263 std::weak_ptr<carla::multigpu::Primary> expired_session;
265 auto session = MakeFakeSession();
266 expired_session = session;
268 ASSERT_TRUE(expired_session.expired());
273 ASSERT_EQ(future.wait_for(std::chrono::seconds(2)), std::future_status::ready);
274 EXPECT_TRUE(IsRejected(future));
277TEST_F(MultiGpuRouterTest, write_to_one_with_disconnected_but_still_alive_session_rejects) {
280 auto session = MakeFakeSession();
281 router.TestConnectSession(session);
282 router.TestDisconnectSession(session);
292 ASSERT_EQ(future.wait_for(std::chrono::seconds(2)), std::future_status::ready);
293 EXPECT_TRUE(IsRejected(future));
296TEST_F(MultiGpuRouterTest, write_to_next_with_connected_session_stays_pending) {
299 auto session = MakeFakeSession();
300 router.TestConnectSession(session);
304 EXPECT_TRUE(router.TestHasPendingRequest(session));
310 EXPECT_EQ(future.wait_for(std::chrono::milliseconds(50)), std::future_status::timeout);
313TEST_F(MultiGpuRouterTest, disconnect_rejects_pending_promise_for_that_session) {
316 auto session = MakeFakeSession();
317 router.TestConnectSession(session);
320 ASSERT_EQ(future.wait_for(std::chrono::milliseconds(50)), std::future_status::timeout);
322 router.TestDisconnectSession(session);
324 ASSERT_EQ(future.wait_for(std::chrono::seconds(2)), std::future_status::ready);
325 EXPECT_TRUE(IsRejected(future));
326 EXPECT_FALSE(router.TestHasPendingRequest(session));
329TEST_F(MultiGpuRouterTest, response_after_rejection_is_dropped) {
332 auto session = MakeFakeSession();
333 router.TestConnectSession(session);
336 router.TestDisconnectSession(session);
337 ASSERT_EQ(future.wait_for(std::chrono::seconds(2)), std::future_status::ready);
341 EXPECT_NO_THROW(router.TestHandleResponse(session, MakeBuffer(
"late reply")));
342 EXPECT_TRUE(IsRejected(future));
343 EXPECT_FALSE(router.TestHasPendingRequest(session));
346TEST_F(MultiGpuRouterTest, duplicate_response_does_not_resolve_a_promise_twice) {
349 auto session = MakeFakeSession();
350 router.TestConnectSession(session);
353 router.TestHandleResponse(session, MakeBuffer(
"first"));
354 EXPECT_FALSE(router.TestHasPendingRequest(session));
358 EXPECT_NO_THROW(router.TestHandleResponse(session, MakeBuffer(
"second")));
359 EXPECT_NO_THROW(router.TestDisconnectSession(session));
361 ASSERT_EQ(future.wait_for(std::chrono::seconds(2)), std::future_status::ready);
362 auto result = future.get();
363 EXPECT_EQ(result.session, session);
364 EXPECT_EQ(ToString(result.buffer),
"first");
367TEST_F(MultiGpuRouterTest, stop_rejects_every_pending_request) {
370 auto session_a = MakeFakeSession();
371 auto session_b = MakeFakeSession();
372 router.TestConnectSession(session_a);
373 router.TestConnectSession(session_b);
377 ASSERT_TRUE(router.TestHasPendingRequest(session_a));
378 ASSERT_TRUE(router.TestHasPendingRequest(session_b));
382 ASSERT_EQ(future_a.wait_for(std::chrono::seconds(2)), std::future_status::ready);
383 ASSERT_EQ(future_b.wait_for(std::chrono::seconds(2)), std::future_status::ready);
384 EXPECT_TRUE(IsRejected(future_a));
385 EXPECT_TRUE(IsRejected(future_b));
388 EXPECT_NO_THROW(router.
Stop());
391TEST_F(MultiGpuRouterTest, episode_ready_marker_with_no_pending_promise_triggers_resync_callback) {
395 int callback_count = 0;
398 auto session = MakeFakeSession();
399 router.TestConnectSession(session);
405 router.TestHandleResponse(session, std::move(marker));
407 EXPECT_EQ(callback_count, 1);
410TEST_F(MultiGpuRouterTest, session_connecting_after_load_map_loads_the_remembered_map_before_resync) {
412 RequestLog log(router);
413 int callback_count = 0;
417 auto session = MakeFakeSession();
418 router.TestConnectSession(session);
420 const auto sent = log.Take();
421 ASSERT_EQ(sent.size(), 1u);
422 EXPECT_EQ(sent[0].session, session.get());
425 std::string_view(
reinterpret_cast<const char *
>(sent[0].payload.data()), sent[0].payload.size()));
426 EXPECT_EQ(request.map,
"Town03");
427 EXPECT_GT(request.load_id, first_load);
429 EXPECT_EQ(callback_count, 0);
432 router.TestHandleResponse(session, MakeBuffer(ready));
434 EXPECT_EQ(callback_count, 1);
437TEST_F(MultiGpuRouterTest, session_connecting_without_a_remembered_map_gets_no_load_map) {
439 RequestLog log(router);
440 int callback_count = 0;
443 router.TestConnectSession(MakeFakeSession());
445 EXPECT_TRUE(log.Take().empty());
447 EXPECT_EQ(callback_count, 1);
450TEST_F(MultiGpuRouterTest, session_connected_before_load_map_keeps_the_broadcast_behaviour) {
452 RequestLog log(router);
453 int callback_count = 0;
456 auto session = MakeFakeSession();
457 router.TestConnectSession(session);
458 EXPECT_EQ(callback_count, 1);
461 const auto sent = log.Take();
462 ASSERT_EQ(sent.size(), 1u);
463 EXPECT_EQ(sent[0].session, session.get());
466 EXPECT_EQ(callback_count, 1);
470 EXPECT_EQ(callback_count, 2);
473TEST_F(MultiGpuRouterTest, stale_ready_of_an_older_load_does_not_finish_a_late_secondary_load) {
477 auto session = MakeFakeSession();
478 router.TestConnectSession(session);
486TEST_F(MultiGpuRouterTest, unsolicited_non_marker_data_does_not_trigger_resync_callback) {
490 int callback_count = 0;
493 auto session = MakeFakeSession();
494 router.TestConnectSession(session);
501 std::string garbage(
"not the marker");
503 reinterpret_cast<const unsigned char *
>(garbage.data()), garbage.size());
504 router.TestHandleResponse(session, std::move(buffer));
506 EXPECT_EQ(callback_count, 0);
509TEST_F(MultiGpuRouterTest, episode_ready_marker_is_not_misdelivered_to_a_pending_promise) {
513 int callback_count = 0;
516 auto session = MakeFakeSession();
517 router.TestConnectSession(session);
526 ASSERT_EQ(future.wait_for(std::chrono::milliseconds(50)), std::future_status::timeout);
531 router.TestHandleResponse(session, std::move(marker));
533 EXPECT_EQ(callback_count, 1);
534 EXPECT_EQ(future.wait_for(std::chrono::milliseconds(50)), std::future_status::timeout);
538 router.TestHandleResponse(session, MakeBuffer(
"token reply"));
539 ASSERT_EQ(future.wait_for(std::chrono::seconds(2)), std::future_status::ready);
540 auto result = future.get();
541 EXPECT_EQ(result.session, session);
542 EXPECT_EQ(ToString(result.buffer),
"token reply");
545TEST_F(MultiGpuRouterTest, response_with_pending_promise_does_not_trigger_resync_callback) {
549 int callback_count = 0;
552 auto session = MakeFakeSession();
553 router.TestConnectSession(session);
557 ASSERT_EQ(future.wait_for(std::chrono::milliseconds(50)), std::future_status::timeout);
561 router.TestHandleResponse(session, MakeBuffer(
"token reply"));
563 ASSERT_EQ(future.wait_for(std::chrono::seconds(2)), std::future_status::ready);
564 auto result = future.get();
565 EXPECT_EQ(result.session, session);
566 EXPECT_EQ(ToString(result.buffer),
"token reply");
567 EXPECT_EQ(callback_count, 0);
570TEST_F(MultiGpuRouterTest, overlapping_request_to_same_session_is_rejected_not_orphaned) {
573 auto session = MakeFakeSession();
574 router.TestConnectSession(session);
584 ASSERT_EQ(second.wait_for(std::chrono::seconds(2)), std::future_status::ready);
585 EXPECT_TRUE(IsRejected(second));
589 EXPECT_EQ(
first.wait_for(std::chrono::milliseconds(50)), std::future_status::timeout);
591 router.TestHandleResponse(session, MakeBuffer(
"first reply"));
592 ASSERT_EQ(
first.wait_for(std::chrono::seconds(2)), std::future_status::ready);
593 auto result =
first.get();
594 EXPECT_EQ(result.session, session);
595 EXPECT_EQ(ToString(result.buffer),
"first reply");
598TEST_F(MultiGpuRouterTest, write_to_next_round_robins_across_connected_sessions) {
601 auto session_a = MakeFakeSession();
602 auto session_b = MakeFakeSession();
603 auto session_c = MakeFakeSession();
604 router.TestConnectSession(session_a);
605 router.TestConnectSession(session_b);
606 router.TestConnectSession(session_c);
621 ASSERT_EQ(future_a.wait_for(std::chrono::milliseconds(50)), std::future_status::timeout);
622 ASSERT_EQ(future_b.wait_for(std::chrono::milliseconds(50)), std::future_status::timeout);
623 ASSERT_EQ(future_c.wait_for(std::chrono::milliseconds(50)), std::future_status::timeout);
625 router.TestDisconnectSession(session_b);
626 ASSERT_EQ(future_b.wait_for(std::chrono::seconds(2)), std::future_status::ready);
627 EXPECT_TRUE(IsRejected(future_b));
628 EXPECT_EQ(future_a.wait_for(std::chrono::milliseconds(20)), std::future_status::timeout);
629 EXPECT_EQ(future_c.wait_for(std::chrono::milliseconds(20)), std::future_status::timeout);
631 router.TestDisconnectSession(session_a);
632 ASSERT_EQ(future_a.wait_for(std::chrono::seconds(2)), std::future_status::ready);
633 EXPECT_TRUE(IsRejected(future_a));
634 EXPECT_EQ(future_c.wait_for(std::chrono::milliseconds(20)), std::future_status::timeout);
636 router.TestDisconnectSession(session_c);
637 ASSERT_EQ(future_c.wait_for(std::chrono::seconds(2)), std::future_status::ready);
638 EXPECT_TRUE(IsRejected(future_c));
641TEST_F(MultiGpuRouterTest, get_token_with_no_secondary_returns_nullopt_and_caches_nothing) {
642 auto router = std::make_shared<carla::multigpu::Router>(
TESTING_PORT);
646 std::optional<carla::multigpu::token_type> token;
647 ASSERT_NO_THROW(token = commander.
GetToken(42u, 42u));
648 EXPECT_FALSE(token.has_value());
651 auto session = MakeFakeSession();
652 router->TestConnectSession(session);
653 auto retried = std::async(std::launch::async, [&commander]() {
return commander.
GetToken(42u, 42u); });
654 ASSERT_TRUE(WaitUntilPending(*router, session));
655 router->TestHandleResponse(session, MakeTokenReply(42u, 2001u));
656 ASSERT_EQ(retried.wait_for(std::chrono::seconds(2)), std::future_status::ready);
657 const auto routed = retried.get();
658 ASSERT_TRUE(routed.has_value());
659 EXPECT_EQ(routed->get_port(), 2001u);
662TEST_F(MultiGpuRouterTest, get_token_with_malformed_reply_returns_nullopt_and_caches_nothing) {
663 auto router = std::make_shared<carla::multigpu::Router>(
TESTING_PORT);
665 auto session = MakeFakeSession();
666 router->TestConnectSession(session);
669 auto malformed = std::async(std::launch::async, [&commander]() {
return commander.
GetToken(42u, 42u); });
670 ASSERT_TRUE(WaitUntilPending(*router, session));
672 router->TestHandleResponse(session, MakeBuffer(
"short"));
673 ASSERT_EQ(malformed.wait_for(std::chrono::seconds(2)), std::future_status::ready);
674 EXPECT_FALSE(malformed.get().has_value());
676 auto retried = std::async(std::launch::async, [&commander]() {
return commander.
GetToken(42u, 42u); });
677 ASSERT_TRUE(WaitUntilPending(*router, session));
678 router->TestHandleResponse(session, MakeTokenReply(42u, 2003u));
679 ASSERT_EQ(retried.wait_for(std::chrono::seconds(2)), std::future_status::ready);
680 const auto token = retried.get();
681 ASSERT_TRUE(token.has_value());
682 EXPECT_EQ(token->get_port(), 2003u);
685TEST_F(MultiGpuRouterTest, get_token_not_ready_reply_returns_nullopt_and_caches_nothing) {
686 auto router = std::make_shared<carla::multigpu::Router>(
TESTING_PORT);
688 auto session = MakeFakeSession();
689 router->TestConnectSession(session);
692 auto not_ready = std::async(std::launch::async, [&commander]() {
return commander.
GetToken(42u, 9u); });
693 ASSERT_TRUE(WaitUntilPending(*router, session));
695 ASSERT_EQ(not_ready.wait_for(std::chrono::seconds(2)), std::future_status::ready);
696 EXPECT_FALSE(not_ready.get().has_value());
698 auto retried = std::async(std::launch::async, [&commander]() {
return commander.
GetToken(42u, 9u); });
699 ASSERT_TRUE(WaitUntilPending(*router, session));
700 router->TestHandleResponse(session, MakeTokenReply(42u, 2005u));
701 ASSERT_EQ(retried.wait_for(std::chrono::seconds(2)), std::future_status::ready);
702 const auto token = retried.get();
703 ASSERT_TRUE(token.has_value());
704 EXPECT_EQ(token->get_stream_id(), 42u);
707TEST_F(MultiGpuRouterTest, get_token_always_carries_the_primary_stream_id) {
708 auto router = std::make_shared<carla::multigpu::Router>(
TESTING_PORT);
710 auto session = MakeFakeSession();
711 router->TestConnectSession(session);
714 auto pending = std::async(std::launch::async, [&commander]() {
return commander.
GetToken(42u, 9u); });
715 ASSERT_TRUE(WaitUntilPending(*router, session));
717 router->TestHandleResponse(session, MakeTokenReply(5u, 2006u));
718 ASSERT_EQ(pending.wait_for(std::chrono::seconds(2)), std::future_status::ready);
719 const auto token = pending.get();
720 ASSERT_TRUE(token.has_value());
721 EXPECT_EQ(token->get_stream_id(), 42u);
722 EXPECT_EQ(token->get_port(), 2006u);
725TEST_F(MultiGpuRouterTest, cached_token_of_a_disconnected_secondary_is_routed_again) {
726 auto router = std::make_shared<carla::multigpu::Router>(
TESTING_PORT);
728 auto session_a = MakeFakeSession();
729 auto session_b = MakeFakeSession();
730 router->TestConnectSession(session_a);
731 router->TestConnectSession(session_b);
734 auto first = std::async(std::launch::async, [&commander]() {
return commander.
GetToken(42u, 9u); });
735 ASSERT_TRUE(WaitUntilPending(*router, session_a));
736 router->TestHandleResponse(session_a, MakeTokenReply(42u, 2007u));
737 ASSERT_EQ(
first.wait_for(std::chrono::seconds(2)), std::future_status::ready);
738 ASSERT_TRUE(
first.get().has_value());
740 router->TestDisconnectSession(session_a);
741 auto rerouted = std::async(std::launch::async, [&commander]() {
return commander.
GetToken(42u, 9u); });
742 ASSERT_TRUE(WaitUntilPending(*router, session_b));
743 router->TestHandleResponse(session_b, MakeTokenReply(42u, 2008u));
744 ASSERT_EQ(rerouted.wait_for(std::chrono::seconds(2)), std::future_status::ready);
745 const auto token = rerouted.get();
746 ASSERT_TRUE(token.has_value());
747 EXPECT_EQ(token->get_port(), 2008u);
750TEST(MultiGpuCommands, token_not_ready_marker_is_distinct_from_other_replies) {
755TEST(MultiGpuCommands, get_token_request_starts_with_the_stream_id) {
758 std::memcpy(&leading_stream_id, &request,
sizeof(leading_stream_id));
759 EXPECT_EQ(leading_stream_id, 42u);
762TEST_F(MultiGpuRouterTest, concurrent_get_token_calls_are_serialized_not_rejected) {
763 auto router = std::make_shared<carla::multigpu::Router>(
TESTING_PORT);
765 auto session = MakeFakeSession();
766 router->TestConnectSession(session);
772 auto first = std::async(std::launch::async, [&commander]() {
return commander.
GetToken(1u, 1u); });
773 auto second = std::async(std::launch::async, [&commander]() {
return commander.
GetToken(2u, 2u); });
775 ASSERT_TRUE(WaitUntilPending(*router, session));
778 std::this_thread::sleep_for(std::chrono::milliseconds(50));
779 router->TestHandleResponse(session, MakeTokenReply(100u, 3001u));
781 ASSERT_TRUE(WaitUntilPending(*router, session));
782 router->TestHandleResponse(session, MakeTokenReply(101u, 3002u));
784 ASSERT_EQ(
first.wait_for(std::chrono::seconds(2)), std::future_status::ready);
785 ASSERT_EQ(second.wait_for(std::chrono::seconds(2)), std::future_status::ready);
786 const auto first_token =
first.get();
787 const auto second_token = second.get();
788 ASSERT_TRUE(first_token.has_value());
789 ASSERT_TRUE(second_token.has_value());
790 const std::set<uint16_t> ports{first_token->get_port(), second_token->get_port()};
791 EXPECT_EQ(ports, (std::set<uint16_t>{3001u, 3002u}));
794TEST_F(MultiGpuRouterTest, get_token_fails_cleanly_when_secondary_disconnects_mid_request) {
795 auto router = std::make_shared<carla::multigpu::Router>(
TESTING_PORT);
797 auto session = MakeFakeSession();
798 router->TestConnectSession(session);
801 auto pending = std::async(std::launch::async, [&commander]() {
return commander.
GetToken(42u, 42u); });
802 ASSERT_TRUE(WaitUntilPending(*router, session));
807 router->TestDisconnectSession(session);
809 ASSERT_EQ(pending.wait_for(std::chrono::seconds(2)), std::future_status::ready);
810 std::optional<carla::multigpu::token_type> token;
811 ASSERT_NO_THROW(token = pending.get());
812 EXPECT_FALSE(token.has_value());
816 auto replacement = MakeFakeSession();
817 router->TestConnectSession(replacement);
818 auto next = std::async(std::launch::async, [&commander]() {
return commander.
GetToken(43u, 43u); });
819 ASSERT_TRUE(WaitUntilPending(*router, replacement));
820 router->TestHandleResponse(replacement, MakeTokenReply(43u, 2004u));
821 ASSERT_EQ(next.wait_for(std::chrono::seconds(2)), std::future_status::ready);
822 const auto next_token = next.get();
823 ASSERT_TRUE(next_token.has_value());
824 EXPECT_EQ(next_token->get_stream_id(), 43u);
827TEST_F(MultiGpuRouterTest, get_token_after_failed_request_routes_to_remaining_secondary) {
828 auto router = std::make_shared<carla::multigpu::Router>(
TESTING_PORT);
830 auto session_a = MakeFakeSession();
831 auto session_b = MakeFakeSession();
832 router->TestConnectSession(session_a);
833 router->TestConnectSession(session_b);
836 auto failed = std::async(std::launch::async, [&commander]() {
return commander.
GetToken(42u, 42u); });
837 ASSERT_TRUE(WaitUntilPending(*router, session_a));
838 router->TestDisconnectSession(session_a);
839 ASSERT_EQ(failed.wait_for(std::chrono::seconds(2)), std::future_status::ready);
840 ASSERT_FALSE(failed.get().has_value());
844 auto retried = std::async(std::launch::async, [&commander]() {
return commander.
GetToken(42u, 42u); });
845 ASSERT_TRUE(WaitUntilPending(*router, session_b));
846 router->TestHandleResponse(session_b, MakeTokenReply(42u, 2002u));
847 ASSERT_EQ(retried.wait_for(std::chrono::seconds(2)), std::future_status::ready);
848 const auto token = retried.get();
849 ASSERT_TRUE(token.has_value());
850 EXPECT_EQ(token->get_stream_id(), 42u);
851 EXPECT_EQ(token->get_port(), 2002u);
855 const auto cached = commander.
GetToken(42u, 42u);
856 ASSERT_TRUE(cached.has_value());
857 EXPECT_EQ(cached->get_port(), 2002u);
858 EXPECT_FALSE(router->TestHasPendingRequest(session_b));
861TEST_F(MultiGpuRouterTest, enable_for_ros_without_any_secondary_returns_instead_of_recursing) {
862 auto router = std::make_shared<carla::multigpu::Router>(
TESTING_PORT);
869 auto done = std::async(std::launch::async, [&commander]() {
return commander.
EnableForROS(7u, 7u); });
870 ASSERT_EQ(done.wait_for(std::chrono::seconds(2)), std::future_status::ready);
872 ASSERT_NO_THROW(enabled = done.get());
873 EXPECT_FALSE(enabled);
876TEST_F(MultiGpuRouterTest, enable_for_ros_refused_by_the_secondary_returns_false) {
877 auto router = std::make_shared<carla::multigpu::Router>(
TESTING_PORT);
879 auto session = MakeFakeSession();
880 router->TestConnectSession(session);
882 ASSERT_TRUE(RouteSensor(*router, commander, session, 2010u));
884 auto refused = std::async(std::launch::async, [&commander]() {
return commander.
EnableForROS(42u, 9u); });
885 ASSERT_TRUE(WaitUntilPending(*router, session));
886 router->TestHandleResponse(session, MakeBoolReply(
false));
887 ASSERT_EQ(refused.wait_for(std::chrono::seconds(2)), std::future_status::ready);
888 EXPECT_FALSE(refused.get());
890 auto accepted = std::async(std::launch::async, [&commander]() {
return commander.
EnableForROS(42u, 9u); });
891 ASSERT_TRUE(WaitUntilPending(*router, session));
892 router->TestHandleResponse(session, MakeBoolReply(
true));
893 ASSERT_EQ(accepted.wait_for(std::chrono::seconds(2)), std::future_status::ready);
894 EXPECT_TRUE(accepted.get());
897TEST_F(MultiGpuRouterTest, enable_for_ros_on_a_disconnected_secondary_routes_the_sensor_again) {
898 auto router = std::make_shared<carla::multigpu::Router>(
TESTING_PORT);
900 auto session_a = MakeFakeSession();
901 auto session_b = MakeFakeSession();
902 router->TestConnectSession(session_a);
903 router->TestConnectSession(session_b);
905 ASSERT_TRUE(RouteSensor(*router, commander, session_a, 2011u));
906 router->TestDisconnectSession(session_a);
908 auto enabled = std::async(std::launch::async, [&commander]() {
return commander.
EnableForROS(42u, 9u); });
909 ASSERT_TRUE(WaitUntilPending(*router, session_b));
910 router->TestHandleResponse(session_b, MakeTokenReply(42u, 2012u));
911 ASSERT_TRUE(WaitUntilPending(*router, session_b));
912 router->TestHandleResponse(session_b, MakeBoolReply(
true));
913 ASSERT_EQ(enabled.wait_for(std::chrono::seconds(2)), std::future_status::ready);
914 EXPECT_TRUE(enabled.get());
916 const auto token = commander.
GetToken(42u, 9u);
917 ASSERT_TRUE(token.has_value());
918 EXPECT_EQ(token->get_port(), 2012u);
921TEST_F(MultiGpuRouterTest, ros_queries_on_a_disconnected_secondary_return_without_a_round_trip) {
922 auto router = std::make_shared<carla::multigpu::Router>(
TESTING_PORT);
924 auto session_a = MakeFakeSession();
925 auto session_b = MakeFakeSession();
926 router->TestConnectSession(session_a);
927 router->TestConnectSession(session_b);
929 ASSERT_TRUE(RouteSensor(*router, commander, session_a, 2013u));
930 router->TestDisconnectSession(session_a);
934 EXPECT_FALSE(router->TestHasPendingRequest(session_b));
937 auto rerouted = std::async(std::launch::async, [&commander]() {
return commander.
GetToken(42u, 9u); });
938 ASSERT_TRUE(WaitUntilPending(*router, session_b));
939 router->TestHandleResponse(session_b, MakeTokenReply(42u, 2014u));
940 ASSERT_EQ(rerouted.wait_for(std::chrono::seconds(2)), std::future_status::ready);
941 const auto token = rerouted.get();
942 ASSERT_TRUE(token.has_value());
943 EXPECT_EQ(token->get_port(), 2014u);
946TEST_F(MultiGpuRouterTest, load_map_forgets_cached_tokens) {
947 auto router = std::make_shared<carla::multigpu::Router>(
TESTING_PORT);
949 auto session = MakeFakeSession();
950 router->TestConnectSession(session);
952 ASSERT_TRUE(RouteSensor(*router, commander, session, 2015u));
954 commander.
SendLoadMap(
"/Game/Carla/Maps/Town10HD_Opt");
955 auto routed = std::async(std::launch::async, [&commander]() {
return commander.
GetToken(42u, 9u); });
956 ASSERT_TRUE(WaitUntilPending(*router, session));
957 router->TestHandleResponse(session, MakeTokenReply(42u, 2016u));
958 ASSERT_EQ(routed.wait_for(std::chrono::seconds(2)), std::future_status::ready);
959 const auto token = routed.get();
960 ASSERT_TRUE(token.has_value());
961 EXPECT_EQ(token->get_port(), 2016u);
964TEST_F(MultiGpuRouterTest, new_secondary_takes_over_the_sensors_of_a_lost_one) {
965 auto router = std::make_shared<carla::multigpu::Router>(
TESTING_PORT);
967 RequestLog log(*router);
968 auto lost = MakeFakeSession();
969 router->TestConnectSession(lost);
971 ASSERT_TRUE(RouteSensorTo(*router, commander, lost, 42u, 9u, 2020u));
972 ASSERT_TRUE(RouteSensorTo(*router, commander, lost, 43u, 10u, 2020u));
973 router->TestDisconnectSession(lost);
974 auto replacement = MakeFakeSession();
975 router->TestConnectSession(replacement);
976 static_cast<void>(log.Take());
978 auto rerouting = std::async(std::launch::async, [&commander]() {
return commander.
RerouteLostSensors(); });
980 ASSERT_TRUE(Answer(*router, replacement, MakeTokenReply(7u, 2020u)));
981 ASSERT_TRUE(Answer(*router, replacement, MakeTokenReply(8u, 2021u)));
982 ASSERT_EQ(rerouting.wait_for(std::chrono::seconds(2)), std::future_status::ready);
983 EXPECT_EQ(rerouting.get(), 2u);
984 const auto sent = log.Take();
985 ASSERT_EQ(sent.size(), 2u);
986 EXPECT_TRUE(IsGetToken(sent[0], replacement, 42u, 9u));
987 EXPECT_TRUE(IsGetToken(sent[1], replacement, 43u, 10u));
989 EXPECT_EQ(CachedPort(*router, commander, 42u), std::optional<uint16_t>(2020u));
990 EXPECT_EQ(CachedPort(*router, commander, 43u), std::optional<uint16_t>(2021u));
992 auto enabled = std::async(std::launch::async, [&commander]() {
return commander.
IsEnabledForROS(42u); });
993 ASSERT_TRUE(Answer(*router, replacement, MakeBoolReply(
true)));
994 ASSERT_EQ(enabled.wait_for(std::chrono::seconds(2)), std::future_status::ready);
995 EXPECT_TRUE(enabled.get());
1000 EXPECT_FALSE(router->TestHasPendingRequest(replacement));
1003TEST_F(MultiGpuRouterTest, reroute_without_lost_sensors_is_a_no_op) {
1004 auto router = std::make_shared<carla::multigpu::Router>(
TESTING_PORT);
1006 auto session = MakeFakeSession();
1007 router->TestConnectSession(session);
1009 ASSERT_TRUE(RouteSensorTo(*router, commander, session, 42u, 9u, 2022u));
1010 auto added = MakeFakeSession();
1011 router->TestConnectSession(added);
1014 EXPECT_FALSE(router->TestHasPendingRequest(session));
1015 EXPECT_FALSE(router->TestHasPendingRequest(added));
1016 EXPECT_EQ(CachedPort(*router, commander, 42u), std::optional<uint16_t>(2022u));
1019TEST_F(MultiGpuRouterTest, reroute_after_load_map_is_a_no_op) {
1020 auto router = std::make_shared<carla::multigpu::Router>(
TESTING_PORT);
1022 auto lost = MakeFakeSession();
1023 router->TestConnectSession(lost);
1025 ASSERT_TRUE(RouteSensorTo(*router, commander, lost, 42u, 9u, 2023u));
1026 router->TestDisconnectSession(lost);
1027 auto replacement = MakeFakeSession();
1028 router->TestConnectSession(replacement);
1030 commander.
SendLoadMap(
"/Game/Carla/Maps/Town10HD_Opt");
1033 EXPECT_FALSE(router->TestHasPendingRequest(replacement));
1036TEST_F(MultiGpuRouterTest, sensor_refused_by_the_new_secondary_is_routed_lazily_later) {
1037 auto router = std::make_shared<carla::multigpu::Router>(
TESTING_PORT);
1039 auto lost = MakeFakeSession();
1040 router->TestConnectSession(lost);
1042 ASSERT_TRUE(RouteSensorTo(*router, commander, lost, 42u, 9u, 2024u));
1043 ASSERT_TRUE(RouteSensorTo(*router, commander, lost, 43u, 10u, 2024u));
1044 router->TestDisconnectSession(lost);
1045 auto replacement = MakeFakeSession();
1046 router->TestConnectSession(replacement);
1048 auto rerouting = std::async(std::launch::async, [&commander]() {
return commander.
RerouteLostSensors(); });
1051 ASSERT_TRUE(Answer(*router, replacement, MakeTokenReply(43u, 2024u)));
1052 ASSERT_EQ(rerouting.wait_for(std::chrono::seconds(2)), std::future_status::ready);
1053 EXPECT_EQ(rerouting.get(), 1u);
1054 EXPECT_EQ(CachedPort(*router, commander, 43u), std::optional<uint16_t>(2024u));
1056 auto retried = std::async(std::launch::async, [&commander]() {
return commander.
GetToken(42u, 9u); });
1057 ASSERT_TRUE(Answer(*router, replacement, MakeTokenReply(42u, 2025u)));
1058 ASSERT_EQ(retried.wait_for(std::chrono::seconds(2)), std::future_status::ready);
1059 const auto token = retried.get();
1060 ASSERT_TRUE(token.has_value());
1061 EXPECT_EQ(token->get_port(), 2025u);
1064TEST_F(MultiGpuRouterTest, reroute_leaves_sensors_of_live_secondaries_alone) {
1065 auto router = std::make_shared<carla::multigpu::Router>(
TESTING_PORT);
1067 auto lost = MakeFakeSession();
1068 auto alive = MakeFakeSession();
1069 router->TestConnectSession(lost);
1070 router->TestConnectSession(alive);
1072 ASSERT_TRUE(RouteSensorTo(*router, commander, lost, 42u, 9u, 2026u));
1073 ASSERT_TRUE(RouteSensorTo(*router, commander, alive, 43u, 10u, 2027u));
1076 router->TestDisconnectSession(lost);
1077 auto replacement = MakeFakeSession();
1078 router->TestConnectSession(replacement);
1080 auto rerouting = std::async(std::launch::async, [&commander]() {
return commander.
RerouteLostSensors(); });
1081 ASSERT_TRUE(WaitUntilPending(*router, replacement));
1082 EXPECT_FALSE(router->TestHasPendingRequest(alive));
1083 router->TestHandleResponse(replacement, MakeTokenReply(42u, 2026u));
1084 ASSERT_EQ(rerouting.wait_for(std::chrono::seconds(2)), std::future_status::ready);
1085 EXPECT_EQ(rerouting.get(), 1u);
1087 EXPECT_EQ(CachedPort(*router, commander, 42u), std::optional<uint16_t>(2026u));
1088 EXPECT_EQ(CachedPort(*router, commander, 43u), std::optional<uint16_t>(2027u));
1089 auto enabled = std::async(std::launch::async, [&commander]() {
return commander.
IsEnabledForROS(43u); });
1090 ASSERT_TRUE(Answer(*router, alive, MakeBoolReply(
true)));
1091 ASSERT_EQ(enabled.wait_for(std::chrono::seconds(2)), std::future_status::ready);
1092 EXPECT_TRUE(enabled.get());
1095TEST_F(MultiGpuRouterTest, reroute_stops_when_the_new_secondary_disconnects_mid_way) {
1096 auto router = std::make_shared<carla::multigpu::Router>(
TESTING_PORT);
1098 RequestLog log(*router);
1099 auto lost = MakeFakeSession();
1100 router->TestConnectSession(lost);
1102 ASSERT_TRUE(RouteSensorTo(*router, commander, lost, 42u, 9u, 2028u));
1103 ASSERT_TRUE(RouteSensorTo(*router, commander, lost, 43u, 10u, 2028u));
1104 router->TestDisconnectSession(lost);
1105 auto short_lived = MakeFakeSession();
1106 router->TestConnectSession(short_lived);
1107 static_cast<void>(log.Take());
1109 auto rerouting = std::async(std::launch::async, [&commander]() {
return commander.
RerouteLostSensors(); });
1110 ASSERT_TRUE(WaitUntilPending(*router, short_lived));
1111 router->TestDisconnectSession(short_lived);
1112 ASSERT_EQ(rerouting.wait_for(std::chrono::seconds(2)), std::future_status::ready);
1113 EXPECT_EQ(rerouting.get(), 0u);
1114 auto sent = log.Take();
1115 ASSERT_EQ(sent.size(), 1u);
1116 EXPECT_TRUE(IsGetToken(sent[0], short_lived, 42u, 9u));
1120 auto replacement = MakeFakeSession();
1121 router->TestConnectSession(replacement);
1122 auto second_try = std::async(std::launch::async, [&commander]() {
return commander.
RerouteLostSensors(); });
1123 ASSERT_TRUE(Answer(*router, replacement, MakeTokenReply(42u, 2029u)));
1124 ASSERT_TRUE(Answer(*router, replacement, MakeTokenReply(43u, 2029u)));
1125 ASSERT_EQ(second_try.wait_for(std::chrono::seconds(2)), std::future_status::ready);
1126 EXPECT_EQ(second_try.get(), 2u);
1128 ASSERT_EQ(sent.size(), 2u);
1129 EXPECT_TRUE(IsGetToken(sent[0], replacement, 42u, 9u));
1130 EXPECT_TRUE(IsGetToken(sent[1], replacement, 43u, 10u));
1131 EXPECT_EQ(CachedPort(*router, commander, 42u), std::optional<uint16_t>(2029u));
1132 EXPECT_EQ(CachedPort(*router, commander, 43u), std::optional<uint16_t>(2029u));
1135TEST_F(MultiGpuRouterTest, first_new_secondary_takes_every_lost_sensor) {
1136 auto router = std::make_shared<carla::multigpu::Router>(
TESTING_PORT);
1138 RequestLog log(*router);
1139 auto lost = MakeFakeSession();
1140 router->TestConnectSession(lost);
1142 ASSERT_TRUE(RouteSensorTo(*router, commander, lost, 42u, 9u, 2030u));
1143 ASSERT_TRUE(RouteSensorTo(*router, commander, lost, 43u, 10u, 2030u));
1144 router->TestDisconnectSession(lost);
1145 auto first = MakeFakeSession();
1146 auto second = MakeFakeSession();
1147 router->TestConnectSession(
first);
1148 router->TestConnectSession(second);
1149 static_cast<void>(log.Take());
1151 auto rerouting = std::async(std::launch::async, [&commander]() {
return commander.
RerouteLostSensors(); });
1152 ASSERT_TRUE(Answer(*router,
first, MakeTokenReply(42u, 2030u)));
1153 ASSERT_TRUE(Answer(*router,
first, MakeTokenReply(43u, 2030u)));
1154 ASSERT_EQ(rerouting.wait_for(std::chrono::seconds(2)), std::future_status::ready);
1155 EXPECT_EQ(rerouting.get(), 2u);
1156 const auto sent = log.Take();
1157 ASSERT_EQ(sent.size(), 2u);
1158 EXPECT_TRUE(IsGetToken(sent[0],
first, 42u, 9u));
1159 EXPECT_TRUE(IsGetToken(sent[1],
first, 43u, 10u));
1162TEST_F(MultiGpuRouterTest, next_new_secondary_takes_over_when_the_first_drops_mid_way) {
1163 auto router = std::make_shared<carla::multigpu::Router>(
TESTING_PORT);
1165 RequestLog log(*router);
1166 auto lost = MakeFakeSession();
1167 router->TestConnectSession(lost);
1169 ASSERT_TRUE(RouteSensorTo(*router, commander, lost, 42u, 9u, 2031u));
1170 ASSERT_TRUE(RouteSensorTo(*router, commander, lost, 43u, 10u, 2031u));
1171 router->TestDisconnectSession(lost);
1172 auto first = MakeFakeSession();
1173 auto second = MakeFakeSession();
1174 router->TestConnectSession(
first);
1175 router->TestConnectSession(second);
1176 static_cast<void>(log.Take());
1178 auto rerouting = std::async(std::launch::async, [&commander]() {
return commander.
RerouteLostSensors(); });
1179 ASSERT_TRUE(WaitUntilPending(*router,
first));
1180 router->TestDisconnectSession(
first);
1181 ASSERT_TRUE(Answer(*router, second, MakeTokenReply(42u, 2032u)));
1182 ASSERT_TRUE(Answer(*router, second, MakeTokenReply(43u, 2032u)));
1183 ASSERT_EQ(rerouting.wait_for(std::chrono::seconds(2)), std::future_status::ready);
1184 EXPECT_EQ(rerouting.get(), 2u);
1185 const auto sent = log.Take();
1186 ASSERT_EQ(sent.size(), 3u);
1187 EXPECT_TRUE(IsGetToken(sent[0],
first, 42u, 9u));
1188 EXPECT_TRUE(IsGetToken(sent[1], second, 42u, 9u));
1189 EXPECT_TRUE(IsGetToken(sent[2], second, 43u, 10u));
1190 EXPECT_EQ(CachedPort(*router, commander, 42u), std::optional<uint16_t>(2032u));
1193TEST_F(MultiGpuRouterTest, session_that_left_before_the_call_is_skipped) {
1194 auto router = std::make_shared<carla::multigpu::Router>(
TESTING_PORT);
1196 RequestLog log(*router);
1197 auto lost = MakeFakeSession();
1198 router->TestConnectSession(lost);
1200 ASSERT_TRUE(RouteSensorTo(*router, commander, lost, 42u, 9u, 2033u));
1201 router->TestDisconnectSession(lost);
1202 auto gone = MakeFakeSession();
1203 router->TestConnectSession(gone);
1204 router->TestDisconnectSession(gone);
1205 static_cast<void>(log.Take());
1208 EXPECT_TRUE(log.Take().empty());
1210 auto gone_again = MakeFakeSession();
1211 router->TestConnectSession(gone_again);
1212 router->TestDisconnectSession(gone_again);
1213 auto replacement = MakeFakeSession();
1214 router->TestConnectSession(replacement);
1215 auto rerouting = std::async(std::launch::async, [&commander]() {
return commander.
RerouteLostSensors(); });
1216 ASSERT_TRUE(Answer(*router, replacement, MakeTokenReply(42u, 2034u)));
1217 ASSERT_EQ(rerouting.wait_for(std::chrono::seconds(2)), std::future_status::ready);
1218 EXPECT_EQ(rerouting.get(), 1u);
1219 const auto sent = log.Take();
1220 ASSERT_EQ(sent.size(), 1u);
1221 EXPECT_TRUE(IsGetToken(sent[0], replacement, 42u, 9u));
1224TEST_F(MultiGpuRouterTest, lost_route_survives_ros_queries_and_failed_get_token) {
1225 auto router = std::make_shared<carla::multigpu::Router>(
TESTING_PORT);
1227 RequestLog log(*router);
1228 auto lost = MakeFakeSession();
1229 router->TestConnectSession(lost);
1231 ASSERT_TRUE(RouteSensorTo(*router, commander, lost, 42u, 9u, 2035u));
1232 router->TestDisconnectSession(lost);
1233 static_cast<void>(log.Take());
1238 EXPECT_FALSE(commander.
GetToken(42u, 9u).has_value());
1239 EXPECT_TRUE(log.Take().empty());
1241 auto replacement = MakeFakeSession();
1242 router->TestConnectSession(replacement);
1243 auto rerouting = std::async(std::launch::async, [&commander]() {
return commander.
RerouteLostSensors(); });
1244 ASSERT_TRUE(Answer(*router, replacement, MakeTokenReply(42u, 2035u)));
1245 ASSERT_EQ(rerouting.wait_for(std::chrono::seconds(2)), std::future_status::ready);
1246 EXPECT_EQ(rerouting.get(), 1u);
1247 const auto sent = log.Take();
1248 ASSERT_EQ(sent.size(), 1u);
1249 EXPECT_TRUE(IsGetToken(sent[0], replacement, 42u, 9u));
1252TEST_F(MultiGpuRouterTest, get_token_reroutes_a_lost_route_before_reroute_runs) {
1253 auto router = std::make_shared<carla::multigpu::Router>(
TESTING_PORT);
1255 RequestLog log(*router);
1256 auto lost = MakeFakeSession();
1257 router->TestConnectSession(lost);
1259 ASSERT_TRUE(RouteSensorTo(*router, commander, lost, 42u, 9u, 2036u));
1260 router->TestDisconnectSession(lost);
1261 auto replacement = MakeFakeSession();
1262 router->TestConnectSession(replacement);
1263 static_cast<void>(log.Take());
1265 auto lazy = std::async(std::launch::async, [&commander]() {
return commander.
GetToken(42u, 9u); });
1266 ASSERT_TRUE(Answer(*router, replacement, MakeTokenReply(42u, 2037u)));
1267 ASSERT_EQ(lazy.wait_for(std::chrono::seconds(2)), std::future_status::ready);
1268 const auto token = lazy.get();
1269 ASSERT_TRUE(token.has_value());
1270 EXPECT_EQ(token->get_port(), 2037u);
1271 const auto sent = log.Take();
1272 ASSERT_EQ(sent.size(), 1u);
1273 EXPECT_TRUE(IsGetToken(sent[0], replacement, 42u, 9u));
1276 EXPECT_FALSE(router->TestHasPendingRequest(replacement));
1279TEST_F(MultiGpuRouterTest, forgotten_sensor_is_not_rerouted) {
1280 auto router = std::make_shared<carla::multigpu::Router>(
TESTING_PORT);
1282 RequestLog log(*router);
1283 auto lost = MakeFakeSession();
1284 router->TestConnectSession(lost);
1286 ASSERT_TRUE(RouteSensorTo(*router, commander, lost, 42u, 9u, 2038u));
1287 ASSERT_TRUE(RouteSensorTo(*router, commander, lost, 43u, 10u, 2038u));
1289 router->TestDisconnectSession(lost);
1290 auto replacement = MakeFakeSession();
1291 router->TestConnectSession(replacement);
1292 static_cast<void>(log.Take());
1294 auto rerouting = std::async(std::launch::async, [&commander]() {
return commander.
RerouteLostSensors(); });
1295 ASSERT_TRUE(Answer(*router, replacement, MakeTokenReply(43u, 2039u)));
1296 ASSERT_EQ(rerouting.wait_for(std::chrono::seconds(2)), std::future_status::ready);
1297 EXPECT_EQ(rerouting.get(), 1u);
1298 const auto sent = log.Take();
1299 ASSERT_EQ(sent.size(), 1u);
1300 EXPECT_TRUE(IsGetToken(sent[0], replacement, 43u, 10u));
1304 auto routed = std::async(std::launch::async, [&commander]() {
return commander.
GetToken(43u, 10u); });
1305 ASSERT_TRUE(Answer(*router, replacement, MakeTokenReply(43u, 2040u)));
1306 ASSERT_EQ(routed.wait_for(std::chrono::seconds(2)), std::future_status::ready);
1307 const auto token = routed.get();
1308 ASSERT_TRUE(token.has_value());
1309 EXPECT_EQ(token->get_port(), 2040u);
1312TEST_F(MultiGpuRouterTest, reroute_enables_ros_again_on_the_new_secondary) {
1313 auto router = std::make_shared<carla::multigpu::Router>(
TESTING_PORT);
1315 RequestLog log(*router);
1316 auto lost = MakeFakeSession();
1317 router->TestConnectSession(lost);
1319 ASSERT_TRUE(RouteSensorTo(*router, commander, lost, 42u, 9u, 2041u));
1320 ASSERT_TRUE(RouteSensorTo(*router, commander, lost, 43u, 10u, 2041u));
1321 ASSERT_TRUE(RouteSensorTo(*router, commander, lost, 44u, 11u, 2041u));
1322 auto enabled = std::async(std::launch::async, [&commander]() {
return commander.
EnableForROS(42u, 9u); });
1323 ASSERT_TRUE(Answer(*router, lost, MakeBoolReply(
true)));
1324 ASSERT_EQ(enabled.wait_for(std::chrono::seconds(2)), std::future_status::ready);
1325 ASSERT_TRUE(enabled.get());
1326 auto toggled = std::async(std::launch::async, [&commander]() {
return commander.
EnableForROS(44u, 11u); });
1327 ASSERT_TRUE(Answer(*router, lost, MakeBoolReply(
true)));
1328 ASSERT_EQ(toggled.wait_for(std::chrono::seconds(2)), std::future_status::ready);
1329 ASSERT_TRUE(toggled.get());
1330 auto disabled = std::async(std::launch::async, [&commander]() { commander.
DisableForROS(44u); });
1331 ASSERT_TRUE(Answer(*router, lost, MakeBoolReply(
true)));
1332 ASSERT_EQ(disabled.wait_for(std::chrono::seconds(2)), std::future_status::ready);
1333 router->TestDisconnectSession(lost);
1334 auto replacement = MakeFakeSession();
1335 router->TestConnectSession(replacement);
1336 static_cast<void>(log.Take());
1338 auto rerouting = std::async(std::launch::async, [&commander]() {
return commander.
RerouteLostSensors(); });
1339 ASSERT_TRUE(Answer(*router, replacement, MakeTokenReply(42u, 2041u)));
1341 ASSERT_TRUE(Answer(*router, replacement, MakeBoolReply(
false)));
1342 ASSERT_TRUE(Answer(*router, replacement, MakeTokenReply(43u, 2041u)));
1343 ASSERT_TRUE(Answer(*router, replacement, MakeTokenReply(44u, 2041u)));
1344 ASSERT_EQ(rerouting.wait_for(std::chrono::seconds(2)), std::future_status::ready);
1345 EXPECT_EQ(rerouting.get(), 3u);
1346 const auto sent = log.Take();
1347 ASSERT_EQ(sent.size(), 4u);
1348 EXPECT_TRUE(IsGetToken(sent[0], replacement, 42u, 9u));
1349 EXPECT_TRUE(IsEnableRos(sent[1], replacement, 42u));
1350 EXPECT_TRUE(IsGetToken(sent[2], replacement, 43u, 10u));
1351 EXPECT_TRUE(IsGetToken(sent[3], replacement, 44u, 11u));
1354TEST_F(MultiGpuRouterTest, get_token_on_a_lost_route_enables_ros_again) {
1355 auto router = std::make_shared<carla::multigpu::Router>(
TESTING_PORT);
1357 RequestLog log(*router);
1358 auto lost = MakeFakeSession();
1359 router->TestConnectSession(lost);
1361 ASSERT_TRUE(RouteSensorTo(*router, commander, lost, 42u, 9u, 2042u));
1362 auto enabled = std::async(std::launch::async, [&commander]() {
return commander.
EnableForROS(42u, 9u); });
1363 ASSERT_TRUE(Answer(*router, lost, MakeBoolReply(
true)));
1364 ASSERT_EQ(enabled.wait_for(std::chrono::seconds(2)), std::future_status::ready);
1365 ASSERT_TRUE(enabled.get());
1366 router->TestDisconnectSession(lost);
1367 auto replacement = MakeFakeSession();
1368 router->TestConnectSession(replacement);
1369 static_cast<void>(log.Take());
1371 auto lazy = std::async(std::launch::async, [&commander]() {
return commander.
GetToken(42u, 9u); });
1372 ASSERT_TRUE(Answer(*router, replacement, MakeTokenReply(42u, 2043u)));
1373 ASSERT_TRUE(Answer(*router, replacement, MakeBoolReply(
true)));
1374 ASSERT_EQ(lazy.wait_for(std::chrono::seconds(2)), std::future_status::ready);
1375 EXPECT_TRUE(lazy.get().has_value());
1376 const auto sent = log.Take();
1377 ASSERT_EQ(sent.size(), 2u);
1378 EXPECT_TRUE(IsGetToken(sent[0], replacement, 42u, 9u));
1379 EXPECT_TRUE(IsEnableRos(sent[1], replacement, 42u));
1382TEST_F(MultiGpuRouterTest, load_map_waits_until_every_secondary_is_ready_or_gone) {
1385 auto session_a = MakeFakeSession();
1386 auto session_b = MakeFakeSession();
1387 router.TestConnectSession(session_a);
1388 router.TestConnectSession(session_b);
1398 router.TestDisconnectSession(session_b);
1402TEST_F(MultiGpuRouterTest, stale_episode_ready_for_map_a_does_not_clear_map_b_wait) {
1405 auto session = MakeFakeSession();
1406 router.TestConnectSession(session);
1410 ASSERT_NE(map_a_load, map_b_load);
1419TEST_F(MultiGpuRouterTest, untagged_episode_ready_does_not_clear_a_tagged_load_wait) {
1422 auto session = MakeFakeSession();
1423 router.TestConnectSession(session);
1430TEST_F(MultiGpuRouterTest, stale_episode_ready_still_rearms_resync) {
1433 int callback_count = 0;
1435 auto session = MakeFakeSession();
1436 router.TestConnectSession(session);
1443 EXPECT_EQ(callback_count, 1);
1444 EXPECT_FALSE(router.TestHasPendingRequest(session));
1447TEST(MultiGpuCommands, load_map_payload_round_trips_map_and_load_id) {
1450 EXPECT_EQ(request.map,
"/Game/Carla/Maps/Town15");
1451 EXPECT_EQ(request.load_id, 7u);
1454TEST(MultiGpuCommands, load_map_payload_without_load_id_parses_as_zero) {
1455 const std::string legacy(
"/Game/Carla/Maps/Town15\0", 24u);
1457 EXPECT_EQ(request.map,
"/Game/Carla/Maps/Town15");
1458 EXPECT_EQ(request.load_id, 0u);
1461TEST(MultiGpuCommands, episode_ready_message_round_trips_load_id) {
1464 ASSERT_TRUE(load_id.has_value());
1465 EXPECT_EQ(*load_id, 42u);
1471TEST_F(MultiGpuRouterTest, load_map_wait_can_be_abandoned) {
1474 auto session = MakeFakeSession();
1475 router.TestConnectSession(session);
1483TEST_F(MultiGpuRouterTest, load_map_without_secondaries_waits_for_nothing) {
1490TEST(MultiGpuRouterProductionShutdown, stop_releases_listening_port_despite_reference_cycle) {
1501 uint16_t fixed_test_port;
1503 boost::asio::io_context probe_io_context;
1505 probe_io_context, boost::asio::ip::tcp::endpoint(boost::asio::ip::make_address(
"0.0.0.0"), 0));
1509 auto router = std::make_shared<carla::multigpu::Router>(fixed_test_port);
1517 std::this_thread::sleep_for(std::chrono::milliseconds(100));
1525 boost::asio::io_context probe_io_context;
1528 }) <<
"Router::Stop() did not release its listening port";
1540 using tcp = boost::asio::ip::tcp;
1543 template <
typename F>
1544 bool WaitFor(F condition, std::chrono::milliseconds timeout = std::chrono::milliseconds(5000)) {
1545 const auto deadline = std::chrono::steady_clock::now() + timeout;
1546 while (!condition()) {
1547 if (std::chrono::steady_clock::now() >= deadline) {
1550 std::this_thread::sleep_for(std::chrono::milliseconds(10));
1555 uint16_t FreePort() {
1556 boost::asio::io_context io_context;
1558 return probe.GetLocalEndpoint().port();
1562 struct RunningRouter {
1563 std::shared_ptr<carla::multigpu::Router> router;
1564 tcp::endpoint endpoint;
1567 const uint16_t port = FreePort();
1568 router = std::make_shared<carla::multigpu::Router>(port);
1569 router->SetCallbacks();
1570 router->AsyncRun(1u);
1571 endpoint = tcp::endpoint(boost::asio::ip::make_address(
"127.0.0.1"), port);
1576 router->GetCommander().set_router(
nullptr);
1580 struct ReadCommand {
1582 std::string payload;
1586 ReadCommand ReadOneCommand(tcp::socket &socket) {
1587 uint32_t total = 0u;
1588 boost::asio::read(socket, boost::asio::buffer(&total,
sizeof(total)));
1589 std::string bytes(total,
'\0');
1590 boost::asio::read(socket, boost::asio::buffer(bytes.data(), bytes.size()));
1592 std::memcpy(&header, bytes.data(),
sizeof(header));
1593 ReadCommand command;
1594 command.id =
static_cast<uint32_t
>(header.id);
1595 command.payload = bytes.substr(
sizeof(header), header.size);
1601TEST(MultiGpuTransport, write_queue_preserves_order_under_backpressure) {
1602 RunningRouter running;
1603 boost::asio::io_context io_context;
1604 tcp::socket client(io_context);
1605 client.open(tcp::v4());
1606 client.set_option(boost::asio::socket_base::receive_buffer_size(4096));
1607 client.connect(running.endpoint);
1608 ASSERT_TRUE(WaitFor([&]() {
return running.router->HasClientsConnected(); }));
1610 const std::string
first(1u << 20u,
'a');
1611 const std::string second(1u << 20u,
'b');
1612 const std::string third(
"8 bytes!");
1613 for (
const std::string *payload : {&
first, &second, &third}) {
1615 reinterpret_cast<const unsigned char *
>(payload->data()), payload->size()));
1618 std::this_thread::sleep_for(std::chrono::milliseconds(200));
1620 auto received = std::async(std::launch::async, [&]() {
1621 std::vector<ReadCommand> commands;
1622 for (
int i = 0; i < 3; ++i) {
1623 commands.push_back(ReadOneCommand(client));
1627 if (received.wait_for(std::chrono::seconds(10)) != std::future_status::ready) {
1628 boost::system::error_code ignored;
1629 client.close(ignored);
1630 FAIL() <<
"the three messages did not arrive";
1632 const std::vector<ReadCommand> commands = received.get();
1633 ASSERT_EQ(commands.size(), 3u);
1634 EXPECT_EQ(commands[0].payload,
first);
1635 EXPECT_EQ(commands[1].payload, second);
1636 EXPECT_EQ(commands[2].payload, third);
1637 for (
const ReadCommand &command : commands) {
1642TEST(MultiGpuTransport, oversize_header_closes_the_session) {
1643 RunningRouter running;
1644 boost::asio::io_context io_context;
1645 tcp::socket client(io_context);
1646 client.connect(running.endpoint);
1647 ASSERT_TRUE(WaitFor([&]() {
return running.router->HasClientsConnected(); }));
1650 boost::asio::write(client, boost::asio::buffer(&announced,
sizeof(announced)));
1651 EXPECT_TRUE(WaitFor([&]() {
return !running.router->HasClientsConnected(); }))
1652 <<
"the primary kept a session that announced an impossible message";
1658 struct FakePrimary {
1659 boost::asio::io_context io_context;
1660 tcp::acceptor acceptor{io_context, tcp::endpoint(boost::asio::ip::make_address(
"127.0.0.1"), 0)};
1661 tcp::socket peer{io_context};
1663 uint16_t port()
const {
1664 return acceptor.local_endpoint().port();
1669 auto accepted = std::async(std::launch::async, [
this]() {
1670 boost::system::error_code ec;
1671 acceptor.accept(peer, ec);
1674 if (accepted.wait_for(std::chrono::seconds(5)) != std::future_status::ready) {
1675 boost::system::error_code ignored;
1676 acceptor.close(ignored);
1680 return accepted.get();
1684 std::shared_ptr<carla::multigpu::Secondary> MakeSecondary(uint16_t port) {
1685 return std::make_shared<carla::multigpu::Secondary>(
1691TEST(MultiGpuSecondary, reports_connected_until_the_primary_closes) {
1692 FakePrimary primary;
1693 auto secondary = MakeSecondary(primary.port());
1694 secondary->Connect();
1695 ASSERT_TRUE(primary.Accept());
1696 EXPECT_TRUE(WaitFor([&]() {
return secondary->IsConnected(); }));
1698 primary.peer.close();
1699 EXPECT_TRUE(WaitFor([&]() {
return !secondary->IsConnected(); }))
1700 <<
"the secondary still reports a connection the primary closed";
1704TEST(MultiGpuSecondary, is_not_connected_before_connect_or_after_stop) {
1705 FakePrimary primary;
1706 auto secondary = MakeSecondary(primary.port());
1707 EXPECT_FALSE(secondary->IsConnected());
1708 secondary->Connect();
1709 ASSERT_TRUE(primary.Accept());
1710 ASSERT_TRUE(WaitFor([&]() {
return secondary->IsConnected(); }));
1712 EXPECT_FALSE(secondary->IsConnected());
1715TEST(MultiGpuSecondary, oversize_message_from_the_primary_disconnects) {
1716 FakePrimary primary;
1717 auto secondary = MakeSecondary(primary.port());
1718 secondary->Connect();
1719 ASSERT_TRUE(primary.Accept());
1720 ASSERT_TRUE(WaitFor([&]() {
return secondary->IsConnected(); }));
1723 boost::asio::write(primary.peer, boost::asio::buffer(&announced,
sizeof(announced)));
1724 EXPECT_TRUE(WaitFor([&]() {
return !secondary->IsConnected(); }));
1728TEST(MultiGpuSecondary, replies_reach_the_primary_in_order) {
1729 FakePrimary primary;
1730 auto secondary = MakeSecondary(primary.port());
1731 secondary->Connect();
1732 ASSERT_TRUE(primary.Accept());
1733 ASSERT_TRUE(WaitFor([&]() {
return secondary->IsConnected(); }));
1735 const std::string
first(256u * 1024u,
'x');
1737 secondary->Write(std::string(
"second"));
1739 auto received = std::async(std::launch::async, [&]() {
1740 std::vector<std::string> messages;
1741 for (
int i = 0; i < 2; ++i) {
1743 boost::asio::read(primary.peer, boost::asio::buffer(&size,
sizeof(size)));
1744 std::string bytes(size,
'\0');
1745 boost::asio::read(primary.peer, boost::asio::buffer(bytes.data(), bytes.size()));
1746 messages.push_back(std::move(bytes));
1750 if (received.wait_for(std::chrono::seconds(10)) != std::future_status::ready) {
1751 boost::system::error_code ignored;
1752 primary.peer.close(ignored);
1753 FAIL() <<
"the replies did not arrive";
1755 const std::vector<std::string> messages = received.get();
1756 ASSERT_EQ(messages.size(), 2u);
1757 EXPECT_EQ(messages[0],
first);
1758 EXPECT_EQ(messages[1],
"second");
1762TEST(MultiGpuLoadMap, payload_carries_the_generated_world) {
1764 extras.
opendrive = std::string(
"<OpenDRIVE>\0binary-safe</OpenDRIVE>", 35u);
1768 EXPECT_EQ(request.map,
"/Game/Carla/Maps/OpenDriveMap");
1769 EXPECT_EQ(request.load_id, 7u);
1770 EXPECT_EQ(request.extras, extras);
1773TEST(MultiGpuLoadMap, payload_without_extras_has_none) {
1775 EXPECT_EQ(request.load_id, 2u);
1776 EXPECT_FALSE(request.extras.opendrive.has_value());
1777 EXPECT_FALSE(request.extras.generation_parameters.has_value());
1780TEST(MultiGpuLoadMap, unknown_extras_are_skipped_and_cut_ones_dropped) {
1785 const uint32_t unknown_size = 3u;
1786 payload.push_back(
static_cast<char>(9));
1787 payload.append(
reinterpret_cast<const char *
>(&unknown_size),
sizeof(unknown_size));
1788 payload.append(
"abc");
1791 EXPECT_EQ(request.extras.opendrive, std::optional<std::string>(
"xodr"));
1795 EXPECT_EQ(cut.load_id, 1u);
1796 EXPECT_FALSE(cut.extras.opendrive.has_value());
1799TEST_F(MultiGpuRouterTest, session_connecting_later_gets_the_remembered_generated_world) {
1801 RequestLog log(router);
1803 extras.
opendrive = std::string(
"<OpenDRIVE/>");
1805 router.
WriteLoadMap(
"/Game/Carla/Maps/OpenDriveMap", extras);
1806 auto session = MakeFakeSession();
1807 router.TestConnectSession(session);
1809 const auto sent = log.Take();
1810 ASSERT_EQ(sent.size(), 1u);
1812 std::string_view(
reinterpret_cast<const char *
>(sent[0].payload.data()), sent[0].payload.size()));
1813 EXPECT_EQ(request.map,
"/Game/Carla/Maps/OpenDriveMap");
1814 EXPECT_EQ(request.extras, extras);
1818 auto late = MakeFakeSession();
1820 router.TestConnectSession(late);
1821 const auto again = log.Take();
1822 ASSERT_EQ(again.size(), 1u);
1824 std::string_view(
reinterpret_cast<const char *
>(again[0].payload.data()), again[0].payload.size()));
1825 EXPECT_EQ(plain.map,
"Town01");
1826 EXPECT_FALSE(plain.extras.opendrive.has_value());
Creating a constant view from an existing buffer.
size_type size() const noexcept
const value_type * data() const noexcept
Direct access to the allocated memory or nullptr if no memory is allocated.
const value_type * data() const noexcept
Direct access to the allocated memory or nullptr if no memory is allocated.
size_type size() const noexcept
endpoint GetLocalEndpoint() const
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 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...
void ForgetSensor(stream_id sensor_id)
Drops the routing of a destroyed sensor so it is never routed again.
bool IsEnabledForROS(stream_id sensor_id)
void SendLoadMap(std::string map, const LoadMapExtras &extras={})
boost::asio::ip::tcp::endpoint GetLocalEndpoint() const
void Write(MultiGPUCommand id, Buffer &&buffer)
load_map_id_type WriteLoadMap(std::string_view map, const LoadMapExtras &extras={})
Broadcasts LOAD_MAP tagged with a new load id; each connected secondary counts as loading until it re...
std::future< SessionInfo > WriteToNext(MultiGPUCommand id, Buffer &&buffer)
void StopWaitingForSecondaryLoads()
std::future< SessionInfo > WriteToOne(std::weak_ptr< Primary > server, MultiGPUCommand id, Buffer &&buffer)
bool IsAnySecondaryLoading()
Whether a connected secondary sent LOAD_MAP has not yet reported the episode of its latest LOAD_MAP r...
void AsyncRun(size_t worker_threads)
void SetNewConnectionCallback(std::function< void(void)>)
bool HasClientsConnected() const
Any thread: reads a count kept next to the session list, which is itself only touched under the route...
PrimaryCommands & GetCommander()
static time_duration seconds(size_t timeout)
constexpr std::size_t kMaxMultiGpuMessageBytes
Largest message either end accepts: a frame (capped at 1 GiB by the primary) plus room for its header...
std::string MakeLoadMapPayload(std::string_view map, load_map_id_type load_id, const LoadMapExtras &extras={})
LOAD_MAP payload: the NUL-terminated map path, the load id, then the extras as [u8 kind][u32 size][by...
LoadMapRequest ParseLoadMapPayload(std::string_view payload)
constexpr std::string_view kTokenNotReadyMarker
GET_TOKEN reply when the secondary cannot resolve the sensor to a stream (or the request is malformed...
std::optional< load_map_id_type > ParseEpisodeReadyMessage(std::string_view payload)
Load id of an episode-ready message (0 for a bare marker), or nullopt if payload is not one.
carla::streaming::detail::stream_id_type stream_id
std::string MakeEpisodeReadyMessage(load_map_id_type load_id)
Episode-ready message: the marker followed by the load id of the LOAD_MAP that started the episode,...
constexpr std::string_view kEpisodeReadyMarker
Sent unprompted by a secondary, on the same channel as command responses, to signal that its episode ...
const char * ToString(LaneType type)
GET_TOKEN request payload.
enum carla::streaming::detail::token_data::address address_type
constexpr uint16_t TESTING_PORT
TEST(MultiGpuCommands, token_not_ready_marker_is_distinct_from_other_replies)
TEST_F(MultiGpuRouterTest, write_to_next_with_no_sessions_rejects_instead_of_hanging)