PreprocessorService::run | end - {"stack":"AbortError: The operation was aborted
RCA: PreprocessorService::run AbortError (Process timeout)
Overview#
What Happened#
2026-06-11 22:23:18 KST에 cupixworks-pix-genie-preprocessor-instance 서비스(capture 709953 처리 작업)에서 spawn된 Python child process(/tmp/lib/pixgenie/pixgenie/scripts/docker_entrypoint.py)가 정확히 2시간 후 AbortSignal.timeout에 의해 abort되며 AbortError로 종료되었다. 단일 occurrence이며, 동일 instance에서 그 이전·이후의 다른 capture 처리는 정상적으로 PreprocessorService::run | end info log로 마무리되었다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | AbortError |
| exception.message | The operation was aborted (cause: Process timeout) |
| exception.code | ABORT_ERR |
| top_frame | /tmp/agent/dist/app.cjs:6613:31 → pixgenie.process.ts:60 (this._abortController!.abort(new Error('Process timeout'))) |
| timeout config | TIMEOUT_MS = 2 * 60 * 60 * 1000 (2 hours) |
| runtime | Node.js (child_process spawn → python3) |
| env | production, us-west-2, tenant cupix |
| capture_id | 709953 (uncertain — inferred from preceding PixGenieProcess::execute | begin - captureId: 709953 log at 20:23:17 KST on the same instance) |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| cana / pix-genie preprocessor | 1 | 1 capture(추정 id 709953)의 Pix Genie 전처리 작업이 error 상태로 마킹됨. capture는 pix_genie_state = error로 전이, 사용자는 해당 capture의 Pix Genie 결과를 사용할 수 없다. 동일 시간대의 다른 capture 처리는 영향 없음. |
Timeline#
- 2026-06-11 20:23:16 KST —
PreprocessorService::run | begin(info log, same instance) - 2026-06-11 20:23:17 KST —
PixGenieProcess::execute | begin - captureId: 709953(Python child process spawn, 2시간 timeout 등록) - 2026-06-11 22:23:17 KST — 정확히 2시간 경과,
timeoutSignalfire →PixGenieProcess | process timeout, aborting...(warn log) →abortController.abort(new Error('Process timeout')) - 2026-06-11 22:23:17 KST — Node
child_process모듈이 abort signal에 반응해abortChildProcess를 호출,AbortErrorreject로 promise 거부 →PixGenieProcess::execute | failed - {"code":"ABORT_ERR",...}(error log) - 2026-06-11 22:23:18 KST —
PreprocessorService::runcatch block에서pix_genie_state = error로 업데이트, 본 cluster의 error log 발생:PreprocessorService::run | end - {"stack":"AbortError: ..."}. 이어 finally block에서updateJobState(stopped)호출, infoPreprocessorService::run | end출력.
Error Log#
PreprocessorService::run | end - {"stack":"AbortError: The operation was aborted
at abortChildProcess (node:child_process:725:27)
at EventTarget.onAbortListener (node:child_process:795:7)
at [nodejs.internal.kHybridDispatch] (node:internal/event_target:831:20)
at EventTarget.dispatchEvent (node:internal/event_target:766:26)
at abortSignal (node:internal/abort_controller:370:10)
at AbortController.abort (node:internal/abort_controller:392:5)
at timeoutSignal.addEventListener.once (/tmp/agent/dist/app.cjs:6613:31)
at [nodejs.internal.kHybridDispatch] (node:internal/event_target:831:20)
at EventTarget.dispatchEvent (node:internal/event_target:766:26)
at abortSignal (node:internal/abort_controller:370:10)","message":"The operation was aborted","cause":{"stack":"Error: Process timeout
at timeoutSignal.addEventListener.once (/tmp/agent/dist/app.cjs:6613:37)
at [nodejs.internal.kHybridDispatch] (node:internal/event_target:831:20)
at EventTarget.dispatchEvent (node:internal/event_target:766:26)
at abortSignal (node:internal/abort_controller:370:10)
at Timeout._onTimeout (node:internal/abort_controller:127:7)
at listOnTimeout (node:internal/timers:581:17)
at process.processTimers (node:internal/timers:519:7)","message":"Process timeout","name":"Error"},"code":"ABORT_ERR","name":"AbortError"}
Impact#
- Service:
cupixworks-pix-genie-preprocessor-instance - Team: cana
- 발생 횟수: 1
- 최초 발생: 2026-06-11 22:23:18 KST
- 최근 발생: 2026-06-11 22:23:18 KST
Root Cause Summary#
PixGenieProcess.runPythonProcess가 spawn한 Python child process(docker_entrypoint.py)가 하드코딩된 2시간(TIMEOUT_MS = 2 * 60 * 60 * 1000) 안에 종료되지 못했고, AbortSignal.timeout가 등록한 abort handler가 발화하여 abortController.abort(new Error('Process timeout'))로 child process를 강제 종료했다. Node.js child_process 모듈이 abort signal을 받아 자식 프로세스를 kill하면서 spawn promise가 AbortError(code: ABORT_ERR, cause: Error('Process timeout'))로 reject되었고, PreprocessorService::run의 catch block에서 그대로 JSON.stringify 직렬화되어 error log로 출력되었다. 즉, 본 에러는 코드 결함이 아니라 "2시간 SLA를 초과한 capture 처리"라는 운영 시나리오를 그대로 error로 표면화한 것이다.
Technical Analysis#
Code Path#
- Entry point:
applications/agents/packages/cupix-pix-genie-preprocessor-agent/src/preprocessor-service.ts:66(PreprocessorService::run) - 호출 체인:
preprocessor-service.ts:93→PixGenieManager.executePixGenieProcessing(cpCapture)pix-genie-manager.ts:75→PixGenieProcess.execute(processParams)pixgenie.process.ts:33→runPythonProcess(params)(Python 자식 프로세스 spawn)
- Failure point:
pixgenie.process.ts:56-61—AbortSignal.timeout(2h)이 발화하면 즉시abortController.abort(new Error('Process timeout'))가 실행되어 spawn 옵션의signal(line 69) 을 통해 Node 가 자식 프로세스를 kill 하고 promise 를AbortError로 reject.
private _abortController?: AbortController;
private readonly TIMEOUT_MS = 2 * 60 * 60 * 1000; // 2 hours
private static readonly PIXGENIE_LIB_PATH = '/tmp/lib/pixgenie';
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 });
logger.debug('PixGenieProcess::runPythonProcess | executing: python3 %s', PixGenieProcess.PIXGENIE_SCRIPT_PATH);
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);
});
}
} 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');
}
기대 동작 vs 실제 동작:
- 기대: Python child process가 2시간 안에 SQLite 결과 파일을 생성하고
code === 0로 종료(pixgenie.process.ts:108-109). - 실제: 2시간이 경과해도 종료하지 않아 timeout signal 이 발화, child process 가 kill 되어
AbortError로 reject. 결과 SQLite 파일은 생성되지 않고 capture 의pix_genie_state가error로 마킹됨.
또한 setupProcessHandlers 의 error 핸들러가 그대로 reject 하기 때문에(pixgenie.process.ts:115-118), 자식 프로세스의 stdout/stderr 진행 상황(어디까지 처리되었는지) 정보가 error log 의 cause 에는 누락되어 있다. capture id 도 root error 메시지에는 포함되지 않아 어떤 capture 가 실패했는지 별도 시간 정합으로 추적해야 한다(아래 Log Evidence 참고).
Log Evidence#
Datadog query (재현용):
service:cupixworks-pix-genie-preprocessor-instance status:error @environment:production "PreprocessorService::run"
보조 query (begin/end pair, capture id 식별):
service:cupixworks-pix-genie-preprocessor-instance ("PreprocessorService::run | begin" OR "PixGenieProcess::execute | begin" OR "process timeout" OR "PreprocessorService::run | end")
핵심 로그 시퀀스(시간순):
2026-06-11 20:23:16 KST info PreprocessorService::run | begin
2026-06-11 20:23:17 KST info PixGenieProcess::execute | begin - captureId: 709953
2026-06-11 22:23:17 KST warn PixGenieProcess | process timeout, aborting...
2026-06-11 22:23:17 KST error PixGenieProcess | process error: The operation was aborted
2026-06-11 22:23:17 KST error PixGenieProcess::execute | failed - {"code":"ABORT_ERR","name":"AbortError"}
2026-06-11 22:23:18 KST error PreprocessorService::run | end - {"stack":"AbortError: ...","cause":{...,"message":"Process timeout"},...}
2026-06-11 22:23:18 KST info PreprocessorService::run | end
PixGenieProcess::execute | begin (20:23:17 KST) 와 process timeout (22:23:17 KST) 사이의 간격이 정확히 2 시간 0 분 0 초 — 즉 코드의 TIMEOUT_MS 와 일치한다. 이는 자연 종료 또는 다른 종류의 실패가 아니라 프로세스가 진행 중인 상태에서 timeout 한도에 도달해 강제 종료되었다는 결정적 증거다.
동일 시간대의 다른 instance 비교(정상 종료 사례, 동 24시간 내):
2026-06-11 21:40:49 KST info PreprocessorService::run | end (정상)
2026-06-11 21:54:52 KST info PreprocessorService::run | end (정상)
2026-06-11 20:35:57 KST info PreprocessorService::run | end (정상)
→ pix-genie preprocessor 자체는 동일 환경에서 다른 capture 들에 대해 정상 동작 중. 본 인시던트는 특정 capture(추정 id 709953) 의 처리 시간이 2 시간을 초과한 단일 케이스다.
이전 24 시간 동안 동일 fingerprint 의 timeout 발생 횟수: 1회(본 occurrence). 동일 service 의 다른 에러는 모두 별개 fingerprint(HttpError, ARG10000 등).
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | Python child process가 2시간 hardcoded timeout(TIMEOUT_MS = 2 * 60 * 60 * 1000)을 초과하여 AbortSignal.timeout에 의해 강제 abort됨 |
(a) PixGenieProcess::execute | begin 20:23:17 KST 와 process timeout, aborting... 22:23:17 KST 사이 정확히 2h 경과. (b) error log 의 cause.message = "Process timeout", stack 의 at Timeout._onTimeout 이 코드 pixgenie.process.ts:58-61 의 timeoutSignal.addEventListener('abort', ...) 와 일치. (c) code: ABORT_ERR, child_process 의 signal 옵션을 통한 abort 경로(abortChildProcess at node:child_process:725:27) 가 stack 에 명시. |
— | Confirmed |
| H2 | Tesla API(capture/job update)가 응답하지 못해 hang 됨 | 없음. catch block 의 updatePixGenieState 는 timeout 이후 호출되며, log 상 그 직후 PreprocessorService::run | end info 가 정상 출력됨. |
error 의 cause 가 Process timeout 이지 HttpError 가 아님. 동일 instance 의 finally updateJobState 호출이 같은 초에 완료되었음(시간 간격 0초). |
Rejected |
| H3 | PixGenieProcess 인스턴스가 재사용되어 이전 abort controller / signal 누수가 timeout 을 잘못 트리거 |
PixGenieManager 가 _pixGenieProcess 를 한 번만 생성(pix-genie-manager.ts:23) 하므로 이론적으로 가능 |
본 instance 에서 단 1 capture(709953) 만 처리 중이었고(PreprocessorService::run | begin 1회), runPythonProcess 는 매 호출마다 새 AbortController/AbortSignal.timeout 을 생성(pixgenie.process.ts:54-56). |
Rejected |
| H4 | Python 스크립트가 무한 루프 / deadlock 에 빠짐 (H1 의 하위 원인) | timeout 한도까지 child process 가 alive 상태였다는 점은 일관 | 본 cluster 자체로는 stdout/stderr 가 caching 안되어(error 객체에는 stderr 포함되지 않음) Python 측 행 위치를 확인할 수 없음 — 별도 debug log / Watch (Kibana) 조사가 필요함. | Inconclusive — needs verification |
Fix Recommendation#
즉시 조치 (Critical)#
특별한 즉시 조치는 권장하지 않는다. 단일 capture 가 2 시간을 초과하여 강제 종료된 1 회 이벤트이며, capture 의 pix_genie_state 도 정상적으로 error 로 전이되었다(preprocessor-service.ts:101). 동일 instance 의 다른 capture 처리는 정상이다. 사용자가 동일 capture(추정 id 709953) 를 재처리하면 회복 가능하다.
운영팀은 capture 709953 이 실제로 어떤 데이터(panorama 수, 해상도, BIM 사용 여부 등)를 가지고 있어 2 시간을 초과했는지 확인하고, 단순 재시도가 안전한지 판단할 것을 권장한다.
단기 개선 (1주 이내)#
- 에러 로그 컨텍스트 보강 —
pixgenie.process.ts:36-39의 catch block 에서 현재는JSON.stringify(error)만 남기고 있어 (a)captureId, (b) 경과 시간, (c)stderr누적값(setupProcessHandlers의stderr변수는 close handler 에만 전달됨) 이 모두 누락된다. timeout 경로에서도captureId와elapsed_ms를 포함하도록 로깅을 보강하여 추후 동일 timeout 이 다시 일어났을 때 어떤 capture 가 영향을 받았는지 cluster 만으로 즉시 파악할 수 있게 한다. - Timeout 발화 시 stderr 캡처 —
setupProcessHandlers가 누적한stderr를runPythonProcess의reject분기(특히 abort 케이스) 에서도 함께 reject 페이로드에 포함시키면 H4(Python 스크립트 무한 루프 vs 정상 진행 중 시간 초과) 를 사후 가설로 분리 가능. 다만 이는 RCA 의 fix 범위가 아닌 다음 인시던트 대비용 개선 권고.
장기 개선 (재발 방지)#
- Timeout 의 환경변수화 — 현재
TIMEOUT_MS가 코드에 하드코딩되어 있어 capture 크기/리전별로 다른 SLA 가 필요할 때 코드 배포가 필수다. 환경변수(PIXGENIE_PROCESS_TIMEOUT_MS등) 로 분리하면 운영 중 조정 가능.pixgenie.process.ts:18한 줄 변경 +config/environment.ts엔트리 추가 수준의 작업. - 장시간 capture 사전 진단 — pano 개수 / 이미지 크기로 처리 시간 예측 메트릭을 발행하고, 2 시간 SLA 초과가 의심되는 capture 는 큐 단계에서 분리 처리. (ops 결정 영역, 본 cluster 만으로 ROI 판단 어려움 — Inconclusive 권고.)
Monitoring#
다음 Datadog 쿼리들은 release dashboard timeseries widget 에 그대로 사용 가능하다. monitor-only 문법(| stats, count by(...)) 은 사용하지 않았다.
1. Pix Genie process timeout 발생 추이
service:cupixworks-pix-genie-preprocessor-instance "process timeout, aborting..."
2. PreprocessorService::run 단의 AbortError 발생 추이
service:cupixworks-pix-genie-preprocessor-instance status:error "AbortError"
3. 전체 PreprocessorService::run 에러 추이 (HttpError 등 포함, 비교용)
service:cupixworks-pix-genie-preprocessor-instance status:error "PreprocessorService::run | end"
4. 정상 종료 대비 (분모용)
service:cupixworks-pix-genie-preprocessor-instance "PixGenieProcess::execute | begin"
운영 팀이 임계치 알림을 추가한다면, 동일 24 시간 윈도우에서 process timeout, aborting... 발생 횟수가 1 건을 초과하는 경우(즉 산발적 단일 occurrence 가 아닌 패턴) 알림이 적절하다. 단일 occurrence 알림은 false-positive 비용이 더 클 가능성이 있다.
Risk Assessment#
- Risk level: low — 단일 occurrence, 동일 fingerprint 24h 내 1 건. 다른 capture 처리는 정상. capture state 가 error 로 전이되어 사용자가 재처리할 수 있는 상태.
- 예상 복잡도: trivial (즉시 코드 변경 권고 없음. 단기 로깅 보강은 standard 수준의 1-2줄 변경.)