콘텐츠로 이동

고아 프로세스 회수

적용 대상: 모든 플랫폼, 서로 다른 두 메커니즘으로.

플랫폼 동작 결과
POSIX 고아를 init으로 reparent합니다 process.ppid바뀌므로, 시작 시점 값과 비교하는 것으로 충분합니다
win32 ppid를 spawn 시점에 고정하고 갱신하지 않습니다 그 비교는 결코 발동할 수 없고, 원래 pid에 대한 signal-0 프로브만이 답할 수 있습니다

ParentProcess는 플랫폼과 무관하게 두 검사를 모두 실행합니다. 각각이 저렴하고, 둘 다 실행한다는 것은 결국 어느 신호가 먼저 도착하느냐에 관한 차이를 위해 분기를 두지 않는다는 뜻입니다.

죽음으로 치는 것은 ESRCH뿐입니다. EPERM은 프로세스가 존재하지만 우리가 시그널을 보낼 대상이 아니라는 뜻이므로 — 여전히 살아 있습니다.

win32에는 잔여 거짓 음성이 하나 남습니다. OS가 죽은 부모의 pid를 재사용할 수 있고, 그러면 프로브가 새로운 프로세스를 살아 있다고 판단합니다. 이는 받아들여집니다. 대가라고는 watchdog이 조금 오래 무장 상태로 남는 것뿐이기 때문입니다.

ParentWatchdog은 그 두 검사를 폴링합니다. 종료 코드를 전혀 실행하지 못한 방식으로 죽었을 수도 있는 부모에게 어떤 협조도 요구하지 않기 위해서입니다. 타이머는 unref되어 있어, 무장한 watchdog이 그 자체로 프로세스를 살려 두는 일은 결코 없습니다.

강제 종료에서 살아남는 것: 인메모리에는 없습니다

섹션 제목: “강제 종료에서 살아남는 것: 인메모리에는 없습니다”

위의 모든 메커니즘은 우리 코드가 어느 정도 실행되어야 합니다. 호스트에 대한 SIGKILL은 그중 아무것도 실행하지 않습니다.

메커니즘 무엇을 감당하는가 어떻게 실패하는가
Commandclose() 평범한 경우: 호스트가 멈추기로 결정했을 때 호스트가 강제 종료되면 아예 실행되지 않습니다
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에서 제거되는 중이기 때문입니다.

커맨드라인을 읽을 수 없다는 것은 죽음이 아니라 알 수 없음을 뜻합니다 — 살아 있는 프로세스에 대해서도 질의가 실패할 수 있습니다. 종료 여부를 결정하는 호출자는 알 수 없음을 “하지 말 것”으로 취급해야 합니다.