# 노드 얘기치 않는 종료(Crash) 발생 시 대처

ROS2 Humble 환경에서 노드가 예기치 않게 종료(Crash)되는 상황은 분산 환경 디버깅의 난이도를 높이는 주요 요인 중 하나이다. 충돌의 원인을 효과적으로 파악하기 위해서는 노드 단에서 발생하는 메모리 접근 오류(Segmentation Fault), 예외 처리 누락, 혹은 라이브러리·의존성 충돌 등 다양한 시나리오를 고려해야 한다. 특히 네트워크를 통해 통신하는 다중 노드 구조에서는 “어떤 노드가 왜 죽었는지” 뿐만 아니라 “어떤 조건에서 죽었는지”까지 파악해야 하므로 노드 충돌을 재현하기 위한 시나리오 설정과 디버깅 기법을 충분히 숙지해야 한다.

ROS2의 프로세스 충돌은 OS가 프로세스를 강제로 종료시키는 형태로 나타날 수도 있고, 스스로의 예외 처리 루틴(Exception Handler)에서 문제가 발생해 중단되는 경우도 있다. 따라서 충돌 시에는 해당 노드가 동작하던 워크스페이스와 터미널(혹은 로거)에서 남긴 메시지, 시스템 로그, core dump 파일 등을 확보해야 한다.

#### 노드 Crash가 발생하는 흔한 원인

1. **메모리 접근 오류**
   * C++ 라이브러리를 사용하는 경우, 동적 메모리 할당 해제 시점이나 범위를 벗어나는 포인터 참조가 가장 흔한 원인이다.
   * 주로 `std::vector` 또는 배열 범위를 넘어서는 쓰기 연산이 문제를 일으킨다.
   * 예: 노드 콜백함수에서 동적 할당한 포인터를 나중에 해제하지 않거나, 이미 해제한 포인터를 다시 참조하는 경우 등.
2. **라이브러리·의존성 버전 충돌**
   * ROS2 패키지 의존성에서 ABI(Application Binary Interface) 수준의 불일치가 발생해 충돌이 일어날 수 있다.
   * ROS2 Humble에 맞지 않는 외부 라이브러리를 링크할 경우, 런타임 시에 예상치 못한 심볼(Symbol) 누락이나 중복 정의 문제가 발생해 Crash로 이어질 수 있다.
3. **예외 처리 누락**
   * C++의 `throw` 구문을 처리하지 못하거나, Python 노드에서 발생한 예외가 적절히 잡히지 않으면 노드가 곧바로 종료될 수 있다.
   * try-catch 블록에서 예외를 제대로 처리하지 않거나, `std::exception`이 아닌 다른 타입(예: int, char 등)으로 `throw`되는 경우도 문제가 된다.
4. **ROS2 미들웨어(RMW) 이슈**
   * RMW 계층에서의 QoS 설정 불일치, DDS 벤더 이슈, 혹은 네트워크 환경 불안정 등으로 인해 노드가 데이터를 송·수신하다가 Crash에 이를 수 있다.
   * 특히 FastDDS, CycloneDDS 등 벤더별로 특정 상황에서 발생하는 버그(Unsupported Feature)가 있을 수 있으며, 이런 경우에는 관련 이슈 트래킹을 반드시 확인해야 한다.
5. **Node Interface 잘못된 사용**
   * ROS2 노드가 관리하는 Executor, CallbackGroup, Timer, Parameter Client 등에서 API 사용이 잘못되거나, destroy 후 재사용이 이뤄지면 Crash가 발생한다.
   * rclcpp Node 인스턴스를 잘못 가리키는 shared\_ptr, weak\_ptr 문제도 대표적 사례다.

#### Crash 후 재시작 전략

노드 충돌이 발생했을 때, 이를 무조건 수동으로 다시 띄울 수는 없으므로 자동 복구(Autorestart) 기법을 고려해야 한다. 특히 로보틱스 시스템에서는 장애 복구 시간이 길어지면 전체 시스템이 중단될 수 있으므로, Node Level의 장애 대응 방안이 중요하다.

**ros2 launch 파일에서 re-spawn 설정**:

* ROS2에서 launch 파일을 이용해 노드를 실행할 때, 특정 노드가 종료되면 재시작하는 ‘re-spawn’ 파라미터를 둘 수 있다.
* 예시:

```xml
<launch>
  <node pkg="my_pkg" exec="my_node" name="my_node_instance" respawn="true" respawn_delay="5"/>
</launch>
```

* 위처럼 `respawn="true"`로 지정하면 Crash 발생 시 자동으로 재시작되며, `respawn_delay`를 통해 재시작 전 지연 시간을 설정할 수 있다.

**Supervisor 노드 또는 Watchdog 구현**:

* 별도의 Supervisor 노드가 다른 노드들의 생존 여부를 주기적으로 점검(Ping)하고, Crash가 확인되면 즉시 재시작 요청을 보낸다.
* 노드의 Heartbeat 토픽을 구독하거나, ROS2 Lifecycle 상태를 통해 노드 상태 변화를 모니터링하는 방법도 있다.

**OS 수준의 Systemd·Docker 사용**:

* Ubuntu 환경에서는 Systemd의 서비스 단위로 ROS2 노드를 실행해, Crash가 발생하면 자동 재시작을 시도하게 설정할 수 있다.
* Docker 환경에서는 컨테이너 재시작 정책(예: `--restart=always`)을 통해 컨테이너가 Crash되는 즉시 재시작하도록 구성할 수도 있다.

#### Core Dump 분석

Crash가 발생하면 일반적으로 OS는 core dump 파일을 생성한다. 해당 파일에는 충돌 지점의 스택 정보와 메모리 상태가 포함되어 있어, 사후 분석(Post-mortem Debugging)을 가능하게 해준다.

**Core Dump 설정**:

* Ubuntu에서 core dump 생성을 활성화하려면, 셸에서 다음과 같이 설정한다:

```bash
ulimit -c unlimited
```

* 또는 `/etc/security/limits.conf` 파일에 세션 혹은 사용자별로 core dump 제한을 설정할 수도 있다.
* 실제로 core dump 파일이 어느 디렉터리에 생성되는지는 시스템 설정에 따라 다르므로 `/proc/sys/kernel/core_pattern`을 확인한다:

```bash
cat /proc/sys/kernel/core_pattern
```

* 여러 노드가 동시에 실행되는 경우, 각각의 노드 프로세스에 대해 개별 core dump 파일을 생성할 수 있도록 구성을 바꿀 수 있다.

**Core Dump 파일 분석**:

* C++ 노드의 경우 GDB(혹은 LLDB)로 core dump 파일을 열어 분석한다:

```bash
gdb <실행파일경로> <코어덤프파일경로>
```

* GDB에서 `bt` (backtrace) 명령으로 충돌 지점까지의 함수 호출 스택을 확인한다:

```gdb
(gdb) bt
```

* 분석 시 주로 확인하는 항목은 ‘충돌 함수’, ‘함수 호출 스택’, ‘로컬 변수값’ 등이다. 필요에 따라 `frame`, `info local`, `p variable_name` 등을 활용해 세부 정보를 살펴본다.

#### 고급 디버깅 기법

ROS2 노드 충돌은 단순히 코드 로직의 오류로 발생하는 경우도 많지만, 멀티스레드 환경, 동적 메모리 사용, 각종 RMW 계층 의존성 등에 의해 복합적으로 유발되기도 한다. 이러한 복잡한 문제를 찾아내기 위해서는 전통적인 단일 프로세스 디버거(GDB, LLDB) 외에도 다양한 분석 기법을 병행해야 한다.

**AddressSanitizer(ASan)**:

* 메모리 접근 오류(버퍼 오버런, Use-After-Free 등)를 자동으로 감지하는 런타임 도구이다.
* Clang 혹은 GCC 컴파일 시 `-fsanitize=address -fno-omit-frame-pointer -g` 등의 플래그를 적용하여 빌드한다.
* 예시:

```bash
colcon build --cmake-args \
  -DCMAKE_CXX_FLAGS="-fsanitize=address -fno-omit-frame-pointer -g"
```

* 실행 시 문제가 감지되면 콘솔에 즉시 경고와 함께 스택 트레이스(위치 정보)가 출력된다.

**Valgrind**:

* 메모리 누수, 잘못된 메모리 접근 등 광범위한 메모리 사용 오류를 검사하는 데 활용된다.
* 다만 ROS2의 멀티스레드 구조와 DDS 통신량이 큰 경우, Valgrind 상에서 실행 속도가 매우 느려질 수 있다.
* 예시:

  ```bash
  valgrind --tool=memcheck ros2 run my_pkg my_node
  ```

**ThreadSanitizer(TSan)**:

* 멀티스레드 환경에서 발생하는 데이터 레이스(Data Race), 뮤텍스 충돌, 락 순서 교착(Deadlock) 등을 탐지해준다.
* 특히 콜백이 여러 스레드에서 동시에 실행되는 ROS2 Executor 구조를 사용하는 경우, 동시성 문제로 인해 예기치 못한 Crash가 발생할 수 있다.
* 빌드 시 다음과 같은 플래그를 준다:

```bash
colcon build --cmake-args \
  -DCMAKE_CXX_FLAGS="-fsanitize=thread -fno-omit-frame-pointer -g"
```

* 실행 시 스레드 충돌이 감지되면 경고 메시지와 함께 정확한 소스 파일, 함수, 스택 트레이스를 확인할 수 있다.

**식별 정보(Symbol) 확보**:

* Debug Symbol이 없는 상태에서 Crash가 발생하면 스택 트레이스만으로는 어디에서 오류가 났는지 알기 어렵다.
* CMake 빌드시 `-g -O0 -DDEBUG` 등의 플래그를 추가해 디버그 기호를 충분히 확보하고, 최적화를 낮춘 상태에서 우선 디버깅을 진행한다.

**라이브 디버깅**:

* GDB 또는 LLDB로 노드를 바로 실행하고, Crash 직전에 중단(Breakpoint)시켜 메모리·변수를 확인한다.
* 예시:

```bash
ros2 run --prefix 'gdb --args' my_pkg my_node
```

* 여러 노드를 동시에 구동하는 Launch 환경에서는 `--prefix` 파라미터를 통해 특정 노드만 GDB에 연결할 수 있다.

**사후 디버깅**:

* Core Dump 파일을 분석하는 방식을 주로 활용한다.
* 배포 환경(Release Build)에서 Crash 발생 시 즉시 Debug Build로 재실행하기 어렵기 때문에, 사후 디버깅이 더 효율적일 수 있다.

#### 동시성(Concurrency) 문제와 Crash

ROS2는 멀티스레드 Executor 구조, 콜백 그룹(Callback Group)에 따라 콜백이 병렬 실행될 수 있으므로, 일반적인 싱글스레드 환경에서보다 동시성 이슈가 더 빈번하게 발생한다. 특히 콜백 간 공유자원에 대한 접근이 올바르게 보호되지 않으면 데이터 레이스로 이어질 수 있다.

1. **콜백 그룹(Callback Group)의 올바른 설정**
   * `MutuallyExclusive` 설정으로 그룹 내에서 콜백이 동시에 실행되지 않도록 제한할 수 있다.
   * `Reentrant` 설정은 콜백이 병렬로 실행될 수 있음을 의미한다.
   * 복잡한 노드라면 필요한 자원을 어떤 콜백 그룹에서 공유하는지 설계 단계에서부터 정교하게 검토해야 한다.
2. **락(Lock) 사용 시 주의사항**
   * C++ 표준 라이브러리의 `std::mutex`, `std::recursive_mutex`, `std::shared_mutex` 등을 사용할 경우, 락 순서가 뒤바뀌거나 조건 변수가 신호를 놓치면 교착상태나 Crash가 발생할 수 있다.
   * ROS2 콜백이 서로 다른 executor에서 실행될 수도 있음을 인지해야 하므로, 전역 혹은 static 객체에 대한 락 경쟁을 주의한다.
3. **원자성(Atomic) 보장 여부 확인**
   * 공유 변수를 갱신할 때는 `std::atomic` 자료형을 사용하거나, 뮤텍스로 보호해야 한다.
   * 단순히 volatile로 선언하는 것만으로는 원자적 연산을 보장하지 못한다.

#### Crash 재현 시나리오 작성

디버깅에서는 일회성으로 발생하는 Crash를 재현 가능하게 만드는 과정이 핵심이다. Crash가 다시 발생해야 문제의 원인을 찾고 수정했는지 확인할 수 있기 때문이다.

1. **단위 테스트 기반 시나리오**
   * `rostest` 또는 `launch_testing`을 이용해 재현 테스트를 자동화한다.
   * 토픽 퍼블리셔·서브스크라이버 간에 메시지 트래픽 양을 높이거나, QoS를 극단적인 설정으로 바꿔보며 Crash를 재현한다.
2. **스트레스 테스트(Stress Test)**
   * 동일 노드를 여러 개 인스턴스로 실행해 자원 경쟁을 유도하거나, 메시지 발행 빈도를 매우 높여 네트워크 부담을 준다.
   * CPU·메모리 부하가 큰 상황에서만 재현되는 Crash가 종종 있으므로, `stress-ng`, `cpulimit`, `tc`(Traffic Control) 등 OS 툴을 활용해 환경을 조작한다.
3. **파라미터/환경 변화**
   * ROS2 Node 파라미터를 엣지 케이스(최솟값, 최댓값, 음수, 매우 큰 값 등)로 설정하거나, DDS 라우팅 QoS를 비정상적인 값으로 준다.
   * 콜백 주기가 매우 짧거나(1ms 이하), 타이머를 동시에 여러 개 두어 콜백이 한꺼번에 몰리도록 조정한다.

#### Crash 로그 수집

ROS2 노드 충돌의 원인을 빠르게 찾기 위해서는 로그를 최대한 풍부하게 수집하고, 이를 체계적으로 보관·분석하는 방법이 중요하다. 특히 어떤 시점에 어떤 메시지가 출력되었는지, 충돌 전후로 ROS2 로거에서 어떤 warn/error 메시지가 있었는지가 큰 도움이 된다.

1. **ROS2 로거(Output)**
   * C++ 노드에서는 `RCLCPP_INFO`, `RCLCPP_WARN`, `RCLCPP_ERROR` 등을 이용해 적절히 로그 레벨을 남겨야 한다.
   * Python 노드에서는 `rclpy.logging` 모듈을 사용해 로그를 남길 수 있다.
   * Crash 직전 발생한 에러 로그를 확인하면, 예외 혹은 메모리 문제 등의 징후를 파악할 수 있다.
2. **syslog / journalctl**
   * Ubuntu 시스템에서는 데몬 프로세스나 서비스 형태로 노드를 실행하는 경우, `/var/log/syslog` 또는 `journalctl -u <서비스명>` 등을 통해 별도 로그가 기록될 수 있다.
   * Node가 Crash로 종료될 때, `SIGSEGV`, `SIGABRT` 등의 신호가 발생하며, 관련 정보가 syslog에 남을 수 있다.
3. **Docker Logs**
   * Docker 컨테이너 내에서 ROS2 노드를 실행한다면, `docker logs <컨테이너ID>` 명령으로 Crash 관련 정보를 확인할 수 있다.
   * 컨테이너 내부에서 core dump 설정을 별도로 해줘야 할 수도 있으므로, Dockerfile에 `ulimit` 설정을 추가하거나 호스트와 볼륨을 공유해 core dump 파일을 보관하는 방식을 고려한다.
4. **로깅 수준(Level) 동적 변경**
   * ROS2는 노드 실행 중에 `ros2 param set <node_name> rclcpp_log_level <level>` 명령 등을 통해 로깅 수준을 바꿀 수 있다.
   * Crash 원인이 분명하지 않을 때, 실행 중에 로그 레벨을 높여서 DEBUG나 TRACE를 활성화해 더욱 상세한 정보를 기록할 수 있다.

#### Crash와 이슈 추적

ROS2 기반 프로젝트가 점차 커지면서, 단순히 Crash를 관찰하는 것만으로는 해결 방안을 신속히 찾기 어렵다. 체계적인 이슈 추적과 관리가 필요한 이유다.

1. **이슈 추적 툴 사용**
   * GitHub Issues, Jira 등에서 Crash 상황을 재현하는 환경 정보, 로그, core dump 스택 트레이스, 노드 구성 정보를 체계적으로 정리한다.
   * Crash가 발생하는 정확한 조건(예: 특정 QoS 설정, 특정 메시지 타입, 특정 시간 지연 등)을 재현 시나리오로 정리해두면, 팀 단위로 원인 분석이 쉬워진다.
2. **버전 관리와 Crash 보고**
   * Crash가 특정 커밋이나 태그에서만 발생한다면, `git bisect` 등을 통해 어느 시점에 문제가 도입되었는지 추적한다.
   * 이를 통해 ROS2 프레임워크 자체(예: rmw\_fastrtps 버전)와 애플리케이션 코드 사이에 충돌이 발생하는 경우를 신속히 파악할 수 있다.
3. **ROS2 Issue Tracker 활용**
   * ROS2의 핵심 레포지토리(e.g., `rclcpp`, `rmw_fastrtps`, `rclpy`)에서 발생하는 Crash라면, ROS2 Issue Tracker에 리포트하고 커뮤니티로부터 피드백을 받을 수 있다.
   * DDS 벤더(예: eProsima, ADLINK, RTI)별 이슈 트래커가 분리되어 있으니, 특정 DDS 버그가 의심된다면 해당 벤더 Issue Tracker에도 제출해야 한다.

#### CI/CD 통합 디버깅

대규모 ROS2 프로젝트에서는 단순 로컬 환경에서의 Crash 디버깅만으로는 충분하지 않다. CI/CD 파이프라인 상에서 자동으로 빌드·테스트를 수행하고, Crash 발생 시 그 로그·core dump까지 함께 수집해주는 환경을 구축하면 효율성이 크게 높아진다.

1. **Automated Test 및 Crash 감지**
   * GitHub Actions, GitLab CI 등에서 `colcon test` 또는 `ros2 test`를 구동해 Crash 여부를 점검한다.
   * Crash가 발생하면 CI가 즉시 실패를 리턴하며, 로그와(core dump 등) 아티팩트를 CI 서버에 업로드하도록 구성한다.
2. **Docker 컨테이너 기반 테스트**
   * 일관된 빌드·실행 환경을 유지하기 위해 Docker 컨테이너를 사용해 CI 파이프라인을 구축한다.
   * 컨테이너가 Crash를 일으키면 Docker 레벨에서 로그가 수집되고, core dump도 컨테이너 볼륨으로 복사해 기록할 수 있다.
3. **메모리/스레드 Sanitizer 통합**
   * CI에서 AddressSanitizer, ThreadSanitizer 옵션을 활성화하여, 매 PR(Pull Request)마다 메모리 오류·데이터 레이스를 체크한다.
   * Crash는 안 나더라도 데이터 레이스나 메모리 누수가 있다는 경고만으로도 문제를 사전에 발견할 수 있다.

#### 추가 디버깅 툴 및 기법

Crash 문제를 해결하기 위해서는 단순히 GDB, core dump 분석에 그치지 않고, 시스템 레벨에서 다양한 툴을 활용하여 프로그램의 동작을 추적·계측하는 방식을 고려할 수 있다. 특히 로보틱스 시스템처럼 실시간성을 요구하는 상황이나 외부 라이브러리가 많이 얽힌 환경에서는 문제 원인이 여러 계층에 걸쳐 있을 수 있으므로, 더 정교한 디버깅 기법이 필요하다.

**strace**:

* 리눅스 시스템 콜(System Call)의 호출 과정을 추적하고, 각 호출 결과와 에러 코드를 확인할 수 있다.
* Crash를 일으키는 특정 시점에 어떤 시스템 콜이 실패했는지 알면, 권한 문제나 파일 I/O 문제, 소켓 연결 오류 등을 간접적으로 파악 가능하다.
* 예시:

```bash
strace -o trace.log ros2 run my_pkg my_node
```

* Crash 직전의 마지막 시스템 콜이 무엇인지 확인하면 힌트를 얻을 수 있다.

**ltrace**:

* 라이브러리 함수 호출을 추적하며, 동적 링킹 문제나 external 라이브러리 호출 시점에서의 오류를 파악하기 용이하다.
* 예시:

```bash
ltrace -o ltrace.log ros2 run my_pkg my_node
```

* 특정 라이브러리 함수를 호출하다가 Crash가 발생하는지 추적할 수 있다.

**perf**:

* CPU 이벤트(캐시 미스, 사이클, 브랜치 미스 등)를 계측하고, 함수 프로파일링(어떤 함수가 얼마만큼 CPU를 사용했는지)을 지원한다.
* Crash가 성능 병목이나 경합으로 인해 발생하는지, 혹은 특정 라이브러리를 과도하게 호출하는지 파악할 수 있다.

**eBPF(Berkeley Packet Filter)**:

* 커널 레벨에서 동작하는 다양한 트레이싱 툴(bpftrace, syscount, opensnoop 등)을 통해, 노드가 시스템과 어떻게 상호작용하는지 실시간으로 관찰할 수 있다.
* 고성능, 실시간 모니터링이 가능하나, 설정이 까다로울 수 있다.

**Fuzzing(퍼징) 기법**:

* 입력 데이터를 무작위/반무작위로 조작해 프로그램이 예기치 못한 데이터를 처리하게 함으로써 Crash를 유발해보는 테스트 방식이다.
* ROS2 노드 간 메시지 구조에 대해 Fuzzing을 적용해볼 수 있으나, 일반적으로 메시지 타입이 엄격하게 정해져 있기 때문에 범용 Fuzzing 툴과는 조금 다른 접근이 필요하다.
* 그래도 노드가 처리할 수 있는 범위 이상의 값, 혹은 전혀 다른 타입의 데이터를 강제로 주입하는 식으로 Crash를 재현할 수도 있다.

**신호(Signal) 핸들링 강화**:

* Unix 계열 시스템에서 Crash에 대응하는 시그널(예: SIGSEGV, SIGABRT, SIGFPE 등)을 개발자가 직접 핸들링하여, Crash 직전에 특정 로그를 더 남기거나 상태를 덤프할 수도 있다.
* C++의 경우 `std::signal(SIGSEGV, custom_handler);` 같은 방식으로 등록 가능하지만, 잘못 사용하면 오히려 Crash 디버깅을 더 복잡하게 만들 수 있으므로 주의해야 한다.
* 예시:

```cpp
#include <signal.h>

void segv_handler(int signum) {
  // custom 로그 or 상태 덤프
  // ...
  // 원래 시그널 핸들러로 돌아가게 하거나, 강제 종료
  _exit(signum);
}

int main(int argc, char** argv) {
  std::signal(SIGSEGV, segv_handler);
  // ...
}
```

**Remote Debugging(원격 디버깅)**:

* 실제 로봇 기기나 임베디드 장치에서 Crash가 발생하는 경우, 디버거를 직접 연결하기 어려울 수 있다.
* GDB 서버나 LLDB 서버를 원격 장치에서 실행하고, 개발 PC에서 이를 붙여서 디버깅한다.
* 타겟 장치에서 예시:

```bash
gdbserver :1234 ros2 run my_pkg my_node
```

* 호스트 PC에서 예시:

```bash
gdb <실행파일> 
(gdb) target remote <타겟IP>:1234
```

* 이 방식으로 Crash 직전 상태, 메모리, 변수 등을 호스트 PC의 GDB 세션에서 제어 가능하다.

#### 결함 격리(Fault Isolation)

Crash가 복잡한 모듈 간 상호작용에서 비롯되었다면, 시스템 전체를 축소해 가며 어느 지점에서 결함이 유발되는지 찾는 과정이 필요하다. 이를 ‘결함 격리’라고 한다.

1. **단순화된 환경 재현**
   * 의존성이 많은 노드(예: 카메라 드라이버, SLAM, AI inference 노드 등)를 하나씩 끊고, 단순 노드만 실행했을 때 Crash가 발생하는지 확인한다.
   * Crash가 사라진다면, 제거했던 노드들 중 어느 하나와의 상호작용에서 문제가 생긴다고 가정하고, 다시 단계적으로 추가하면서 재현 여부를 확인한다.
2. **Mocking / Stubbing**
   * 실제 라이브러리 대신 모의(Mock) 라이브러리를 사용해 통신·처리를 가짜로 흉내 낸다.
   * 이를 통해 네트워크·센서 I/O 부담이 제거되면 Crash가 사라지는지, 혹은 여전히 발생하는지 비교 분석이 가능하다.
3. **노드 간 통신 차단/지연 테스트**
   * 네트워크를 의도적으로 단절하거나, 메시지 통신을 지연시켜 본다.
   * Crash가 통신 지연 상황에서만 재현된다면, QoS 설정이나 RMW 계층에서 특정 타임아웃 처리가 미흡한 것일 수 있다.

#### 프로파일링과 Crash 연관성 확인

Crash가 성능 문제(고부하, 메모리 부족 등) 때문에 발생하기도 한다. 따라서 프로파일링을 통해 시스템 리소스 사용 패턴을 관찰하는 것도 도움이 된다.

1. **CPU / 메모리 사용량 모니터링**
   * `top`, `htop`, `free -m`, `vmstat` 등을 주기적으로 확인하거나, ROS2 내부에서 노드 상태를 모니터링하는 스레드를 둬서 메모리 사용량이 일정 임계치 이상일 때 경고를 남긴다.
   * Crash 직전에 메모리 사용이 급격히 증가했다면, 힙(Heap) 메모리 할당 문제가 의심된다.
2. **ROS2 택트(Ticket) 기반 프로파일링**
   * rclcpp에서 제공하는 콜백 프로파일링 도구(예: callback\_duration 추적)를 활성화해, 콜백 실행 시간이 과도하게 길거나 스레드가 오래 블록되어 있는지 살펴본다.
   * ROS2 Tracing 패키지(`ros2_tracing`)를 사용해 노드에서 발생하는 이벤트를 기록하고, 크로노그램(chronogram) 형태로 시각화할 수 있다.

#### Crash 예방(Prevention) 전략

지금까지의 내용이 주로 Crash가 발생한 뒤 이를 분석·복구하는 방법이라면, 미연에 문제를 방지하기 위한 예방 전략 역시 중요하다. Crash가 발생할 가능성을 시스템 차원에서 줄이는 설계를 채택하면, 실제 운용 중 디버깅 부담을 크게 경감할 수 있다.

1. **에러 핸들링·예외 처리 방어적 코딩(Defensive Coding)**
   * C++ 노드에서는 `throw`가 발생할 가능성이 있는 모든 코드 구간을 명확히 파악해, 적절한 `try-catch` 블록으로 예외를 처리한다.
   * Python 노드에서도 `try-except` 구문을 광범위하게 적용하고, 예상치 못한 TypeError, ValueError 등이 언제든지 노드를 죽일 수 있음을 인지해야 한다.
   * ROS2 API 호출에서 반환값이 에러 코드이거나, `rclcpp::exceptions` 계열의 예외가 발생할 수 있음을 잊지 말고 방어적으로 처리한다.
2. **메모리 사용 최소화 및 스마트 포인터 활용**
   * C++에서는 `new` / `delete` 대신 `std::shared_ptr`, `std::unique_ptr` 등을 활용해 메모리 누수 위험을 줄인다.
   * `std::vector` 등 표준 컨테이너를 사용하되, 범위체크(예: `at()` 메서드)를 습관적으로 적용하거나 ASan, Valgrind 테스트를 주기적으로 실시한다.
   * 임베디드 환경에서는 메모리 풀(Pool)을 미리 잡아두고, 할당·해제를 관리하여 동적 할당 부하로 인한 Crash를 예방할 수 있다.
3. **QoS 설정의 일관성 유지**
   * 퍼블리셔와 서브스크라이버 사이에 QoS 매개변수(신뢰도, 내구성, 히스토리 등)가 불일치하면, DDS 계층에서 예기치 못한 동작이 일어날 수 있다.
   * 특히 `BEST_EFFORT` 신뢰 모드에서 고속 스트리밍 데이터가 유실되면, 상위 노드의 버그가 드러나 Crash로 이어지는 경우도 있다.
   * 네트워크 대역폭, 노드의 트래픽 처리 능력에 맞춰 QoS 프로필을 적절히 설계한다.
4. **정기적인 정적 분석·동적 분석**
   * 정적 분석(Static Analysis) 도구(Cppcheck, Clang-Tidy, SonarQube 등)를 CI 단계에 통합해, 위험 코드 패턴(Null Pointer Dereference, 무의미한 조건문 등)을 사전에 걸러낸다.
   * 동적 분석(ASan, TSan, Valgrind 등)은 개발 단계부터 주기적으로 수행해, 런타임 메모리 오류나 스레드 경합 문제를 초기에 잡는다.
5. **ROS2 Node 설계 분산화**
   * 단일 노드가 모든 기능을 담당하는 ‘거대(God) 노드’ 형태는 Crash 발생 시 시스템 전체가 정지되는 문제로 이어질 수 있다.
   * 기능을 여러 노드로 분할하고, 각각을 독립적으로 관리·감시함으로써 Crash 영향 범위를 최소화한다(모듈화).
   * 구독자/퍼블리셔 단위로 책임을 명확히 분할하면, Crash 디버깅 시에도 어느 노드가 문제인지를 빠르게 좁힐 수 있다.
6. **디버그 빌드와 릴리즈 빌드 분리 운영**
   * 개발 단계에서는 디버그 기호와 함께 최적화를 낮춘 빌드(-O0, -g)를 사용해 Crash 상황을 조기에 발견·분석한다.
   * 릴리즈 단계에서는 -O2 이상 최적화를 적용하더라도, 최종 배포 전 자동화된 테스트(정적·동적 분석)를 통해 Crash 위험을 최소화한다.
   * CI/CD에서 디버그 빌드와 릴리즈 빌드를 모두 돌려 보는 식으로, 릴리즈 상태에서도 예기치 못한 Crash가 없는지 지속 검증한다.
7. **ROS2 Lifecycle, Managed Node 활용**
   * ROS2의 Lifecycle 노드를 이용하면 노드의 상태(UNCONFIGURED, INACTIVE, ACTIVE, FINALIZED 등)가 명시적이므로, 초기화·종료 절차에서 발생하는 Crash를 방지하기 쉽다.
   * 모듈 간 의존성이 복잡할 경우, Lifecycle 상태 전환 시점에만 노드 간 연결을 맺고 끊도록 설계함으로써, 안정적인 시작·종료를 보장한다.
8. **하드웨어 리소스 모니터링**
   * 임베디드 시스템이나 로봇에 탑재된 기기의 CPU, 메모리, 디스크 I/O, 네트워크 사용량이 한계에 달할 경우 예측하기 어려운 Crash가 발생할 수 있다.
   * 노드 단에서 수시로 리소스를 모니터링하고, 임계치 초과 시 속도 제한(Throttle) 또는 데이터 샘플링 비율을 조정하는 식으로 부하를 완화한다.
