ChildProcessManager::setupEventHandlers | Child process exited
RCA: ChildProcessManager — Child process exited (SIGSEGV in scenemapper native lib)
Overview#
What Happened#
2026-06-24 23:52 KST에 cupixworks-capture-refinement-arm-instance 서비스가 capture refinement job 1149542를 실행하던 중, 자식 프로세스로 분리된 scenemapper.process가 native C++ 라이브러리(scenemapper_api.node) 내에서 SIGSEGV로 강제 종료되었다. 부모의 ChildProcessManager는 child의 exit/close 이벤트를 받아 두 줄의 error 로그를 남겼고, refinement job은 그대로 실패 처리되었다. 동일 패턴은 14일 윈도우에서 24회 이상 반복되고 있는 만성 결함이다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | Error |
| exception.message | Process killed by signal SIGSEGV (code: null) |
| top_frame | /tmp/agent/dist/app.cjs:5307:33 (native module crash) |
| signal | SIGSEGV |
| host | 7fac00a01d46 (ARM container) |
| env | production, us-west-2 |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| clark-vdc (team_id 87) | 1 (this cluster) | capture 720983 의 refinement job 1149542 실패. 사용자 clara.moschella@clarkconstruction.com 의 캡처 처리 지연 |
| 전체 서비스 (모든 팀) | 24+ in 14d | scenemapper SIGSEGV 가 반복적으로 capture refinement job 을 실패시키는 만성 이슈 |
Timeline#
- 2026-06-24 23:52:14 KST —
RefinementService::runbegin, job 1149542 load (session73061b01b1cb31a8beea4c67975f8215175bb9d1, capture 720983) - 2026-06-24 23:52:17 KST —
[123762.57] | video filepath not found - 679519.insvwarn 다수 발생 (자식 프로세스 처리 단계) - 2026-06-24 23:52:53 KST — child process가 SIGSEGV 로 종료 →
ChildProcessManager::setupEventHandlers | Child process exited,... | Child process closederror 로그 (이 cluster 의 fingerprint) - 2026-06-24 23:52:53 KST —
ScenemapperManager::execute | error - Process killed by signal SIGSEGV (code: null)warn,RefinementService::run | end - {}error - 2026-06-24 23:52:54 KST —
RefinementService::terminateService | force shutdown after 10 seconds
Error Log#
ChildProcessManager::setupEventHandlers | Child process exited
수집된 attributes (Datadog raw):
{
"message": "ChildProcessManager::setupEventHandlers | Child process exited",
"status": "error",
"signal": "SIGSEGV",
"job": { "id": 1149542 },
"capture": { "id": 720983 },
"session": { "id": "73061b01b1cb31a8beea4c67975f8215175bb9d1" },
"team": { "domain": "clark-vdc", "id": 87 },
"host": { "name": "7fac00a01d46" },
"region": "us-west-2",
"environment": "production"
}
Impact#
- Service:
cupixworks-capture-refinement-arm-instance - Team: clark-vdc
- 발생 횟수: 1 (이 fingerprint), 그러나 동일 root cause(SIGSEGV in scenemapper) 14일 누적 24회 이상
- 최초 발생: 2026-06-24 23:52 KST
- 최근 발생: 2026-06-24 23:52 KST
Root Cause Summary#
scenemapper.process 자식이 native addon scenemapper_api.node 의 Refiner.process() 호출 도중 SIGSEGV(segmentation fault)로 비정상 종료되었다. 이는 JS 레이어에서 throw된 예외가 아니라 C++ native 코드 내부의 메모리 접근 위반이며, 부모 ChildProcessManager.setupEventHandlers 의 exit/close 핸들러가 비정상 종료를 단순 보고한 것이다. 즉, 이 cluster 의 두 error 로그는 root cause 가 아니라 native crash 의 증상 이다. 동일 SIGSEGV 패턴이 14일 동안 24회 이상 반복되고 있어 scenemapper 라이브러리(또는 그 입력 데이터)에 만성적 안정성 결함이 존재한다.
Technical Analysis#
Code Path#
- Entry point:
RefinementService::run→ScenemapperManager::execute→ IPC →ScenemapperProcess::execute→ nativeRefiner.process() - Failure point: native module
scenemapper_api.node내부 (top JS frame 은 child_process exit handler)
부모 측 manager는 정상적으로 childProcessManager.execute('execute', params) 를 호출:
execute = async (params: RefinerParams): Promise<void> => {
await this.ensureInitialized();
try {
await this.childProcessManager.execute('execute', params);
} catch (error) {
logger.warn('ScenemapperManager::execute | error - %s', error);
if (this.setJobErrorCode) this.setJobErrorCode(ErrorCode.ScenemapperUtils.RefinerExecute);
throw error;
}
};
자식 프로세스에서 native lib 를 require 하고 즉시 process() 호출:
private execute = (params: ExecuteMessage): void => {
this.log(`ScenemapperProcess::execute | params: ${JSON.stringify(params)}`);
if (!this.libPath || !params?.refinerParamsFilepath) {
throw new Error('ScenemapperProcess::execute | Required parameters missing');
}
// eslint-disable-next-line @typescript-eslint/no-require-imports
const ScenemapperApi = require(this.libPath);
const Refiner = new ScenemapperApi.refiner();
Refiner.load_params(params.refinerParamsFilepath);
Refiner.process(); // <-- native crash (SIGSEGV) 가 여기서 발생
};
native crash 는 JS try/catch 로 잡을 수 없으며, child가 죽은 결과로 부모의 두 핸들러가 차례로 호출된다:
this.process.on('close', (code: number | null, signal: string | null) => {
logger.error('ChildProcessManager::setupEventHandlers | Child process closed', {
code,
signal
});
const errorMsg = signal
? `Process killed by signal ${signal} (code: ${code})`
: `Process exited with code ${code}`;
this.rejectAllPending(new Error(errorMsg));
this.process = undefined;
this.emit('close', code, signal);
});
this.process.on('exit', (code: number | null, signal: string | null) => {
logger.error('ChildProcessManager::setupEventHandlers | Child process exited', {
code,
signal
});
});
기대 동작: child가 정상 종료(code 0)되면 위 로그가 발생하지 않거나, 비정상 종료 시 error 레벨로 단 한 줄(또는 의미 있는 컨텍스트 포함)만 남는다. 실제 동작: signal=SIGSEGV로 종료 → exit + close 두 핸들러 모두 error 로 로그 → fingerprint 가 두 개로 갈라지고, 두 로그 모두 native crash 의 stack trace 나 입력 컨텍스트(refinerParamsFilepath, capture id, job id) 를 포함하지 않는다.
Log Evidence#
Datadog 쿼리:
service:cupixworks-capture-refinement-arm-instance @job.id:1149542 -"video filepath not found"
Job 1149542 전체 흐름 (KST):
2026-06-24 23:52:14 info RefinementService::run | begin
2026-06-24 23:52:14 info JobManager::loadJob | begin - job id: 1149542
2026-06-24 23:52:14 info JobManager::loadJob | end - job id: 1149542
2026-06-24 23:52:53 error ChildProcessManager::setupEventHandlers | Child process exited ← cluster
2026-06-24 23:52:53 error ChildProcessManager::setupEventHandlers | Child process closed
2026-06-24 23:52:53 warn ScenemapperManager::execute | error - Process killed by signal SIGSEGV (code: null)
2026-06-24 23:52:53 error RefinementService::run | end - {}
2026-06-24 23:52:54 info RefinementService::terminateService | force shutdown after 10 seconds
부모가 수신한 error 객체 (ScenemapperManager::execute warn 라인 원문):
{
name: 'Error',
message: 'Process killed by signal SIGSEGV (code: null)',
stack: 'Error: Process killed by signal SIGSEGV (code: null)
at ChildProcess.<anonymous> (/tmp/agent/dist/app.cjs:5307:33)
at ChildProcess.emit (node:events:524:28)
at ChildProcess.emit (node:domain:489:12)
at maybeClose (node:internal/child_process:1104:16)
at ChildProcess._handle.onexit (node:internal/child_process:304:5)'
}
스택은 부모의 child_process exit handler 만 가리키며, native 코드 안의 crash site 정보는 없다. 즉 SIGSEGV 의 정확한 원인(어떤 입력에서 어떤 함수가 죽었는지)을 로그만으로는 식별할 수 없다.
재발 빈도 (14일 윈도우):
service:cupixworks-capture-refinement-arm-instance "SIGSEGV"
→ 24 logs across 2026-06-11 ~ 2026-06-24, 거의 매일 발생
Datadog 직접 링크#
- 이 cluster: 위 Datadog Logs 참고
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | 자식 프로세스 startup 실패 (script not found, fork error 등) | — | 실제 IPC 핸드셰이크 후 `JobManager::loadJob | end등 정상 진행 로그가 있고RefinementService::run |
| H2 | IPC 메시지 타임아웃으로 인한 SIGKILL (ChildProcessManager.sendMessage 의 executeTimeoutMs) |
부모가 timeout 시 process.kill('SIGKILL') 을 호출함 |
error 메시지가 SIGSEGV 이지 SIGKILL 이 아님. 14일 데이터 모두 SIGSEGV. timeout 로그 (Execution timeout after ...ms) 도 미발견 |
Rejected |
| H3 | Out-of-memory(OOM)에 의한 컨테이너 kill | OOM 도 native crash 와 비슷한 시점에 발생 가능 | OOM kill signal 은 일반적으로 SIGKILL/SIGTERM. SIGSEGV 는 메모리 접근 위반에 대한 커널 시그널이며 OOM killer 와 다름. 또한 host 메모리 부족 관련 로그/메트릭 단서 없음 | Rejected (uncertain — needs verification with container metrics) |
| H4 | 입력 데이터 결함으로 native scenemapper 코드가 잘못된 포인터/배열에 접근 | 같은 세션에서 `[123762.57] | video filepath not found - 679519.insv` warn 이 17~24번 반복됨 → 부모 capture 의 일부 video 자산이 누락된 상태. native lib 가 누락 입력에 대한 방어 코드를 갖지 못해 segfault 가능 | 직접 증명할 native stack trace 없음 — 증거는 정황적 |
| H5 | scenemapper native 라이브러리 자체 결함 (회귀, 빌드 호환성, ARM 아키텍처 이슈) | 14일 동안 24+ 회 동일 패턴 SIGSEGV 반복. 호스트가 ARM 인스턴스 변종 (*-arm-instance) 으로 아키텍처 특이 결함 가능 |
동일 input 가 어떤 capture 에서는 성공하는지 확인 못함 | Confirmed (이 cluster 의 즉시 root cause — JS 레이어가 아닌 native lib 의 SIGSEGV) |
Fix Recommendation#
즉시 조치 (Critical)#
- native crash 의 디버깅 컨텍스트 캡처 향상
applications/agents/packages/base/src/manager/child-process.manager.ts:122-127의exit핸들러는 현재 동일 정보를close핸들러와 중복 로깅한다. 두 핸들러를 하나로 합치거나,exit만 debug 레벨로 낮춰 fingerprint 분기를 줄여야 한다 (exit와close가 같은 비정상 종료에서 두 번 error 로그를 남기는 게 cluster 가 두 개 생성되는 원인).- signal 이 비어 있는 정상 종료(code 0) 인 경우는 error 가 아니라 debug/info 로 로깅하도록 분기 추가 권장.
- ARM 인스턴스에서의 core dump 수집 설정
cupixworks-capture-refinement-arm-instance호스트 (예:7fac00a01d46)에서ulimit -c unlimited와/proc/sys/kernel/core_pattern설정으로 core dump 수집을 활성화하면, 다음 SIGSEGV 발생 시 native stack trace 를 분석할 수 있다 (uncertain — needs verification with platform team).
단기 개선 (1주 이내)#
ScenemapperProcess::execute입력 검증 추가applications/agents/packages/cupix-capture-refinement-agent/src/process/scenemapper.process.ts:36-48에서Refiner.load_params/Refiner.process()호출 전,refinerParamsFilepath의 존재 + JSON 파싱 + 핵심 필드(특히 video/insv 자산 경로) 가능 여부를 미리 검사하고, 실패 시 child 가 정상적으로 throw 하도록 가드를 둔다. native lib 가 결함 입력에 segfault 한다는 가설(H4)에 대한 1차 방어선.
- 상위 retry/quarantine 정책 도입
RefinementService::run레벨에서 SIGSEGV 로 실패한 job 을 즉시 retry 하지 말고, 같은 input 으로 N 번 연속 SIGSEGV 시 quarantine 처리 (재진입 시도 회피). 같은 capture 가 무한 재시도 루프에 들어가는 것을 방지.
장기 개선 (재발 방지)#
- scenemapper native 라이브러리 안정화
scenemapper_api.node의 빌드를 ASAN/UBSAN 으로 감싼 디버그 빌드를 staging 환경에 배포하고, 동일 입력으로 reproduction 시도. native 코드 소유 팀(scenemapper 라이브러리 ownership) 이 14일 24회 SIGSEGV 데이터셋(capture id 들) 으로 회귀 테스트 코퍼스를 구성한다.
- ARM vs x86 회귀 비교
- 동일 capture 를
*-arm-instance가 아닌 x86 변종에서 처리했을 때도 segfault 가 재현되는지 비교하여 아키텍처 특이 결함 여부를 분리.
- 동일 capture 를
- child_process 비정상 종료의 단일 카논 로깅 표준 도입
- 모든 agent 가 동일하게
ChildProcessManager를 사용하므로, 비정상 종료 시 native crash signal/PID/job id/capture id 를 한 줄에 묶은 표준 error 로그 한 줄로 정착시키면 fingerprint 분리(이 cluster 처럼 같은 사건이 두 클러스터로 갈라짐)를 방지할 수 있다.
- 모든 agent 가 동일하게
Monitoring#
추가/유지할 메트릭과 알림:
- 서비스 SIGSEGV 발생률 (timeseries)
sum:logs.hits{service:cupixworks-capture-refinement-arm-instance,status:error,@signal:SIGSEGV}.as_count()
- ChildProcessManager 비정상 종료 (timeseries)
sum:logs.hits{service:cupixworks-capture-refinement-arm-instance,@message:"ChildProcessManager::setupEventHandlers | Child process exited"}.as_count()
- RefinementService 실패 종료 (
run | end - {}와 같이 빈 결과로 종료된 패턴) (timeseries)
sum:logs.hits{service:cupixworks-capture-refinement-arm-instance,@message:"RefinementService::run | end - {}"}.as_count()
알림: 동일 capture/job 으로 SIGSEGV 가 24h 내 3회 이상 발생 시 quarantine 알림 (단기 개선의 retry 정책과 연동).
Risk Assessment#
- Risk level: medium (개별 capture 처리 실패는 비치명적이나 14일 24+ 회 누적은 SLO 영향 가능)
- 예상 복잡도: critical (native C++ 라이브러리 디버깅이 필요한 영역; JS 측 가드/로깅 정비는 standard, native 안정화는 critical)