Skip to content

Commit a3be9d3

Browse files
authored
Fixed CAN transport media TX callback lifetime. (#437)
Fix for issue #438: - Fixed callback lifetime bug in CAN transport; added unit test to specifically verify TX media callback lifetime. - Although there is no such issue at UDP transport, still added similar test for udp as well.
1 parent 3d0320d commit a3be9d3

File tree

4 files changed

+54
-3
lines changed

4 files changed

+54
-3
lines changed

include/libcyphal/transport/can/can_transport_impl.hpp

+8
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,14 @@ class TransportImpl final : private TransportDelegate, public ICanTransport
683683
return (*frame_handler_ptr)(deadline, *frame);
684684
});
685685
}
686+
687+
if ((result == 0) && (media.canard_tx_queue().size == 0))
688+
{
689+
// There was nothing successfully polled,
690+
// AND won't be in the (near) future (b/c queue is empty),
691+
// so we are done with this TX media - no more callbacks for now (until brand new TX transfer).
692+
media.tx_callback().reset();
693+
}
686694
}
687695

688696
/// @brief Tries to peek the first TX item from the media TX queue which is not expired.

include/libcyphal/transport/udp/udp_transport_impl.hpp

+3-1
Original file line numberDiff line numberDiff line change
@@ -760,7 +760,9 @@ class TransportImpl final : private TransportDelegate, public IUdpTransport
760760

761761
} // for a valid tx item
762762

763-
// There is nothing to send anymore, so we are done with this media TX socket - no more callbacks for now.
763+
// There was nothing successfully sent (otherwise we would have `return`-ed earlier),
764+
// AND won't be in the (near) future (b/c queue is empty),
765+
// so we are done with this TX media - no more callbacks for now (until brand new TX transfer).
764766
media.txSocketState().callback.reset();
765767
}
766768

test/unittest/transport/can/test_can_transport.cpp

+20-1
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,7 @@ TEST_F(TestCanTransport, sending_multiframe_payload_for_non_anonymous)
529529

530530
constexpr auto timeout = 1s;
531531

532+
// MTU size makes sure that 2 frames are needed - hence "multi-frame".
532533
const auto payload = makeIotaArray<CANARD_MTU_CAN_CLASSIC>(b('0'));
533534
TransferTxMetadata metadata{{0x13, Priority::Nominal}, {}};
534535

@@ -537,6 +538,7 @@ TEST_F(TestCanTransport, sending_multiframe_payload_for_non_anonymous)
537538

538539
scheduler_.scheduleAt(1s, [&](const auto&) {
539540
//
541+
// Expect 1st frame pushing.
540542
EXPECT_CALL(media_mock_, push(_, _, _)).WillOnce([&](auto deadline, auto can_id, auto& pld) {
541543
EXPECT_THAT(now(), metadata.deadline - timeout);
542544
EXPECT_THAT(deadline, metadata.deadline);
@@ -549,15 +551,19 @@ TEST_F(TestCanTransport, sending_multiframe_payload_for_non_anonymous)
549551
});
550552
EXPECT_CALL(media_mock_, registerPushCallback(_)) //
551553
.WillOnce(Invoke([&](auto function) { //
552-
return scheduler_.registerAndScheduleNamedCallback("", now() + 10us, std::move(function));
554+
return scheduler_.registerAndScheduleNamedCallback("tx", now() + 10us, std::move(function));
553555
}));
554556

557+
// There was never any TX yet, so no callback should be registered.
558+
EXPECT_FALSE(scheduler_.hasNamedCallback("tx"));
559+
555560
metadata.deadline = now() + timeout;
556561
auto failure = session->send(metadata, makeSpansFrom(payload));
557562
EXPECT_THAT(failure, Eq(cetl::nullopt));
558563
});
559564
scheduler_.scheduleAt(1s + 10us, [&](const auto&) {
560565
//
566+
// Expect 2nd frame pushing.
561567
EXPECT_CALL(media_mock_, push(_, _, _)).WillOnce([&](auto deadline, auto can_id, auto& pld) {
562568
EXPECT_THAT(now(), metadata.deadline - timeout + 10us);
563569
EXPECT_THAT(deadline, metadata.deadline);
@@ -569,6 +575,19 @@ TEST_F(TestCanTransport, sending_multiframe_payload_for_non_anonymous)
569575
return IMedia::PushResult::Success{true /* is_accepted */};
570576
});
571577
});
578+
scheduler_.scheduleAt(1s + 20us, [&](const auto&) {
579+
//
580+
// Callback is still there b/c the 2nd frame was pushed.
581+
ASSERT_TRUE(scheduler_.hasNamedCallback("tx"));
582+
583+
// Emulate that media done with the 2nd frame - this should remove the callback b/c TX queue is empty.
584+
scheduler_.scheduleNamedCallback("tx", now());
585+
});
586+
scheduler_.scheduleAt(1s + 20us + 1us, [&](const auto&) {
587+
//
588+
// TX pipeline encountered an empty queue - hence the callback should be dropped.
589+
EXPECT_FALSE(scheduler_.hasNamedCallback("tx"));
590+
});
572591
scheduler_.spinFor(10s);
573592
}
574593

test/unittest/transport/udp/test_udp_transport.cpp

+23-1
Original file line numberDiff line numberDiff line change
@@ -563,11 +563,13 @@ TEST_F(TestUpdTransport, sending_multiframe_payload_for_non_anonymous)
563563

564564
constexpr auto timeout = 1s;
565565

566+
// +1 makes sure that 2 frames are needed - hence "multi-frame".
566567
const auto payload = makeIotaArray<UDPARD_MTU_DEFAULT_MAX_SINGLE_FRAME + 1>(b('0'));
567568
TransferTxMetadata metadata{{0x13, Priority::Nominal}, {}};
568569

569570
scheduler_.scheduleAt(1s, [&](const auto&) {
570571
//
572+
// Expect 1st frame sending.
571573
EXPECT_CALL(tx_socket_mock_, send(_, _, _, _))
572574
.WillOnce([&](auto deadline, auto endpoint, auto, auto fragments) {
573575
EXPECT_THAT(now(), metadata.deadline - timeout);
@@ -579,15 +581,22 @@ TEST_F(TestUpdTransport, sending_multiframe_payload_for_non_anonymous)
579581
});
580582
EXPECT_CALL(tx_socket_mock_, registerCallback(_)) //
581583
.WillOnce(Invoke([&](auto function) { //
582-
return scheduler_.registerAndScheduleNamedCallback("", now() + 10us, std::move(function));
584+
return scheduler_.registerAndScheduleNamedCallback("tx", now() + 10us, std::move(function));
583585
}));
584586

587+
// There was never any TX yet, so no callback should be registered.
588+
EXPECT_FALSE(scheduler_.hasNamedCallback("tx"));
589+
585590
metadata.deadline = now() + timeout;
586591
auto failure = session->send(metadata, makeSpansFrom(payload));
587592
EXPECT_THAT(failure, Eq(cetl::nullopt));
593+
594+
// We just did TX - it should register callback (to run at +10us) for the 1st frame.
595+
EXPECT_TRUE(scheduler_.hasNamedCallback("tx"));
588596
});
589597
scheduler_.scheduleAt(1s + 10us, [&](const auto&) {
590598
//
599+
// Expect 2nd frame sending.
591600
EXPECT_CALL(tx_socket_mock_, send(_, _, _, _))
592601
.WillOnce([&](auto deadline, auto endpoint, auto, auto fragments) {
593602
EXPECT_THAT(now(), metadata.deadline - timeout + 10us);
@@ -599,6 +608,19 @@ TEST_F(TestUpdTransport, sending_multiframe_payload_for_non_anonymous)
599608
return ITxSocket::SendResult::Success{true /* is_accepted */};
600609
});
601610
});
611+
scheduler_.scheduleAt(1s + 20us, [&](const auto&) {
612+
//
613+
// Callback is still there b/c the 2nd frame was sent.
614+
ASSERT_TRUE(scheduler_.hasNamedCallback("tx"));
615+
616+
// Emulate that media done with the 2nd frame - this should remove the callback b/c TX queue is empty.
617+
scheduler_.scheduleNamedCallback("tx", now());
618+
});
619+
scheduler_.scheduleAt(1s + 20us + 1us, [&](const auto&) {
620+
//
621+
// TX pipeline encountered an empty queue - hence the callback should be dropped.
622+
EXPECT_FALSE(scheduler_.hasNamedCallback("tx"));
623+
});
602624
scheduler_.scheduleAt(9s, [&](const auto&) {
603625
//
604626
session.reset();

0 commit comments

Comments
 (0)