Skip to content

feat(core): warn when a timeout cannot actually stop the job#64

Merged
enginerd-kr merged 1 commit into
mainfrom
feat/warn-unenforceable-timeout
Jul 13, 2026
Merged

feat(core): warn when a timeout cannot actually stop the job#64
enginerd-kr merged 1 commit into
mainfrom
feat/warn-unenforceable-timeout

Conversation

@enginerd-kr

Copy link
Copy Markdown
Owner

요약

timeout=300은 **"이 job은 300초 뒤 멈춘다"**로 읽힙니다. 그런데 sync job에선 멈추지 않습니다.

Chronis는 만기 시 job을 FAILED로 표시하고, on_failure를 호출하고, 재시도를 예약합니다 — 그런데 함수는 계속 돕니다. 파이썬은 스레드를 죽일 수 없으니까요. 즉 현재의 timeout판정(verdict)이지 정지 버튼이 아닙니다.

지금까지 사용자는 이 사실을 abandoned_threads가 쌓이기 시작할 때에야 알았습니다. 이제 타임아웃을 설정하는 그 순간 알려줍니다.

동작

scheduler.every(minutes=5).config(timeout=300).run("sync_job")
WARNING - timeout_seconds is set on a sync job, where it cannot stop the job. On expiry
Chronis marks the job failed and retries it, but Python cannot kill the thread, so the
function keeps running (see get_worker_status()['abandoned_threads']). To make the timeout
real, give the blocking call its own timeout (e.g. requests.get(..., timeout=...)), or
write the job as an async function, where the timeout is a genuine cancellation.

경고로 끝내지 않고 실제로 일을 멈추는 두 가지 방법을 함께 제시합니다.

경고하지 않는 경우

상황 경고 이유
sync + timeout 강제성 없음 — 알려야 함
async + timeout asyncio.wait_for진짜로 취소함. 문제 없음
sync, timeout 없음 애초에 약속한 적 없음
미등록 func + timeout 이미 "not registered here" 경고가 나감. 게다가 실행 파드에서 그 함수가 sync일지 async일지 알 수 없음 (producer/worker 분리 배포)

마지막 항목이 특히 중요합니다 — API 파드가 job을 만들고 워커 파드가 실행하는 정상 패턴에서, 생성 프로세스는 함수의 색깔을 알 수 없으므로 추측해서 경고하지 않습니다.

배경

이건 "언젠가 isolation=\"process\"를 만들면 해결될 문제"의 오늘 할 수 있는 정직한 몫입니다. 프로세스 격리가 생기면 경고에 그 선택지를 추가하면 됩니다. 그 전까지는 사용자가 timeout의 실제 계약을 알고 쓰는 게 맞습니다.

테스트

  • 571개 통과 (unit + integration + e2e + postgres), ruff · mypy 통과
  • 신규 회귀 테스트 4개: sync 경고 / async 무경고 / timeout 없으면 무경고 / 미등록 시 중복 경고 없음
  • docs/ARCHITECTURE_NOTES.md에 경고의 근거 기록

🤖 Generated with Claude Code

`timeout=300` reads as "this job stops after 300 seconds". On a sync job it does not:
Chronis marks the job failed, fires on_failure and schedules the retry, but Python cannot
kill a thread, so the function keeps running and holding whatever it holds. Users only
discovered this when abandoned_threads started climbing.

create_job now says so at the moment the timeout is set, and points at the two things that
do stop the work: a timeout on the blocking call itself (requests.get(..., timeout=...)),
or writing the job as async, where asyncio.wait_for is a genuine cancellation.

Async jobs are not warned about — their timeout is real. Nor are functions this process
does not know, which already get the "not registered here" warning and whose colour on the
executing pod we cannot guess.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@enginerd-kr

Copy link
Copy Markdown
Owner Author

Coverage report

Click to see where and how coverage changed

FileStatementsMissingCoverageCoverage
(new stmts)
Lines missing
  chronis/core/base
  scheduler.py
Project Total  

This report was generated by python-coverage-comment-action

@enginerd-kr enginerd-kr added the safe-small-change Low-risk and straightforward change label Jul 13, 2026
@enginerd-kr
enginerd-kr merged commit 11d6032 into main Jul 13, 2026
3 checks passed
@enginerd-kr
enginerd-kr deleted the feat/warn-unenforceable-timeout branch July 13, 2026 14:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

safe-small-change Low-risk and straightforward change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant