Skip to content

fix(DM01-6341): catch K8s API timeout in kube_job to prevent crash loop - #651

Merged
liuwei08 merged 4 commits into
masterfrom
fix/DM01-6341
Oct 15, 2026
Merged

liuwei08 merged 4 commits into
masterfrom
fix/DM01-6341

Conversation

@liuwei08

@liuwei08 liuwei08 commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Wrap requests.post() in kube_job() with try/except RequestException — catches both Timeout and ConnectionError; returns False so the job stays queued and is retried on the next tick
  • Handle 409 in kube_job() as success — if a previous POST succeeded but the response timed out, the next attempt gets 409; treating it as success prevents the job from being stuck in queued forever
  • Move assign_cluster(), schedule(), and update_cluster_state() inside the single try/except in handle() so no uncaught exception can escape to main()

Root Cause

When K8s API is slow (observed during NodeOS upgrade), requests.post() in kube_job() raises ReadTimeout. schedule() was called outside the try/except block in handle(), so the exception propagated uncaught to main(), crashing the scheduler process. This caused 95 pod restarts in 2 days on the production cluster.

Behavior Change

Previously, an exception in handle_aborts() / handle_timeouts() etc. was caught and schedule() still ran. Now an exception in any part of handle() skips schedule() for that tick. This is intentional: if an early function fails (typically a DB issue), attempting to schedule would also fail against the same broken connection.

Test plan

  • Verify scheduler pod no longer crash-loops when K8s API times out or returns connection errors
  • Verify jobs in queued state are retried on the next tick after a network failure
  • Verify a job that gets 409 on retry is correctly moved to scheduled state
  • Verify normal scheduling flow is unaffected

When K8s API is slow (e.g. during NodeOS upgrade), requests.post() in
kube_job() raises ReadTimeout. This was uncaught and propagated out of
handle() since schedule() was outside the try/except block, crashing
the scheduler process (95 restarts observed in 2 days).

- Wrap requests.post() in kube_job() with try/except Timeout; return
  False so the job stays queued and is retried on the next tick
- Move assign_cluster() and schedule() inside the existing try/except
  in handle() so no uncaught exception can escape to main()
- Broaden exception catch from Timeout to RequestException to also
  handle ConnectionError during K8s API unavailability
- Return True on 409 in kube_job() so a job is not stuck queued when
  a previous POST succeeded but the response timed out
- Move update_cluster_state() inside the try/except block in handle()
  to eliminate a remaining uncaught exception path
…ground threads

handle_pipeline_invocations() and handle_function_invocations() make
live K8s API calls every tick, blocking handle_orphaned_jobs() and
schedule() when K8s API is slow. Move them to dedicated background
threads (same pattern as nodes/pipelines cache refresh) so the main
scheduling loop is never blocked by CRD controller sync latency.

sleep is placed after handle() to prevent concurrent runs if a single
iteration takes longer than the sleep interval.
@liuwei08
liuwei08 merged commit 03e41d2 into master Oct 15, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant