現象
週次 schedule 実行の build-test (lyrical) が colcon test でタイムアウト失敗した。
ログの時系列:
- 先頭 14 テストは各 15–53 ms で完走
- 15 個目
Nav2BridgeTestFixture.IsWithinArrivalToleranceBoundaryYaw が [ RUN ] 出力後に復帰せず kill された
[ RUN ] 15 回 / コンストラクタ完了ログ (MapoiNav2Bridge initialized.) 15 回 / [ OK ] 14 回でカウントが一致する
→ SetUp は完走しており、返っていないのは TearDown
当該テストの本体は static 純関数への EXPECT_TRUE 1 行だけで、node_ を参照しない。
よってハングはテスト本体ではなく TearDown() の node_.reset() にある。
原因
Nav2BridgeTestFixture は毎テスト MapoiNav2Bridge を生成・破棄している。
コンストラクタは単引数版 (= spin_thread=true) の TransformListener を作る。
tf_listener_ = std::make_shared<tf2_ros::TransformListener>(*tf_buffer_);
単引数版は内部ノードと専用 executor スレッドを持ち、デストラクタで cancel() → join() する。
ここに未修正の upstream race がある。
この test binary は 1 run で 67 回 ノードを生成・破棄している (fixture 58 + テスト本体 9)。
そのたびに race の発火機会がある。各テストが 15–23 ms と短命なことが、
cancel-before-spin の窓に正確に嵌まっている。
なお lyrical / humble の既定 RMW はどちらも rmw_fastrtps_cpp であり、distro 差ではなく確率的な発火。
CPU が逼迫する CI ランナーで顕在化しやすい。
対応方針
本番コードの挙動は変えず、テスト側のノード生成回数を削減する。
Nav2BridgeTestFixture からノード生成を撤去する (rclcpp::init の保証のみ残す)。
node_ を参照しない 45 テストはこれで足りる。
node_ を必要とする 13 テストは、スイート内でノードを 1 個共有する新フィクスチャへ移す。
共有に伴う順序依存は、各テストで前提を明示的に初期化して潰す。
- コンストラクタの分岐そのものを検証する 9 テストは、ノード生成が検証対象なので残す。
これで 67 → 10 に減る。
採らない案
受入条件
- humble / lyrical 双方で
colcon build と colcon test --ctest-args -LE launch_test が green
- テストケース総数が変更前と一致する (取りこぼしが無いこと)
--gtest_shuffle を付けても green (順序依存が無いこと)
現象
週次 schedule 実行の
build-test (lyrical)がcolcon testでタイムアウト失敗した。1 - test_nav2_bridge_unit (Timeout)— ctest の 60 秒上限に到達build-test (humble)は成功ログの時系列:
Nav2BridgeTestFixture.IsWithinArrivalToleranceBoundaryYawが[ RUN ]出力後に復帰せず kill された[ RUN ]15 回 / コンストラクタ完了ログ (MapoiNav2Bridge initialized.) 15 回 /[ OK ]14 回でカウントが一致する→ SetUp は完走しており、返っていないのは TearDown
当該テストの本体は static 純関数への
EXPECT_TRUE1 行だけで、node_を参照しない。よってハングはテスト本体ではなく
TearDown()のnode_.reset()にある。原因
Nav2BridgeTestFixtureは毎テストMapoiNav2Bridgeを生成・破棄している。コンストラクタは単引数版 (=
spin_thread=true) のTransformListenerを作る。単引数版は内部ノードと専用 executor スレッドを持ち、デストラクタで
cancel()→join()する。ここに未修正の upstream race がある。
cancel()が専用スレッドのspin()開始前に走ると cancel シグナルが失われ、join()が永久にブロックするFixed deadlock in destructor in TransformListener ros2/geometry2#752 はいずれも未マージ。
humble と rolling でデストラクタ実装は同一で、両方とも未修正のまま
この test binary は 1 run で 67 回 ノードを生成・破棄している (fixture 58 + テスト本体 9)。
そのたびに race の発火機会がある。各テストが 15–23 ms と短命なことが、
cancel-before-spin の窓に正確に嵌まっている。
なお lyrical / humble の既定 RMW はどちらも
rmw_fastrtps_cppであり、distro 差ではなく確率的な発火。CPU が逼迫する CI ランナーで顕在化しやすい。
対応方針
本番コードの挙動は変えず、テスト側のノード生成回数を削減する。
Nav2BridgeTestFixtureからノード生成を撤去する (rclcpp::initの保証のみ残す)。node_を参照しない 45 テストはこれで足りる。node_を必要とする 13 テストは、スイート内でノードを 1 個共有する新フィクスチャへ移す。共有に伴う順序依存は、各テストで前提を明示的に初期化して潰す。
これで 67 → 10 に減る。
採らない案
#ifdef UNIT_TESTで本番コードに分岐を足す: テストした物と出荷する物が別になる。TransformListenerをspin_thread=falseにする: 本番の TF 更新スレッド構成が変わる。select_map_callbackが default group を最大 ~11s 塞ぐ設計 (mapoi_nav_server の callback_group 分離: backend_status timer を blocking 呼び出しと独立させる #213) と干渉するため、CI flake 対策とは切り分けて別途検討する。
受入条件
colcon buildとcolcon test --ctest-args -LE launch_testが green--gtest_shuffleを付けても green (順序依存が無いこと)