마인크래프트에서 자원을 수집하는 에이전트를 학습하기 위해 MineRL 환경을 구성했다. Dreamer V3 모델을 작성하기 전에 env.reset()이 반복해서 실패해 실행 환경과 프로세스 관리 코드를 먼저 분석했다.
env.reset()에서 100% 실패한다
WSL 환경을 한 번 재구성했더니 그 뒤로 env.reset()이 무조건 실패하기 시작했다.
SystemExit: Unable to acquire PID file.
전에는 가끔 실패하고 가끔 성공했다. 환경 재구축 뒤로는 100% 실패했다. 간헐적이던 문제가 확정적으로 변했다는 건 보통 운 좋게 버티던 조건이 사라졌다는 뜻이다.

컨테이너 재시작 후 재사용된 PID와 호스트 마운트에 남은 stale PID 파일이 충돌했다.
daemoniker의 PID 파일 처리 방식
MineRL은 마인크래프트 자바 프로세스를 watcher로 띄운다. 이 watcher가 daemoniker라는 라이브러리로 PID 파일을 잡는데, 소스를 끝까지 따라가니까 이런 코드가 있었다.
# daemoniker/_daemonize_common.py
# "as this is Windows, we cannot really lock it.
# Assume existence is equivalent to locking"
if os.path.isfile(pidfile):
raise SystemExit('Unable to acquire PID file.')
fcntl로 진짜 락을 거는 게 아니라 파일이 있냐 없냐만 본다. 윈도우 호환 때문에 "파일 존재 = 락 잡힘"으로 쳐버리는 것이다. 이게 문제의 시작이었다.
PID 파일 경로는 logs/minerl_watchers/watcher_<부모PID>-<자식PID>.pid다. 그리고 이 logs 디렉터리가 하필 호스트 볼륨 마운트(/workspace) 안에 있었다. 그러면 컨테이너가 죽어도 PID 파일이 호스트에 그대로 남는다.
컨테이너를 다시 시작하면 내부 프로세스에 1, 22, 25처럼 비슷한 PID가 재할당됐다. 따라서 같은 이름의 PID 파일을 만들려고 할 때 호스트 볼륨에 남아 있던 파일과 충돌했다. daemoniker는 파일 존재를 잠금 상태로 판단해 실행을 종료했다.
전에 가끔 됐던 건 컨테이너마다 PID 분포가 미묘하게 달라서 우연히 안 겹친 것이었고, 환경을 깨끗하게 다시 만든 뒤로는 그 우연이 사라졌다.
entrypoint에서 stale PID 정리
컨테이너가 시작될 때 이전 실행에서 남은 PID 파일을 삭제하도록 entrypoint에 정리 절차를 추가했다.
# docker/xvfb-entrypoint.sh
if [ -d /workspace/logs/minerl_watchers ]; then
find /workspace/logs/minerl_watchers -maxdepth 1 -name '*.pid' -delete
fi
.log 파일은 나중에 디버깅할 때 보려고 남겼다. 디렉터리 없을 때(CI 같은 데)는 건너뛰게 가드도 걸었다.
확인은 이렇게 했다. stale 파일을 일부러 만들어두면 에러가 바로 재현됐고, 지우고 다시 부르면 통과했다. 30개쯤 깔아두고 컨테이너 띄우니까 0개로 정리되고 env.reset()이 두 번 연속 성공했다.
$ docker compose run --rm minerl python3 scripts/eval_ppo_treechop.py --episodes 1 --seed 1337
episode 1/1 reward=0.000 steps=6000
OK — 1 episodes reward_mean=0.000 steps/s=48.09
reward가 0인 것은 이 검증의 실패를 의미하지 않는다. shaping 보상으로 학습한 모델을 raw 보상으로 평가했기 때문이다. 이 단계의 목표는 환경이 종료까지 안정적으로 실행되는지 확인하는 것이었다. 6,000스텝 롤아웃과 manifest 생성이 완료돼 인프라 경로가 정상임을 확인했다.
학습 전에 실행 환경을 검증해야 했던 이유
학습을 시작하기 전에 Minecraft Java 프로세스, Docker bind mount와 라이브러리의 Windows 호환 코드부터 확인해야 했다. 강화학습 실험에서도 모델과 보상 설계에 앞서 실행 환경이 반복적으로 초기화되는지 검증하는 절차가 필요했다.
간헐적이던 오류가 환경 재구성 후 매번 발생한 변화는 재사용되는 PID와 영속 볼륨을 원인 후보로 좁히는 단서가 됐다.
참고