Skip to content

fix(model): use os._exit for OOM to trigger pool recovery - #5005

Merged
qinxuye merged 3 commits into
xorbitsai:mainfrom
m199369309:fix/oom-hard-exit
Jun 9, 2026
Merged

qinxuye merged 3 commits into
xorbitsai:mainfrom
m199369309:fix/oom-hard-exit

Conversation

@m199369309

Copy link
Copy Markdown
Collaborator

Summary

  • sys.exit(1) in _handle_oom_error only raises SystemExit, which xoscar's _ErrorProcessor.__exit__ (backends/pool.py) swallows with an unconditional return True. The model subprocess stays alive, its returncode stays None, monitor_sub_pools keeps treating it as alive, and recover_sub_pool never fires. The model becomes a "zombie" that keeps returning OOM errors without ever recovering.
  • Replace sys.exit(1) with os._exit(1) so the subprocess actually exits at the OS level and xoscar can detect the returncode change and rebuild the sub pool.
  • Add a reentrancy gate (_oom_handling) to handle concurrent OOM on the sync batch path (where the worker thread does not await the cleanup coroutine and can trigger another OOM before os._exit lands).
  • Reduce stop() timeout from 30s to 5s to bound recovery delay (OOM-site cleanup mostly cannot reclaim already-reserved GPU fragments anyway).
  • Add regression tests in both sync (test_client.py) and async (test_async_client.py) test suites that verify the subprocess actually exits after OOM.

Test plan

  • Run test_model_oom_triggers_restart in both test_client.py and test_async_client.py — verifies the subprocess exits after OOM
  • Run existing test_model_error — verifies OOM error message is still propagated to client
  • Run existing test_auto_recover — verifies normal auto-recovery (non-OOM path) still works
  • Manual test: trigger OOM on a GPU model and verify the model recovers within ~5.5s (5s stop timeout + 0.5s xoscar monitor interval)

🤖 Generated with Claude Code

@XprobeBot XprobeBot added bug Something isn't working gpu labels Jun 8, 2026
@XprobeBot XprobeBot added this to the v2.x milestone Jun 8, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the Out of Memory (OOM) error handling in xinference/core/model.py to use os._exit(1) instead of sys.exit(1), ensuring that the model subprocess exits immediately and allows xoscar to detect the failure and recover the pool. It also introduces a reentrancy gate, shortens the cleanup timeout to 5 seconds, and adds corresponding integration tests. The review feedback suggests two important improvements: wrapping the worker status update in a timeout to prevent indefinite blocking during OOM, and replacing time.sleep with asyncio.sleep in the asynchronous test to avoid blocking the event loop.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread xinference/core/model.py Outdated
Comment thread xinference/client/tests/test_async_client.py Outdated

@qinxuye qinxuye left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One issue to address before LGTM.

Comment thread xinference/core/model.py Outdated
m199369309 and others added 3 commits June 9, 2026 14:13
sys.exit(1) only raises SystemExit, which xoscar's _ErrorProcessor
swallows with an unconditional `return True`. The process then stays
alive, its returncode stays None, monitor_sub_pools keeps treating it
as alive, and recover_sub_pool never fires.

Replace sys.exit(1) with os._exit(1) in _handle_oom_error so the
subprocess actually exits and xoscar can detect the returncode change
and rebuild the sub pool. Add a reentrancy gate (_oom_handling) to
handle concurrent OOM on the sync batch path, and reduce the stop()
timeout from 30s to 5s to bound recovery delay.

Add regression tests in both sync and async client test suites.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… test

Address code review feedback:

1. Wrap worker_ref.update_model_status in asyncio.wait_for(timeout=5)
   in _handle_oom_error. During OOM the worker may be unresponsive or
   under high load, and the RPC could block indefinitely, preventing
   os._exit(1) from being reached and delaying pool recovery. The
   status update is best-effort and should not block the exit path.

2. Replace time.sleep(1) with await asyncio.sleep(1) in async test
   test_model_oom_triggers_restart. Blocking the event loop with
   time.sleep in an async test can delay other async tasks and cause
   test flakiness or timeouts.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Address review feedback: _get_worker_ref() calls xo.actor_ref() which
is also an xoscar RPC and can hang during OOM / worker failure. Wrap
the entire status report (_get_worker_ref + update_model_status) in a
single xo.wait_for(timeout=5) so the process always reaches os._exit(1)
promptly, even if the worker is completely unreachable.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@m199369309
m199369309 force-pushed the fix/oom-hard-exit branch from fb4bb0d to 5646f57 Compare June 9, 2026 06:14

@qinxuye qinxuye left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@qinxuye
qinxuye merged commit 366cfd9 into xorbitsai:main Jun 9, 2026
11 of 13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working gpu

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants