PixGenieProcess::execute | failed - {"code":"ABORT_ERR","name":"AbortError"}
RCA: PixGenieProcess::execute | failed - AbortError
Overview#
What Happened#
2026-07-02 22:52 KST 에 cupixworks-pano-postprocessor-instance 서비스에서 tenant cana capture 724274 를 처리하던 4개의 인스턴스가 동시에 PixGenieProcess::execute 단계에서 AbortError(code ABORT_ERR) 로 실패했다. 원인은 PixGenieProcess 내부의 하드코딩된 2 시간(TIMEOUT_MS = 2 * 60 * 60 * 1000) AbortSignal.timeout 이 만료되어 자식 Python 프로세스에 SIGTERM 을 발송했기 때문이다. 4건 모두 동일 capture 를 처리 중이던 서로 다른 호스트에서 발생했으며, 결과적으로 해당 capture 의 PixGenie 전처리가 완료되지 못했다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | AbortError |
| exception.message | The operation was aborted (cause: Error: Process timeout) |
| top_frame | packages/cupix-pix-genie-preprocessor-agent/src/process/pixgenie.process.ts:60 |
| runtime | Node.js child_process (spawn python3), pixgenieutil 0.3.3_37465c243f1f9e71dd65bb521d8347b53e0424c0 on Ubuntu 22.04.5 |
| env | production, us-west-2 |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
cana (tenant cupix) |
4 | Capture 724274 의 PixGenie 전처리 실패 — pix_genie_state 가 error 로 마킹되어 후속 3D 파이프라인 단계가 진행되지 않음 |
Timeline#
- 2026-07-02 20:52:33 KST —
PixGenieProcess::execute | begin - captureId: 724274(4개 인스턴스가 동시에 시작; UTC 11:52:33) - 2026-07-02 20:52:39 KST —
pixgenieutil version: 0.3.3_...— Python 프로세스 정상 기동 - 2026-07-02 20:52:44 KST — clusters/panos/pointclouds 진행 로그 확인 (Python 정상 처리 중)
- 2026-07-02 22:52:33 KST — 정확히 2 시간 경과,
AbortSignal.timeout만료 →_abortController.abort(new Error('Process timeout'))호출 - 2026-07-02 22:52:33 KST — Python 자식 프로세스가 SIGTERM(15) 을 수신, g3log FATAL SIGNAL 스택덤프 남김
- 2026-07-02 22:52:33 KST — Node.js
abortChildProcess가 spawn Promise 를AbortError로 reject →PixGenieProcess::execute | failed - {"code":"ABORT_ERR","name":"AbortError"}(Datadog 에 기록된 대표 에러) - 2026-07-02 22:52:33 KST —
PreprocessorService::runcatch 블록이PixGenieState.Error로 상태 업데이트,FileSystemManager::cleanupWorkspace실행 - 2026-07-02 22:52:34 KST —
PreprocessorService::run | end - {"stack":"AbortError: The operation was aborted...","cause":{"message":"Process timeout"...}}상세 스택 로깅
Error Log#
PixGenieProcess::execute | failed - {"code":"ABORT_ERR","name":"AbortError"}
PreprocessorService::run 스택 (동일 시점의 상위 catch 로그) 원문:
{
"stack": "AbortError: The operation was aborted\n at abortChildProcess (node:child_process:725:27)\n at EventTarget.onAbortListener (node:child_process:795:7)\n ...\n at AbortController.abort (node:internal/abort_controller:392:5)\n at timeoutSignal.addEventListener.once (/tmp/agent/dist/app.cjs:6613:31)",
"message": "The operation was aborted",
"cause": {
"stack": "Error: Process timeout\n at timeoutSignal.addEventListener.once (/tmp/agent/dist/app.cjs:6613:37)\n at Timeout._onTimeout (node:internal/abort_controller:127:7)",
"message": "Process timeout",
"name": "Error"
},
"code": "ABORT_ERR",
"name": "AbortError"
}
Impact#
- Service:
cupixworks-pano-postprocessor-instance - Team: cana
- 발생 횟수: 4
- 최초 발생: 2026-07-02 22:52 KST
- 최근 발생: 2026-07-02 22:52 KST
Capture 724274 (3D Map [03:36 PM], job 1169499, user craig.corder@cana.ca) 의 PixGenie 전처리가 완료되지 않아 capture.pix_genie_state = error 상태가 되었고, 이후 pipeline 단계(pix genie 결과 기반 후속 처리)가 자동 진행되지 못한다. 4건은 각각 다른 호스트(e68348869482, 94ae64901b2b, ...)에서 동일 capture 를 처리하고 있던 병렬/재시도 인스턴스로, 사용자 노출 규모는 단일 capture 단위다.
Root Cause Summary#
PixGenieProcess 는 Python 자식 프로세스를 spawn 하면서 AbortSignal.timeout(2 * 60 * 60 * 1000) 로 2 시간 하드 타임아웃을 건다. Capture 724274 의 pixgenie 처리가 이 2 시간 한계를 초과하자 timeoutSignal 이 발화하여 _abortController.abort(new Error('Process timeout')) 를 호출했고, Node.js 가 자식 프로세스에 SIGTERM 을 보내면서 spawn 의 Promise 가 AbortError(code ABORT_ERR) 로 reject 되었다. 즉 에러의 직접 원인은 "capture 의 실제 처리 시간이 하드코딩된 2 시간 타임아웃보다 길었다" 이며, 로그 메시지가 {"code":"ABORT_ERR","name":"AbortError"} 로만 나타난 것은 catch 블록에서 JSON.stringify(error) 를 사용하여 non-enumerable 한 message/stack/cause 필드가 직렬화 결과에서 누락된 부수적 결과다.
Technical Analysis#
Code Path#
- Entry:
preprocessor-service.ts:66(PreprocessorService::run) → capture 로드 후 pixgenie 실행 - 처리 단계:
preprocessor-service.ts:93→PixGenieManager::executePixGenieProcessing - Manager 위임:
pix-genie-manager.ts:75→_pixGenieProcess.execute(processParams) - Timeout 설정 지점:
pixgenie.process.ts:56-61(Failure point) - Reject 경로:
pixgenie.process.ts:69(spawn(..., { signal })— abort 시 Node.js 가 자동으로 SIGTERM 발송 후 Promise reject) - 로그 직렬화 지점 (관찰성 이슈):
pixgenie.process.ts:37
핵심 타임아웃 로직:
private runPythonProcess(params: PixGenieProcessParams): Promise<void> {
return new Promise((resolve, reject) => {
this._abortController = new AbortController();
const timeoutSignal = AbortSignal.timeout(this.TIMEOUT_MS);
timeoutSignal.addEventListener('abort', () => {
logger.warn('PixGenieProcess | process timeout, aborting...');
this._abortController!.abort(new Error('Process timeout'));
}, { once: true });
// ...
this._process = spawn('python3', [PixGenieProcess.PIXGENIE_SCRIPT_PATH], {
env: { ...process.env, ...this.buildEnvironment(params) },
stdio: ['ignore', 'pipe', 'pipe'],
cwd: params.workspaceDir,
signal: this._abortController.signal
});
// ...
하드코딩된 2 시간 상수:
private readonly TIMEOUT_MS = 2 * 60 * 60 * 1000; // 2 hours
에러 catch 및 직렬화 (관찰성 문제 지점):
} catch (error) {
logger.error('PixGenieProcess::execute | failed - %s', JSON.stringify(error));
throw error;
}
AbortError 의 message/stack/cause 는 non-enumerable 프로퍼티이므로 JSON.stringify 결과는 {"code":"ABORT_ERR","name":"AbortError"} 로 축약된다. 상위 PreprocessorService::run 은 stringifyError(error) 로 전체 정보(cause 포함)를 남기므로, 실제 원인(Process timeout)은 상위 로그에서만 확인 가능하다.
기대 동작: 자식 프로세스가 정상 시간 내(수 분~수십 분) 완료되고 close 이벤트에서 exit code 0 으로 resolve.
실제 동작: capture 724274 처리 도중 정확히 2 시간 시점에 timeoutSignal 발화 → AbortController.abort() → Node child_process 가 SIGTERM 발송 → Python 프로세스 Received fatal signal: SIGTERM(15) → spawn Promise 가 AbortError 로 reject.
Log Evidence#
Datadog 쿼리 (원본 클러스터 URL 로부터 재현):
service:cupixworks-pano-postprocessor-instance status:error @environment:production "PixGenieProcess::execute"
Begin 로그 (2 시간 전, UTC 11:52:33 = KST 20:52:33) — 4개 호스트 동시 시작:
[2026-07-02 20:52:33 KST] info PixGenieProcess::execute | begin - captureId: 724274
[2026-07-02 20:52:33 KST] info PixGenieProcess::execute | begin - captureId: 724274
[2026-07-02 20:52:33 KST] info PixGenieProcess::execute | begin - captureId: 724274
[2026-07-02 20:52:33 KST] info PixGenieProcess::execute | begin - captureId: 724274
정확히 2 시간 뒤 SIGTERM 수신 (Python 자식 프로세스 stderr 원문):
PixGenieProcess | STDERR: ******* FATAL SIGNAL *******
file: , function: , Received fatal signal: SIGTERM(15) PID: 33
***** SIGNAL SIGTERM(15)
******* STACKDUMP *******
stack dump [1] /tmp/lib/pixgenie/pixgenie/lib/pixgenieutil-x64-ubuntu22.04-gcc-release/libg3log.so+0x15d5b
stack dump [2] /usr/lib/x86_64-linux-gnu/libc.so.6+0x42520
...
동일 시점 Node 측 rejection stack (cause 포함):
AbortError: The operation was aborted
at abortChildProcess (node:child_process:725:27)
at EventTarget.onAbortListener (node:child_process:795:7)
at AbortController.abort (node:internal/abort_controller:392:5)
at timeoutSignal.addEventListener.once (/tmp/agent/dist/app.cjs:6613:31)
cause:
Error: Process timeout
at timeoutSignal.addEventListener.once (/tmp/agent/dist/app.cjs:6613:37)
at Timeout._onTimeout (node:internal/abort_controller:127:7)
카운트 검증: first_seen (UTC 13:52:33.675) - begin 로그 (UTC 11:52:33.662) ≈ 7,200,013 ms → TIMEOUT_MS = 7,200,000 ms 와 일치 (오차 ~13 ms).
호스트 분포 (raw 조회 결과):
e6834886948294ae64901b2b- (그 외 2개 호스트, 동일 session
aad1dbcf2383f3a8186db9c1ba78e1dcf0fc5a2d)
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | AbortSignal.timeout(2h) 이 만료되어 Node 가 Python 자식 프로세스에 SIGTERM 을 발송, spawn Promise 가 AbortError 로 reject |
begin(11:52:33 UTC) 과 error(13:52:33 UTC) 의 간격이 정확히 2 시간; 상위 로그의 cause: { message: "Process timeout" }; Python 프로세스 stderr 의 Received fatal signal: SIGTERM(15) 스택덤프; pixgenie.process.ts:60 의 abort(new Error('Process timeout')) |
— | Confirmed |
| H2 | Python 프로세스 자체가 크래시 또는 exit code ≠ 0 로 종료 | — | 정상 exit 시 코드 경로는 pixgenie.process.ts:111 의 PixGenie process failed with exit code ... 로 reject 되어야 하며 그 메시지가 없음. 대신 AbortError(code: ABORT_ERR) 가 발생 |
Rejected |
| H3 | 상위 서비스의 terminateService (10 분 forced shutdown) 가 발화하여 프로세스가 중단됨 |
`terminateService | force shutdown after 600 seconds` 로그가 존재 | 해당 로그는 에러 발생 이후 (`PreprocessorService::run |
| H4 | AWS S3 업로드 또는 API 호출의 별도 타임아웃(UPLOAD_TIMEOUT_MS = 600_000) 이 발화 |
— | 스택 최상단이 abortChildProcess → timeoutSignal.addEventListener.once 로 Python spawn 의 timeoutSignal 임이 명확; S3 업로드는 verifyOutput 이후 단계라 도달 자체를 못함 |
Rejected |
| H5 | AbortError 로그 메시지가 {"code":"ABORT_ERR","name":"AbortError"} 로만 표기되는 것은 별도 버그(정보 유실) |
pixgenie.process.ts:37 은 JSON.stringify(error) 를 사용; Error 의 message/stack/cause 는 non-enumerable |
상위 PreprocessorService::run 은 stringifyError 를 사용해 완전한 stack/cause 를 남기므로 총 정보 손실은 없음 |
Confirmed (secondary — 관찰성 개선 필요) |
Fix Recommendation#
즉시 조치 (Critical)#
- 근본 대응 방향은 capture 724274 처리 시간이 왜 2 시간을 초과했는지 확인하는 것이다. Python 프로세스는
Lift2dSegmentations::execute() end. elapsed: 1113.54등 개별 단계가 여전히 진행 중인 상태에서 SIGTERM 을 받았으므로 hang 이 아니라 실제 장시간 처리로 보인다. capture 724274 의 규모(panos 19개, segments 다수) 와 유사 capture 의 P95 소요 시간을 pixgenie util 로그(elapsed:값) 로 확인해 2 시간 상한이 현실적인지 재평가한다. - 파일 위치:
packages/cupix-pix-genie-preprocessor-agent/src/process/pixgenie.process.ts:18(TIMEOUT_MS),packages/cupix-pix-genie-preprocessor-agent/src/config/constants.ts(해당 상수는 process 파일 내부에 하드코딩됨 — 환경변수로 외부화 검토).
단기 개선 (1주 이내)#
- 관찰성 수정 (
pixgenie.process.ts:37):JSON.stringify(error)를 팀 표준 로거 직렬화(예:stringifyError유틸 — 이미preprocessor-service.ts에서 사용) 로 변경하여message/stack/cause가 로그에 포함되게 한다. 현재는 대표 에러 로그만 보면 "Process timeout" 원인을 알 수 없어 RCA 가 어렵다. - 타임아웃 상수 외부화:
TIMEOUT_MS를 환경변수(예:PIXGENIE_TIMEOUT_MS) 로 노출해 인프라/운영이 코드 배포 없이 조정 가능하도록 한다. Capture 크기별 편차가 큰 워크로드에서 하드코딩된 2 시간은 재발 가능성이 높다. - capture-size 기반 동적 타임아웃 검토: capture 의 pano 수/point cloud 크기 등으로 대략적 상한을 계산하거나, 진행 로그(
elapsed) 기반 heartbeat 방식으로 "무진행 X 분" 이면 abort 하는 방식으로 변경하는 것을 검토 (현재는 절대 시간만 사용).
장기 개선 (재발 방지)#
- 재시도/재개 전략: PixGenie 는 단계별 산출물(
clusters→panos→pointclouds→Lift2dSegmentations) 을 생성하므로, 중단된 지점에서 재개 가능한 checkpoint 를 마련하면 2 시간 타임아웃 발화 시 재실행 비용이 감소한다. - 동일 capture 에 대한 중복 실행 조사: 동일 capture 724274 에 대해 4개의 서로 다른 호스트가 동시에 pixgenie 를 실행한 이유가 정상 병렬화인지, job dispatch 중복인지 확인 필요. 만약 job 큐 중복이면 리소스 낭비와 상태 충돌 위험이 있다.
Monitoring#
- PixGenie 처리 시간 P50/P95/P99 — 하드코딩된 2 시간 상한을 재조정할 근거 마련. 팀은
pixgenieutil이elapsed:값을 stdout 에 남기고 있으므로 log-based metric 으로 추출 가능.
service:cupixworks-pano-postprocessor-instance @environment:production "PixGenieProcess::execute" "begin"
- AbortError 발생 카운트 — 이번 타임아웃이 재발하는지 추적.
service:cupixworks-pano-postprocessor-instance @environment:production status:error "ABORT_ERR"
- Process timeout 원인 카운트 (관찰성 수정 이후에는 이 쿼리로 훨씬 정확하게 잡힘):
service:cupixworks-pano-postprocessor-instance @environment:production status:error "Process timeout"
- PixGenie 실패 대비 성공 비율 —
PixGenieProcess::execute | completed successfullyvsPixGenieProcess::execute | failed로그 카운트 비교.
service:cupixworks-pano-postprocessor-instance @environment:production "PixGenieProcess::execute" ("completed successfully" OR "failed")
Risk Assessment#
- Risk level: medium — 단일 capture 단위의 실패이나 하드코딩된 상수로 인해 대형 capture 에서 재발 가능성이 있다.
- 예상 복잡도: standard —
pixgenie.process.ts단일 파일의 상수 외부화 + 로거 직렬화 수정. capture-size 기반 동적 타임아웃/checkpoint 는 별도 과제.