Skip to content

fix(pi-server): 멈춘 엔진은 닫다가 걸려도 스스로 끝나고, 업데이트는 옛 엔진이 남으면 성공이라 하지 않는다 - #30

Merged
rainstorm0907 merged 1 commit into
rubato/basefrom
fix/engine-stop-exit
Oct 23, 2026
Merged

rainstorm0907 merged 1 commit into
rubato/basefrom
fix/engine-stop-exit

Conversation

@rainstorm0907

Copy link
Copy Markdown
Collaborator

왜

10-23 15:25 업데이트가 프로필 엔진에 SIGTERM 을 보냈습니다. 옛 엔진(PID 780)은 소켓 pi.sock·pi.sock.tty 를 닫았지만 프로세스는 끝나지 않았습니다. launchd 는 살아 있는 프로세스를 도는 엔진으로 보고 새 엔진을 띄우지 않았고, 앱은 15:26~15:42 동안 40초마다 Rubato profile engine did not start under launchd 로 실패했습니다. 15:43 에 그 프로세스가 사라지고서야 launchd 가 새 엔진을 띄웠습니다.

시각 일
15:25 업데이트 → SIGTERM, 소켓 닫힘
15:31 재시작 도우미가 5분 기다리다 timeout. 업데이트는 경고 한 줄 뒤 "✓ 업데이트를 마쳤습니다"
15:35~15:41 앱의 재시작도 같은 timeout 으로 실패
15:43 옛 프로세스가 사라지고 새 엔진이 뜸

원인은 두 겹입니다.

  1. 엔진은 이벤트 루프가 비어야만 끝났습니다. cli.mjs 는 service.close() 뒤 루프가 저절로 비기를 기다렸습니다. 끝나지 않는 닫기 단계 하나, 누가 남긴 핸들 하나면 소켓 없는 프로세스가 무한히 남습니다. 옛 엔진 로그에는 종료 중 아무 줄도 없어서 이번에 무엇이 붙잡았는지는 알 수 없었습니다. e915f413d 처럼 걸리는 길을 하나씩 막는 것으로는 다음 길을 못 막습니다.
  2. 업데이트는 엔진 교체 실패를 경고로만 다뤘습니다. "옛 코드가 그대로입니다" 를 찍고도 성공으로 끝나 앱 화면엔 업데이트가 끝난 것으로 보였습니다.

무엇

  • harness/pi-server/src/engine-stop.mjs (새 파일): 멈춘 엔진은 반드시 끝납니다.
    • 닫기를 마치면 2초 동안 루프가 비기를 기다리고, 그래도 남아 있으면 무엇이 남았는지(process.getActiveResourcesInfo()) 로그에 쓰고 끝납니다. 평소처럼 루프가 비면 지금과 똑같이 저절로 끝납니다.
    • 멈춘 지 DRAIN_TIMEOUT_MS + 60s (180초) 안에 닫기를 못 마치면, 걸린 단계와 열린 자원을 로그에 쓰고 1로 끝납니다. 재시작 도우미가 소켓이 닫힌 뒤 옛 프로세스를 기다리는 5분(CLOSE_WAIT_MS) 안이라, 도우미는 정상 재시작으로 끝납니다. 시험이 두 상수의 관계를 지킵니다.
    • 외부 kill -9 는 여전히 쓰지 않습니다. 드레인은 그대로 끝까지 기다리고, 끝내는 건 엔진 자신입니다.
  • exit-guard.mjs: 호스팅된 코드의 process.exit 는 계속 막고, 엔진 자신의 종료만 exitEngine 으로 원래 exit 을 씁니다.
  • profile-server.mjs·host.mjs: 닫기가 지금 어느 단계인지(resident services → drain → terminal server → socket server → conversations → profile lock) 를 읽을 수 있게 합니다. 다음에 또 걸리면 로그가 그 단계를 말합니다.
  • cli.mjs: 시작에 실패한 엔진도 같은 이유로 2초 뒤 끝납니다(소켓 없는 실패한 시작이 남는 같은 모양).
  • rubato-update.sh: 엔진·허브 재시작이 실패해 옛 코드가 도는 채면 남은 단계는 계속하되, 마지막에 "✗ … 업데이트가 끝나지 않았습니다" 를 쓰고 1로 끝납니다. 앱 업데이트(gui-update.mjs)는 이걸 실패로 보여 줍니다. rubato restart 는 이미 그렇게 하고 있었습니다.

검증

  • 새 시험 harness/pi-server/test/engine-stop.test.mjs 7개, 업데이트 시험 1개. 진짜 cli.mjs 를 띄우고 SIGTERM 을 보내는 두 시험은 이 브랜치의 엔진 코드를 빼면 실패합니다(엔진이 15초 넘게 남음 = 이번 사고). 업데이트 시험도 이 브랜치의 rubato-update.sh 를 빼면 실패합니다("업데이트를 마쳤습니다" 출력, 종료 0).
  • 업데이트 시험 픽스처는 이제 재시작 도우미를 늘 기록용 가짜로 둡니다. harness/ 를 건드리는 업데이트는 엔진 재시작을 부르는데, 픽스처에 도우미가 없어서 스킬 시험 둘이 "재시작 실패" 경고를 지나가고 있었습니다. 이제 그 실패가 업데이트 실패라서 드러났습니다.
  • pi-server 전체 시험: base 와 이 브랜치의 실패 목록이 이름까지 같습니다(fork 2개, attach 1개; base 에서만 재시작 타이밍 1개가 한 번 더 실패). rubato-update.test.mjs 의 scheduler 시험 1개, rubato-restart.test.mjs 의 20개도 base 에서 그대로 실패하는 기존 실패입니다.

업데이트가 보낸 SIGTERM 뒤 옛 엔진이 소켓은 닫았는데 프로세스가 끝나지 않았다. launchd 는 살아 있는
프로세스를 도는 엔진으로 봐서 새 엔진을 띄우지 않았고, 앱은 17분 동안 엔진을 찾지 못했다(10-23). 엔진은
이벤트 루프가 비어야만 끝났기 때문에, 끝나지 않는 닫기 단계나 누가 남긴 핸들 하나가 프로세스를 붙잡았다.

- 엔진은 멈출 때 닫기를 마치면 2초 안에 끝나고, 그때 붙잡고 있던 것을 로그에 남긴다. 멈춘 지
  드레인+60초(180초) 안에 닫기를 못 마치면 걸린 단계를 남기고 끝난다. 재시작이 옛 프로세스를 기다리는
  5분 안이라 재시작은 정상으로 끝난다. 시작에 실패한 엔진도 같은 식으로 끝난다.
- 업데이트는 엔진·허브 재시작이 실패해 옛 코드가 도는 채면 마지막에 "업데이트를 마쳤습니다" 대신
  끝나지 않았다고 말하고 1로 끝난다. 앱 업데이트 화면도 실패로 보인다.
@rainstorm0907
rainstorm0907 merged commit 49c96fc into rubato/base Oct 23, 2026
0 of 3 checks passed
@rainstorm0907
rainstorm0907 deleted the fix/engine-stop-exit branch October 23, 2026 07:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant