Skip to content

Commit 6c448e0

Browse files
ignore aborted timer handler in SleepNode (#1267)
1 parent 8e22f83 commit 6c448e0

2 files changed

Lines changed: 36 additions & 3 deletions

File tree

‎src/actions/sleep_node.cpp‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,16 @@ NodeStatus SleepNode::onStart()
2525
timer_waiting_ = true;
2626

2727
timer_id_ = timer_.add(std::chrono::milliseconds(msec), [this](bool aborted) {
28-
const std::unique_lock<std::mutex> lk(delay_mutex_);
29-
if(!aborted)
28+
// An aborted handler runs later on the timer thread, possibly after
29+
// onStart() armed a new timer: it must not clear timer_waiting_, which
30+
// onHalted() already did.
31+
if(aborted)
3032
{
31-
emitWakeUpSignal();
33+
return;
3234
}
35+
const std::unique_lock<std::mutex> lk(delay_mutex_);
3336
timer_waiting_ = false;
37+
emitWakeUpSignal();
3438
});
3539

3640
return NodeStatus::RUNNING;

‎tests/gtest_decorator.cpp‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,35 @@ TEST(Decorator, TimeoutHaltWhileTimerIsHaltingTheChild)
566566
ASSERT_EQ(counters.total.load(), 1);
567567
}
568568

569+
// Regression test: halting a Sleep cancels its timer, and the canceled handler
570+
// runs later on the TimerQueue worker thread. When the Sleep was restarted in
571+
// the meantime, that stale handler used to clear the flag of the new timer, so
572+
// the restarted Sleep returned SUCCESS long before its duration.
573+
TEST(Decorator, SleepRestartedAfterHaltWaitsFullDuration)
574+
{
575+
BT::BehaviorTreeFactory factory;
576+
577+
const std::string xml_text = R"(
578+
<root BTCPP_format="4">
579+
<BehaviorTree ID="MainTree">
580+
<Sleep msec="500"/>
581+
</BehaviorTree>
582+
</root>)";
583+
584+
auto tree = factory.createTreeFromText(xml_text);
585+
586+
for(int i = 0; i < 20; i++)
587+
{
588+
ASSERT_EQ(tree.tickOnce(), NodeStatus::RUNNING);
589+
tree.haltTree();
590+
// restart before the timer thread runs the canceled handler
591+
ASSERT_EQ(tree.tickOnce(), NodeStatus::RUNNING);
592+
std::this_thread::sleep_for(milliseconds(20));
593+
ASSERT_EQ(tree.tickOnce(), NodeStatus::RUNNING) << "iteration " << i;
594+
tree.haltTree();
595+
}
596+
}
597+
569598
// Test KeepRunningUntilFailure decorator
570599
TEST(Decorator, KeepRunningUntilFailure)
571600
{

0 commit comments

Comments
 (0)