Skip to content

Commit fae0f78

Browse files
authored
feat(start_planner): fix non-thread-safe access (#6779)
Signed-off-by: Mamoru Sobue <mamoru.sobue@tier4.jp>
1 parent 9f3633d commit fae0f78

File tree

3 files changed

+148
-39
lines changed

3 files changed

+148
-39
lines changed

planning/behavior_path_start_planner_module/include/behavior_path_start_planner_module/pull_out_planner_base.hpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ class PullOutPlannerBase
5151
}
5252
virtual ~PullOutPlannerBase() = default;
5353

54-
void setPlannerData(std::shared_ptr<const PlannerData> & planner_data)
54+
void setPlannerData(const std::shared_ptr<const PlannerData> & planner_data)
5555
{
5656
planner_data_ = planner_data;
5757
}

planning/behavior_path_start_planner_module/include/behavior_path_start_planner_module/start_planner_module.hpp

+21-2
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,24 @@ class StartPlannerModule : public SceneModuleInterface
155155
bool isFreespacePlanning() const { return status_.planner_type == PlannerType::FREESPACE; }
156156

157157
private:
158+
struct StartPlannerData
159+
{
160+
StartPlannerParameters parameters;
161+
PlannerData planner_data;
162+
ModuleStatus current_status;
163+
PullOutStatus main_thread_pull_out_status;
164+
bool is_stopped;
165+
166+
StartPlannerData clone() const;
167+
void update(
168+
const StartPlannerParameters & parameters, const PlannerData & planner_data,
169+
const ModuleStatus & current_status, const PullOutStatus & pull_out_status,
170+
const bool is_stopped);
171+
};
172+
std::optional<PullOutStatus> freespace_thread_status_{std::nullopt};
173+
std::optional<StartPlannerData> start_planner_data_{std::nullopt};
174+
std::mutex start_planner_data_mutex_;
175+
158176
// Flag class for managing whether a certain callback is running in multi-threading
159177
class ScopedFlag
160178
{
@@ -290,7 +308,6 @@ class StartPlannerModule : public SceneModuleInterface
290308
bool hasFinishedBackwardDriving() const;
291309
bool hasCollisionWithDynamicObjects() const;
292310
bool isStopped();
293-
bool isStuck();
294311
bool hasFinishedCurrentPath();
295312
void updateSafetyCheckTargetObjectsData(
296313
const PredictedObjects & filtered_objects, const TargetObjectsOnLane & target_objects_on_lane,
@@ -307,7 +324,9 @@ class StartPlannerModule : public SceneModuleInterface
307324
SafetyCheckParams createSafetyCheckParams() const;
308325
// freespace planner
309326
void onFreespacePlannerTimer();
310-
bool planFreespacePath();
327+
std::optional<PullOutStatus> planFreespacePath(
328+
const StartPlannerParameters & parameters,
329+
const std::shared_ptr<const PlannerData> & planner_data, const PullOutStatus & pull_out_status);
311330

312331
void setDebugData();
313332
void logPullOutStatus(rclcpp::Logger::Level log_level = rclcpp::Logger::Level::Info) const;

planning/behavior_path_start_planner_module/src/start_planner_module.cpp

+126-36
Original file line numberDiff line numberDiff line change
@@ -93,26 +93,56 @@ void StartPlannerModule::onFreespacePlannerTimer()
9393
{
9494
const ScopedFlag flag(is_freespace_planner_cb_running_);
9595

96-
if (getCurrentStatus() == ModuleStatus::IDLE) {
96+
std::shared_ptr<const PlannerData> local_planner_data{nullptr};
97+
std::optional<ModuleStatus> current_status_opt{std::nullopt};
98+
std::optional<StartPlannerParameters> parameters_opt{std::nullopt};
99+
std::optional<PullOutStatus> pull_out_status_opt{std::nullopt};
100+
bool is_stopped;
101+
102+
// making a local copy of thread sensitive data
103+
{
104+
std::lock_guard<std::mutex> guard(start_planner_data_mutex_);
105+
if (start_planner_data_) {
106+
const auto & start_planner_data = start_planner_data_.value();
107+
local_planner_data = std::make_shared<PlannerData>(start_planner_data.planner_data);
108+
current_status_opt = start_planner_data.current_status;
109+
parameters_opt = start_planner_data.parameters;
110+
pull_out_status_opt = start_planner_data.main_thread_pull_out_status;
111+
is_stopped = start_planner_data.is_stopped;
112+
}
113+
}
114+
// finish copying thread sensitive data
115+
if (!local_planner_data || !current_status_opt || !parameters_opt || !pull_out_status_opt) {
97116
return;
98117
}
99118

100-
if (!planner_data_) {
119+
const auto & current_status = current_status_opt.value();
120+
const auto & parameters = parameters_opt.value();
121+
const auto & pull_out_status = pull_out_status_opt.value();
122+
123+
if (current_status == ModuleStatus::IDLE) {
101124
return;
102125
}
103126

104-
if (!planner_data_->costmap) {
127+
if (!local_planner_data->costmap) {
105128
return;
106129
}
107130

108131
const bool is_new_costmap =
109-
(clock_->now() - planner_data_->costmap->header.stamp).seconds() < 1.0;
132+
(clock_->now() - local_planner_data->costmap->header.stamp).seconds() < 1.0;
110133
if (!is_new_costmap) {
111134
return;
112135
}
113136

114-
if (isStuck()) {
115-
planFreespacePath();
137+
const bool is_stuck = is_stopped && pull_out_status.planner_type == PlannerType::STOP &&
138+
!pull_out_status.found_pull_out_path;
139+
if (is_stuck) {
140+
const auto free_space_status =
141+
planFreespacePath(parameters, local_planner_data, pull_out_status);
142+
if (free_space_status) {
143+
std::lock_guard<std::mutex> guard(start_planner_data_mutex_);
144+
freespace_thread_status_ = free_space_status;
145+
}
116146
}
117147
}
118148

@@ -172,6 +202,38 @@ void StartPlannerModule::updateObjectsFilteringParams(
172202

173203
void StartPlannerModule::updateData()
174204
{
205+
// The method PlannerManager::run() calls SceneModuleInterface::setData and
206+
// SceneModuleInterface::setPreviousModuleOutput() before this module's run() method is called
207+
// with module_ptr->run(). Then module_ptr->run() invokes StartPlannerModule::updateData and,
208+
// finally, the planWaitingApproval()/plan() methods are called by run(). So we can copy the
209+
// latest current_status to start_planner_data_ here for later usage.
210+
211+
// NOTE: onFreespacePlannerTimer copies start_planner_data to its thread local variable, so we
212+
// need to lock start_planner_data_ here to avoid data race. But the following clone process is
213+
// lightweight because most of the member variables of PlannerData/RouteHandler is
214+
// shared_ptrs/bool
215+
// making a local copy of thread sensitive data
216+
{
217+
std::lock_guard<std::mutex> guard(start_planner_data_mutex_);
218+
if (!start_planner_data_) {
219+
start_planner_data_ = StartPlannerData();
220+
}
221+
start_planner_data_.value().update(
222+
*parameters_, *planner_data_, getCurrentStatus(), status_, isStopped());
223+
if (freespace_thread_status_) {
224+
// if freespace solution is available, copy it to status_ on main thread
225+
const auto & freespace_status = freespace_thread_status_.value();
226+
status_.pull_out_path = freespace_status.pull_out_path;
227+
status_.pull_out_start_pose = freespace_status.pull_out_start_pose;
228+
status_.planner_type = freespace_status.planner_type;
229+
status_.found_pull_out_path = freespace_status.found_pull_out_path;
230+
status_.driving_forward = freespace_status.driving_forward;
231+
// and then reset it
232+
freespace_thread_status_ = std::nullopt;
233+
}
234+
}
235+
// finish copying thread sensitive data
236+
175237
if (receivedNewRoute()) {
176238
resetStatus();
177239
DEBUG_PRINT("StartPlannerModule::updateData() received new route, reset status");
@@ -1128,19 +1190,6 @@ bool StartPlannerModule::needToPrepareBlinkerBeforeStartDrivingForward() const
11281190
return elapsed < parameters_->prepare_time_before_start;
11291191
}
11301192

1131-
bool StartPlannerModule::isStuck()
1132-
{
1133-
if (!isStopped()) {
1134-
return false;
1135-
}
1136-
1137-
if (status_.planner_type == PlannerType::STOP || !status_.found_pull_out_path) {
1138-
return true;
1139-
}
1140-
1141-
return false;
1142-
}
1143-
11441193
bool StartPlannerModule::hasFinishedCurrentPath()
11451194
{
11461195
const auto current_path = getCurrentPath();
@@ -1336,19 +1385,21 @@ BehaviorModuleOutput StartPlannerModule::generateStopOutput()
13361385
return output;
13371386
}
13381387

1339-
bool StartPlannerModule::planFreespacePath()
1388+
std::optional<PullOutStatus> StartPlannerModule::planFreespacePath(
1389+
const StartPlannerParameters & parameters,
1390+
const std::shared_ptr<const PlannerData> & planner_data, const PullOutStatus & pull_out_status)
13401391
{
1341-
const Pose & current_pose = planner_data_->self_odometry->pose.pose;
1342-
const auto & route_handler = planner_data_->route_handler;
1392+
const Pose & current_pose = planner_data->self_odometry->pose.pose;
1393+
const auto & route_handler = planner_data->route_handler;
13431394

1344-
const double end_pose_search_start_distance = parameters_->end_pose_search_start_distance;
1345-
const double end_pose_search_end_distance = parameters_->end_pose_search_end_distance;
1346-
const double end_pose_search_interval = parameters_->end_pose_search_interval;
1395+
const double end_pose_search_start_distance = parameters.end_pose_search_start_distance;
1396+
const double end_pose_search_end_distance = parameters.end_pose_search_end_distance;
1397+
const double end_pose_search_interval = parameters.end_pose_search_interval;
13471398

13481399
const double backward_path_length =
1349-
planner_data_->parameters.backward_path_length + parameters_->max_back_distance;
1400+
planner_data->parameters.backward_path_length + parameters.max_back_distance;
13501401
const auto current_lanes = utils::getExtendedCurrentLanes(
1351-
planner_data_, backward_path_length, std::numeric_limits<double>::max(),
1402+
planner_data, backward_path_length, std::numeric_limits<double>::max(),
13521403
/*forward_only_in_route*/ true);
13531404

13541405
const auto current_arc_coords = lanelet::utils::getArcCoordinates(current_lanes, current_pose);
@@ -1361,23 +1412,23 @@ bool StartPlannerModule::planFreespacePath()
13611412

13621413
for (const auto & p : center_line_path.points) {
13631414
const Pose end_pose = p.point.pose;
1364-
freespace_planner_->setPlannerData(planner_data_);
1415+
freespace_planner_->setPlannerData(planner_data);
13651416
auto freespace_path = freespace_planner_->plan(current_pose, end_pose);
13661417

13671418
if (!freespace_path) {
13681419
continue;
13691420
}
13701421

1371-
const std::lock_guard<std::mutex> lock(mutex_);
1372-
status_.pull_out_path = *freespace_path;
1373-
status_.pull_out_start_pose = current_pose;
1374-
status_.planner_type = freespace_planner_->getPlannerType();
1375-
status_.found_pull_out_path = true;
1376-
status_.driving_forward = true;
1377-
return true;
1422+
auto status = pull_out_status;
1423+
status.pull_out_path = *freespace_path;
1424+
status.pull_out_start_pose = current_pose;
1425+
status.planner_type = freespace_planner_->getPlannerType();
1426+
status.found_pull_out_path = true;
1427+
status.driving_forward = true;
1428+
return std::make_optional<PullOutStatus>(status);
13781429
}
13791430

1380-
return false;
1431+
return std::nullopt;
13811432
}
13821433

13831434
void StartPlannerModule::setDrivableAreaInfo(BehaviorModuleOutput & output) const
@@ -1665,4 +1716,43 @@ void StartPlannerModule::logPullOutStatus(rclcpp::Logger::Level log_level) const
16651716

16661717
logFunc("=======================================");
16671718
}
1719+
1720+
StartPlannerModule::StartPlannerData StartPlannerModule::StartPlannerData::clone() const
1721+
{
1722+
StartPlannerData start_planner_data;
1723+
start_planner_data.update(
1724+
parameters, planner_data, current_status, main_thread_pull_out_status, is_stopped);
1725+
return start_planner_data;
1726+
}
1727+
1728+
void StartPlannerModule::StartPlannerData::update(
1729+
const StartPlannerParameters & parameters_, const PlannerData & planner_data_,
1730+
const ModuleStatus & current_status_, const PullOutStatus & pull_out_status_,
1731+
const bool is_stopped_)
1732+
{
1733+
parameters = parameters_;
1734+
planner_data = planner_data_;
1735+
// TODO(Mamoru Sobue): in the future we will add planner_data->is_route_handler_updated flag to
1736+
// avoid the copy overhead of lanelet objects inside the RouteHandler. behavior_path_planner can
1737+
// tell us the flag if map/route changed, so we can skip route_handler update if it
1738+
// is false in the following way
1739+
/*
1740+
auto route_handler_self = planner_data.route_handler;
1741+
planner_data = planner_data_; // sync planer_data to planner_data_, planner_data.route_handler
1742+
is once re-pointed
1743+
1744+
if (!planner_data_->is_route_handler_updated && route_handler_self != nullptr) {
1745+
// we do not need to sync planner_data.route_handler with that of planner_data_
1746+
// re-point to the original again
1747+
planner_data.route_handler = route_handler_self;
1748+
} else {
1749+
// this is actually heavy if the lanelet_map is HUGE
1750+
planner_data.route_handler = std::make_shared<RouteHandler>(*(planner_data_.route_handler));
1751+
}
1752+
*/
1753+
planner_data.route_handler = std::make_shared<RouteHandler>(*(planner_data_.route_handler));
1754+
current_status = current_status_;
1755+
main_thread_pull_out_status = pull_out_status_;
1756+
is_stopped = is_stopped_;
1757+
}
16681758
} // namespace behavior_path_planner

0 commit comments

Comments
 (0)