고아 프로세스 회수
호스트가 죽었음을 감지하기
섹션 제목: “호스트가 죽었음을 감지하기”적용 대상: 모든 플랫폼, 서로 다른 두 메커니즘으로.
| 플랫폼 | 동작 | 결과 |
|---|---|---|
| POSIX | 고아를 init으로 reparent합니다 | process.ppid가 바뀌므로, 시작 시점 값과 비교하는 것으로 충분합니다 |
| win32 | ppid를 spawn 시점에 고정하고 갱신하지 않습니다 | 그 비교는 결코 발동할 수 없고, 원래 pid에 대한 signal-0 프로브만이 답할 수 있습니다 |
ParentProcess는 플랫폼과 무관하게 두 검사를 모두 실행합니다. 각각이 저렴하고, 둘 다 실행한다는 것은 결국 어느 신호가 먼저 도착하느냐에 관한 차이를 위해 분기를 두지 않는다는 뜻입니다.
죽음으로 치는 것은 ESRCH뿐입니다. EPERM은 프로세스가 존재하지만 우리가 시그널을 보낼 대상이 아니라는 뜻이므로 — 여전히 살아 있습니다.
win32에는 잔여 거짓 음성이 하나 남습니다. OS가 죽은 부모의 pid를 재사용할 수 있고, 그러면 프로브가 새로운 프로세스를 살아 있다고 판단합니다. 이는 받아들여집니다. 대가라고는 watchdog이 조금 오래 무장 상태로 남는 것뿐이기 때문입니다.
ParentWatchdog은 그 두 검사를 폴링합니다. 종료 코드를 전혀 실행하지 못한 방식으로 죽었을 수도 있는 부모에게 어떤 협조도 요구하지 않기 위해서입니다. 타이머는 unref되어 있어, 무장한 watchdog이 그 자체로 프로세스를 살려 두는 일은 결코 없습니다.
강제 종료에서 살아남는 것: 인메모리에는 없습니다
섹션 제목: “강제 종료에서 살아남는 것: 인메모리에는 없습니다”위의 모든 메커니즘은 우리 코드가 어느 정도 실행되어야 합니다. 호스트에 대한 SIGKILL은 그중 아무것도 실행하지 않습니다.
| 메커니즘 | 무엇을 감당하는가 | 어떻게 실패하는가 |
|---|---|---|
Command의 close() |
평범한 경우: 호스트가 멈추기로 결정했을 때 | 호스트가 강제 종료되면 아예 실행되지 않습니다 |
ParentWatchdog |
호스트가 깔끔히 종료하지 못하고 죽었을 때 | 우리 폴링 타이머가 우리 프로세스에서 여전히 돌고 있어야 합니다 |
| Job Object (win32) | 프로세스 트리에서 떨어져 나간 자손 | 커널이 강제하지만, job 안에서 spawn된 것에 한합니다 |
CliRegistry (디스크) |
위의 모든 것을, 사후에 | 메모리에 아무것도 필요하지 않습니다 — 나중의 호스트가 디렉터리를 읽습니다 |
마지막 행이 CliRegistry가 메우는 틈이고, 그것이 메모리가 아니라 디스크에 있는 이유입니다. 나중의 호스트가 디렉터리를 읽어 앞선 호스트가 남긴 것을 찾아냅니다.
const registry = new CliRegistry(stateDirectory);
for (const orphan of registry.orphans()) { // 그 CLI는 여전히 실행 중이고, 그것을 spawn한 호스트는 그렇지 않습니다. console.warn('orphan:', orphan.pid, orphan.sessionId, orphan.workingDirectory);}공유 인덱스 하나가 아니라 pid당 파일 하나인데, 호스트가 동시에 실행되는 것이 정당한 일이고 — IDE는 프로젝트마다 하나씩 엽니다 — 항목별 파일 사이에는 read-modify-write 경쟁이 없기 때문입니다. 디렉터리는 호출자가 고릅니다. 호스트가 자신의 상태를 어디에 두는지는 그 호스트의 결정이지 이 라이브러리의 결정이 아닙니다.
레지스트리는 두 번째 질문에도 답합니다. 다른 호스트가 이미 그 세션을 구동하고 있는지 여부입니다. 두 CLI가 하나의 대화에 덧붙이면 히스토리가 갈라지므로, 재개하려면 이를 알아야 합니다.
생존 여부가 아니라 신원
섹션 제목: “생존 여부가 아니라 신원”모든 플랫폼에서 이것을 안전하게 만드는 것은 생존 여부가 아니라 신원입니다. 기록된 pid는 재사용 이후 무관한 프로세스로 살아 있을 수 있으므로, ProcessIdentity.isStill()은 그 프로세스의 커맨드라인에 세션 id가 여전히 나타날 것을 요구합니다 — win32에서는 Get-CimInstance Win32_Process로, 그 밖에서는 ps -o args=로 읽습니다.
tasklist가 아니라 Win32_Process.CommandLine을 쓰는 이유는, 전자가 이미지 이름만 낼 뿐 두 실행을 구별하지 못하기 때문입니다. wmic이 아니라 CIM인 이유는, wmic이 Windows에서 제거되는 중이기 때문입니다.
커맨드라인을 읽을 수 없다는 것은 죽음이 아니라 알 수 없음을 뜻합니다 — 살아 있는 프로세스에 대해서도 질의가 실패할 수 있습니다. 종료 여부를 결정하는 호출자는 알 수 없음을 “하지 말 것”으로 취급해야 합니다.
다음으로 볼 것
섹션 제목: “다음으로 볼 것”- 크로스 플랫폼 — Job Object와 프로세스 트리 비교표 전체.
- 문제 해결: 고아 프로세스 — 하나를 발견했을 때 할 일.