ES /docs

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#

  1. 2026-06-24 23:52:14 KSTRefinementService::run begin, job 1149542 load (session 73061b01b1cb31a8beea4c67975f8215175bb9d1, capture 720983)
  2. 2026-06-24 23:52:17 KST[123762.57] | video filepath not found - 679519.insv warn 다수 발생 (자식 프로세스 처리 단계)
  3. 2026-06-24 23:52:53 KST — child process가 SIGSEGV 로 종료 → ChildProcessManager::setupEventHandlers | Child process exited, ... | Child process closed error 로그 (이 cluster 의 fingerprint)
  4. 2026-06-24 23:52:53 KSTScenemapperManager::execute | error - Process killed by signal SIGSEGV (code: null) warn, RefinementService::run | end - {} error
  5. 2026-06-24 23:52:54 KSTRefinementService::terminateService | force shutdown after 10 seconds

Error Log#

Datadog Logs

text
ChildProcessManager::setupEventHandlers | Child process exited

수집된 attributes (Datadog raw):

json
{
  "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.nodeRefiner.process() 호출 도중 SIGSEGV(segmentation fault)로 비정상 종료되었다. 이는 JS 레이어에서 throw된 예외가 아니라 C++ native 코드 내부의 메모리 접근 위반이며, 부모 ChildProcessManager.setupEventHandlersexit/close 핸들러가 비정상 종료를 단순 보고한 것이다. 즉, 이 cluster 의 두 error 로그는 root cause 가 아니라 native crash 의 증상 이다. 동일 SIGSEGV 패턴이 14일 동안 24회 이상 반복되고 있어 scenemapper 라이브러리(또는 그 입력 데이터)에 만성적 안정성 결함이 존재한다.

Technical Analysis#

Code Path#

  • Entry point: RefinementService::runScenemapperManager::execute → IPC → ScenemapperProcess::execute → native Refiner.process()
  • Failure point: native module scenemapper_api.node 내부 (top JS frame 은 child_process exit handler)

부모 측 manager는 정상적으로 childProcessManager.execute('execute', params) 를 호출:

applications/agents/packages/cupix-capture-refinement-agent/src/manager/scenemapper.manager.ts:49-58typescript
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() 호출:

applications/agents/packages/cupix-capture-refinement-agent/src/process/scenemapper.process.ts:36-48typescript
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가 죽은 결과로 부모의 두 핸들러가 차례로 호출된다:

applications/agents/packages/base/src/manager/child-process.manager.ts:107-127typescript
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 쿼리:

text
service:cupixworks-capture-refinement-arm-instance @job.id:1149542 -"video filepath not found"

Job 1149542 전체 흐름 (KST):

text
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 라인 원문):

text
{
  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일 윈도우):

text
service:cupixworks-capture-refinement-arm-instance "SIGSEGV"
→ 24 logs across 2026-06-11 ~ 2026-06-24, 거의 매일 발생

Datadog 직접 링크#

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.sendMessageexecuteTimeoutMs) 부모가 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-127exit 핸들러는 현재 동일 정보를 close 핸들러와 중복 로깅한다. 두 핸들러를 하나로 합치거나, exit 만 debug 레벨로 낮춰 fingerprint 분기를 줄여야 한다 (exitclose 가 같은 비정상 종료에서 두 번 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 가 재현되는지 비교하여 아키텍처 특이 결함 여부를 분리.
  • child_process 비정상 종료의 단일 카논 로깅 표준 도입
    • 모든 agent 가 동일하게 ChildProcessManager 를 사용하므로, 비정상 종료 시 native crash signal/PID/job id/capture id 를 한 줄에 묶은 표준 error 로그 한 줄로 정착시키면 fingerprint 분리(이 cluster 처럼 같은 사건이 두 클러스터로 갈라짐)를 방지할 수 있다.

Monitoring#

추가/유지할 메트릭과 알림:

  • 서비스 SIGSEGV 발생률 (timeseries)
text
sum:logs.hits{service:cupixworks-capture-refinement-arm-instance,status:error,@signal:SIGSEGV}.as_count()
  • ChildProcessManager 비정상 종료 (timeseries)
text
sum:logs.hits{service:cupixworks-capture-refinement-arm-instance,@message:"ChildProcessManager::setupEventHandlers | Child process exited"}.as_count()
  • RefinementService 실패 종료 (run | end - {} 와 같이 빈 결과로 종료된 패턴) (timeseries)
text
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)