PixGenieProcess | process error: The operation was aborted
RCA: PixGenieProcess | process error: The operation was aborted
Overview#
What Happened#
2026-06-11 22:23 KST에 cupixworks-pix-genie-preprocessor-instance agent가 captureId 709953에 대한 PixGenie 전처리 작업을 수행하던 중, 자식 Python 프로세스(docker_entrypoint.py → lift2dsegmentations_cli)가 2시간 hard timeout (TIMEOUT_MS = 2 * 60 * 60 * 1000)에 도달하여 AbortController가 발동되었다. Node가 자식 프로세스를 강제 종료하면서 'error' 이벤트로 AbortError: The operation was aborted를 emit, agent가 실패 종료했다. us-west-2 / cupix tenant / production 환경에서 1건 발생.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | AbortError |
| exception.code | ABORT_ERR |
| exception.message | The operation was aborted (cause: Error: Process timeout) |
| top_frame | pixgenie.process.ts:60 (this._abortController!.abort(new Error('Process timeout'))) |
| runtime | Node.js child_process (spawned python3 docker_entrypoint.py) |
| env | production, region us-west-2, tenant cupix |
| captureId | 709953 |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| cana / pix-genie preprocessor | 1 | 단일 capture(709953)의 PixGenie 전처리 실패. PixGenieState.Error로 마킹되고 job이 Stopped로 전환됨 — 사용자 대시보드에서 해당 capture의 PixGenie 결과 미생성 |
Timeline#
- 2026-06-11 20:23:17 KST —
PixGenieProcess::execute | begin - captureId: 709953(Python 자식 프로세스 spawn) - 2026-06-11 20:32:42 KST —
ExtractSegments단계 (706 frames decode 완료, MobileSAMv2 + DINOv3 inference) - 2026-06-11 20:43:59 KST —
ExtractFeatures(DINOv3 vitl16_lvd1689m, image_size 1536) 단계 진행 - 2026-06-11 21:38:07 KST —
Lift2DSegmentationsCli시작 (lift2dsegmentations_cli --input … --db pixgenie.sqlite) - 2026-06-11 21:38:26~28 KST —
Lift2dSegmentations::execute()start, 14M vertices에 대해computeVisibilityByReprojectionForRange실행. 이 시점부터 stdout 로그에[TRACE TIMEOUT] detectAndFixVideoFrameTimestampCorruption, #391마커가 반복 출력 - 2026-06-11 21:48:30 KST —
Refine/MaskCoverage로그 일부 출력 (마지막으로 관측된 정상 진행) - 2026-06-11 22:23:17 KST —
PixGenieProcess | process timeout, aborting...(warn) →PixGenieProcess | process error: The operation was aborted(error). begin으로부터 정확히 2시간 경과 — 코드 상의TIMEOUT_MS도달 - 2026-06-11 22:23:18 KST —
PreprocessorService::run | end에 AbortError full stack 기록 (cause: Error: Process timeout), workspace cleanup
Error Log#
PixGenieProcess | process error: The operation was aborted
상관 로그 (같은 인스턴스, 같은 초):
[warn] PixGenieProcess | process timeout, aborting...
[error] PixGenieProcess::execute | failed - {"code":"ABORT_ERR","name":"AbortError"}
[error] PixGenieProcess | process error: The operation was aborted
Impact#
- Service:
cupixworks-pix-genie-preprocessor-instance - Team: cana
- 발생 횟수: 1
- 최초 발생: 2026-06-11 22:23 KST
- 최근 발생: 2026-06-11 22:23 KST
- 블래스트 반경: 단일 capture (id 709953). 14일 retention 내 동일 timeout 메시지 다른 발생 없음 (Datadog
service:cupixworks-pix-genie-preprocessor-instance "process timeout"→ 2건만, 모두 이번 인시던트의 동일 timestamp).
Root Cause Summary#
PixGenie agent는 Python 자식 프로세스에 2시간 hard timeout을 적용한다 (TIMEOUT_MS = 2 * 60 * 60 * 1000, pixgenie.process.ts:18). captureId 709953 작업은 실제로 정상 진행 중이었으나 (706 frame 비디오에서 14M vertex pointcloud + 706 viewpoint visibility 재계산 + segment refinement), 처리량이 timeout 한도를 초과했다. 20:23:17 begin 이후 정확히 2시간이 지난 22:23:17에 AbortSignal.timeout(2h)가 발화하여 AbortController.abort(new Error('Process timeout'))을 호출, Node가 자식 프로세스를 강제 종료하면서 'error' 이벤트로 AbortError를 reject 했고 그 메시지가 process error: The operation was aborted로 로깅되었다. 원인은 외부 시스템 장애나 코드 버그가 아니라, 무거운 capture에 대한 처리 시간이 정적 timeout을 초과한 capacity/workload 미스매치다.
Technical Analysis#
Code Path#
- Entry point:
applications/agents/packages/cupix-pix-genie-preprocessor-agent/src/preprocessor-service.ts:66(PreprocessorService.run) - 호출 흐름:
run→pixGenieManager.executePixGenieProcessing(cpCapture)→PixGenieProcess.execute()→runPythonProcess()→spawn('python3', ...) - Failure point:
applications/agents/packages/cupix-pix-genie-preprocessor-agent/src/process/pixgenie.process.ts:60(timeout 발화 시abort(new Error('Process timeout'))) → 그 결과:115의'error'핸들러가AbortError로 reject
Timeout 정의:
export class PixGenieProcess {
private _process?: ChildProcess;
private _abortController?: AbortController;
private readonly TIMEOUT_MS = 2 * 60 * 60 * 1000; // 2 hours
private static readonly PIXGENIE_LIB_PATH = '/tmp/lib/pixgenie';
private static readonly PIXGENIE_SCRIPT_PATH = '/tmp/lib/pixgenie/pixgenie/scripts/docker_entrypoint.py';
constructor() {}
Timeout → abort → spawn signal 연결:
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
});
this.setupProcessHandlers(resolve, reject);
});
}
signal: this._abortController.signal이 설정되어 있어서 abort 시 Node 내부 abortChildProcess가 SIGTERM을 보내고 'error' 이벤트를 발생시킨다 (스택 트레이스에 node:child_process:725 확인됨).
에러 핸들러가 abort에 의한 종료와 일반 spawn 실패를 동일하게 처리:
this._process.on('error', (error) => {
logger.error('PixGenieProcess | process error: %s', error.message);
reject(error);
});
상위 catch에서 그대로 throw → PreprocessorService.run의 catch 블록이 PixGenieState.Error로 상태를 갱신:
} catch (error) {
await this.pixGenieManager.updatePixGenieState(captureId!, TESLA.PixGenieState.Error);
logger.error('PreprocessorService::run | end - %s', stringifyError(error));
} finally {
await this.pixGenieManager.updateJobState(jobId!, TESLA.UpdateJobRequest.StateEnum.Stopped);
logger.info('PreprocessorService::run | end');
}
기대 동작: 706 frame / 14M vertex 규모의 capture가 2시간 안에 완료되어야 함.
실제 동작: Lift2DSegmentationsCli 단계 (visibility 재계산 + per-segment refinement loop)가 timeout에 걸쳐 진행되어 2시간을 초과.
Log Evidence#
쿼리:
service:cupixworks-pix-genie-preprocessor-instance status:error @environment:production "PixGenieProcess"
service:cupixworks-pix-genie-preprocessor-instance "PixGenieProcess::execute | begin"
service:cupixworks-pix-genie-preprocessor-instance "capture_709953"
service:cupixworks-pix-genie-preprocessor-instance "process timeout"
Begin 시각:
2026-06-11 20:23:17 KST info PixGenieProcess::execute | begin - captureId: 709953
Timeout 발화 (begin + 정확히 2h):
2026-06-11 22:23:17 KST warn PixGenieProcess | process timeout, aborting...
2026-06-11 22:23:17 KST error PixGenieProcess::execute | failed - {"code":"ABORT_ERR","name":"AbortError"}
2026-06-11 22:23:17 KST error PixGenieProcess | process error: The operation was aborted
Full AbortError stack (cause 체인이 root cause를 직접 가리킴):
{
"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)\n at listOnTimeout (node:internal/timers:581:17)",
"message": "Process timeout",
"name": "Error"
},
"code": "ABORT_ERR",
"name": "AbortError"
}
자식 프로세스가 hang이 아니라 실제로 진행 중이었다는 증거 (timeout 직전 ~35분간 정상 stdout 진행):
2026-06-11 21:38:07 KST info PixGenieProcess | Command: ... lift2dsegmentations_cli --input ... --db pixgenie.sqlite
2026-06-11 21:38:26 KST info ... Pointcloud loaded, vertex count: 14283233
2026-06-11 21:38:28 KST info ... Recomputing visibility for 14283233 vertices, 706 viewpoints
2026-06-11 21:48:30 KST info ... [Refine] seg=307228 ... [MaskCoverage] seg=307228 ...
14일 retention 내 동일 service에서 "process timeout" 매칭 로그는 이번 인시던트의 2건(warn + error)뿐 — 즉 첫 발생 (recurrence 없음).
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | Node-side 2시간 hard timeout이 AbortController.abort('Process timeout')을 호출하여 자식 프로세스를 강제 종료, 그 결과 AbortError: The operation was aborted가 emit됨 |
begin 20:23:17 → timeout 22:23:17 정확히 2h 일치 (pixgenie.process.ts:18 TIMEOUT_MS = 2 * 60 * 60 * 1000); error 직전 `PixGenieProcess |
process timeout, aborting... warn 로그 (:59); AbortError stack의 cause가 Error: Process timeout (:60); spawn에 signal: this._abortController.signal이 연결되어 abort → abortChildProcess호출 (Nodechild_process:725`) |
— |
| H2 | 자식 Python 프로세스가 hang/deadlock에 빠져 진행이 멈춤 | stdout 로그가 끊기는 구간이 있음 | 21:48:30까지 [Refine] seg=... 로그가 정상적으로 흘러나오고 있었음 (실제로 진행 중). 22:23:17 abort는 hang 감지가 아니라 정적 wall-clock timeout |
Rejected |
| H3 | Python 측이 자체 예외를 던져 종료 (e.g., OOM, pipeline 오류) | — | 'close' 핸들러가 아닌 'error' 핸들러가 발화 (pixgenie.process.ts:115); exit code 로그(process exited with code) 없음; AbortError code가 ABORT_ERR |
Rejected |
| H4 | 외부 의존(API, S3, GPU) 장애로 작업이 지연됨 | — | 같은 시간대에 다른 service의 5xx/timeout 패턴 없음(쿼리 결과 0건); 진행 로그상 GPU inference / 파일 IO 정상 진행 | Rejected |
| H5 | 단일 capture 특이성 — 14M vertex / 706 viewpoint / 706 frame 규모가 평소보다 큰 워크로드 | Lift2DSegmentationsCli가 2400만건 (vertices × viewpoints / range) 규모의 visibility 재계산을 수행 중. 21:38:28 "Computing visible viewpoints: 14283233 vertices, 706 viewpoints, 408663 faces"; 21:48:30 시점에도 여전히 segment refinement 단계 |
recurrence 없음 (14d 0건). 일반화하긴 이르지만 이 capture의 워크로드가 timeout 경계에 근접했다는 직접적 증거 | Confirmed (contributing) |
Fix Recommendation#
즉시 조치 (Critical)#
applications/agents/packages/cupix-pix-genie-preprocessor-agent/src/process/pixgenie.process.ts:18의TIMEOUT_MS를 환경변수 (e.g.PIXGENIE_PROCESS_TIMEOUT_MS)로 외부화하고, capture 709953을 우선 재처리. 코드 변경 없이 운영적으로 timeout 한도를 늘리거나 capture별로 조정 가능해야 한다.pixgenie.process.ts:115-118의'error'핸들러에서AbortError(codeABORT_ERR)를 일반 spawn error와 구분하여 로깅. 현재는 "process error: The operation was aborted"로만 보여서 timeout인지 spawn 실패인지 즉시 식별 어려움. timeout임이 분명한 경우error대신warn으로 낮추는 것도 고려 (Memory rule "true bug vs expected operational scenario" 참고).- captureId 709953은 PixGenieState가
Error로 마킹된 상태이므로 (preprocessor-service.ts:101), 운영팀이 수동 재처리(혹은 자동 retry) 트리거 필요.
단기 개선 (1주 이내)#
- Progress-based timeout으로 전환: 현재 wall-clock 기반 hard timeout은 large capture에서 false positive를 만든다. 자식 프로세스 stdout에서 일정 시간(e.g. 10–15분) 신규 라인이 없을 때만 timeout 처리하는 idle-watchdog 도입을 검토. 진행 로그가 흐르고 있으면 진짜 hang이 아니라는 H2의 반대 증거가 그대로 활용된다.
- Workload heuristic + 조기 경고: capture의 vertex / viewpoint / frame 수를 PixGenieProcess 시작 시점에 집계해, 사전에 정의한 임계치를 넘으면 SLO 알림(
warn)을 보낸다. 그러면 timeout이 발생하기 전에 capacity/스케줄링 이슈로 분류 가능. - PreprocessorService에서 timeout-aware 재시도:
preprocessor-service.ts:100의 catch 블록에서AbortError/Process timeout케이스를 식별하면 Job state를 단순Stopped가 아니라 retryable 상태로 마킹 (Tesla API 측 retry 정책과 정합).
장기 개선 (재발 방지)#
- PixGenie pipeline의 단계별 timeout/체크포인팅: 현재는 전체 Python 프로세스에 단일 2h timeout만 있다.
ExtractFeatures/ExtractSegments/Lift2DSegmentationsCli등 phase별 진행 상태와 부분 산출물(예:pixgenie.sqlite중간 commit,vv_cache.bin)을 체크포인트로 활용하여 timeout 시 마지막 단계부터 재개 가능하게 한다. - Preprocessor instance 용량 분리: large capture (vertex/viewpoint 임계 초과)는 더 큰 GPU/CPU instance 풀로 라우팅. agent manager 레이어에서 사전 라우팅 결정.
- CapacityPlanning dashboard: PixGenie execution duration 분포 (p50/p95/p99) 및 capture size 메트릭을 상시 가시화하여 timeout 한도가 적정한지 주기적으로 검증.
Monitoring#
다음 쿼리는 release dashboard timeseries widget 에 그대로 들어간다 (writing-datadog-monitoring-queries 가이드 준수: pipe / | stats / threshold suffix 미사용, time-bucketed count/avg 형태로 작성).
PixGenie timeout 발생 추세:
sum:datadog.estimated_usage.logs.ingested_events{service:cupixworks-pix-genie-preprocessor-instance,status:error}.rollup(count, 3600)
(대안 — log-based metric을 쓰는 경우)
logs("service:cupixworks-pix-genie-preprocessor-instance \"process timeout, aborting\"").rollup("count").by("environment")
PixGenie 처리 시간 분포 (p95):
logs("service:cupixworks-pix-genie-preprocessor-instance \"PixGenieProcess::execute | completed successfully\"").rollup("count").by("environment")
PixGenie 실패율:
logs("service:cupixworks-pix-genie-preprocessor-instance status:error \"PixGenieProcess::execute | failed\"").rollup("count").by("environment")
알림 후보:
process timeout, aborting...warn 발생 → 즉시 Slack 알림 (희소 이벤트, 발생 자체가 이상 신호)PixGenieState.Error전이율이 24h 윈도우에서 baseline 대비 3x 초과 시 escalation- per-capture execution duration p95가 90분 초과 시 경고 (timeout 한도 75%)
Risk Assessment#
- Risk level: low (단일 발생, 외부 영향 없음, 사용자 데이터 손실 없음 — 단지 해당 capture의 PixGenie 결과 미생성). 다만 large capture가 늘어나면 recurrence 가능성 있음 (medium 으로 격상 가능).
- 예상 복잡도: standard —
TIMEOUT_MS를 env-driven으로 빼고 idle-watchdog 도입은 표준적인 변경. 장기 개선의 phase별 체크포인팅은 critical 수준의 설계 작업.