Skip to content

Commit 6c5dc22

Browse files
committed
Fixed callback lifetime bug in CAN transport; added unit test to specifically verify TX media callback lifetime
1 parent c3f7b5d commit 6c5dc22

File tree

4 files changed

+32
-3
lines changed

4 files changed

+32
-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

+1-1
Original file line numberDiff line numberDiff line change
@@ -610,7 +610,7 @@ TEST_F(TestUpdTransport, sending_multiframe_payload_for_non_anonymous)
610610
});
611611
scheduler_.scheduleAt(1s + 20us, [&](const auto&) {
612612
//
613-
// Callback still there b/c the 2nd frame was sent.
613+
// Callback is still there b/c the 2nd frame was sent.
614614
ASSERT_TRUE(scheduler_.hasNamedCallback("tx"));
615615

616616
// Emulate that media done with the 2nd frame - this should remove the callback b/c TX queue is empty.

0 commit comments

Comments
 (0)