fix(serve): resource governor for multi-instance system freezes - #142
Open
rajkumarsakthivel wants to merge 7 commits into
Open
fix(serve): resource governor for multi-instance system freezes#142rajkumarsakthivel wants to merge 7 commits into
rajkumarsakthivel wants to merge 7 commits into
Conversation
…eezes (#139) Multiple cce serve processes (one per project per AI session) exhaust system memory and create 10-20 minute desktop freezes. Root cause: no thread caps, no idle shutdown, no cross-process coordination, no memory pressure awareness. Adds resource_governor.py with four mechanisms: - ONNX Runtime thread caps (default 2 per process, via CCE_ORT_THREADS) - Per-project file lock so duplicate instances don't index simultaneously - Linux PSI memory-pressure detection to pause indexing under pressure - Idle auto-shutdown after 30 minutes of MCP inactivity New config keys: serve.idle_timeout_minutes, serve.max_ort_threads New env vars: CCE_ORT_THREADS, CCE_IDLE_TIMEOUT_MINUTES
fazleelahhee
previously approved these changes
Jul 27, 2026
Contributor
There was a problem hiding this comment.
Pull request overview
Adds a “resource governor” to cce serve to reduce multi-instance host overload (threads, indexing contention, and idle zombie processes), addressing issue #139’s reported Linux desktop freezes under heavy multi-process usage.
Changes:
- Introduces
resource_governor.pywith ONNX/thread caps, per-project indexing lock, Linux PSI memory-pressure backoff, and idle auto-shutdown utilities. - Wires the governor into
cce servestartup/reindex flow (thread caps, index lock, PSI backoff, and an idle watchdog). - Adds new
serve.*config keys (idle_timeout_minutes,max_ort_threads) and maps them intoConfig.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| src/context_engine/resource_governor.py | New governor utilities (thread caps, lock, PSI check, idle tracking). |
| src/context_engine/integration/mcp_server.py | Tracks MCP tool activity to support idle shutdown. |
| src/context_engine/config.py | Adds and maps new serve.* configuration keys. |
| src/context_engine/cli.py | Applies governor behavior in cce serve (caps, lock/backoff, idle watchdog). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+109
to
+116
| try: | ||
| self._lock_path.parent.mkdir(parents=True, exist_ok=True) | ||
| fd = os.open(str(self._lock_path), os.O_CREAT | os.O_RDWR, 0o644) | ||
| fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB) | ||
| self._fd = fd | ||
| return True | ||
| except (OSError, BlockingIOError): | ||
| return False |
Comment on lines
+52
to
+75
| raw = os.environ.get("CCE_ORT_THREADS", "").strip() | ||
| if raw: | ||
| try: | ||
| n = int(raw) | ||
| except ValueError: | ||
| n = _DEFAULT_ORT_THREADS | ||
| elif max_threads is not None: | ||
| n = max_threads | ||
| else: | ||
| n = _DEFAULT_ORT_THREADS | ||
|
|
||
| if n <= 0: | ||
| return 0 # 0 = "let ONNX pick" | ||
|
|
||
| for var in ( | ||
| "OMP_NUM_THREADS", | ||
| "MKL_NUM_THREADS", | ||
| "OPENBLAS_NUM_THREADS", | ||
| "ORT_NUM_THREADS", # some ORT builds read this directly | ||
| ): | ||
| os.environ.setdefault(var, str(n)) | ||
|
|
||
| # tokenizers library (used by fastembed) also spawns threads | ||
| os.environ.setdefault("TOKENIZERS_PARALLELISM", "false") |
Comment on lines
+3504
to
+3512
| # Back off under memory pressure (#139). | ||
| if is_memory_pressured(): | ||
| _log.info( | ||
| "Memory pressure detected; deferring re-index of %s", | ||
| rel, | ||
| ) | ||
| _reindex_queue.task_done() | ||
| await asyncio.sleep(30) | ||
| continue |
Comment on lines
+82
to
+86
| # Resource governor (#139) — caps per-process ONNX Runtime threads and | ||
| # auto-shuts down idle servers so zombie processes don't accumulate. | ||
| # 0 = disabled (no auto-shutdown / use ORT default threads). | ||
| serve_idle_timeout_minutes: int = 30 | ||
| serve_max_ort_threads: int = 2 |
- Fix FD leak in ProjectIndexLock.try_acquire when flock fails - Force-set ORT thread env vars instead of setdefault - Re-queue reindex files under memory pressure instead of dropping - Fix docstring mechanism count (three → four) - Add config tests for serve.idle_timeout_minutes and serve.max_ort_threads
Contributor
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (2)
src/context_engine/resource_governor.py:81
- When CCE_ORT_THREADS (or serve.max_ort_threads) is set to 0/negative to disable ORT caps, this returns early and never sets TOKENIZERS_PARALLELISM=false. That contradicts the PR’s stated goal of preventing tokenizers from spawning extra threads, and can reintroduce high thread counts even when ORT caps are disabled.
if n <= 0:
return 0 # 0 = "let ONNX pick"
src/context_engine/cli.py:3525
- If the per-project index lock is held by another process, this worker drops the reindex request entirely. If the other process misses the same filesystem event (watcher queue overflow, transient watcher failure, etc.), the project index can remain stale until a later change happens to the same file. Consider re-queuing with a small backoff (similar to the PSI-pressure path) so every change is eventually indexed once the lock becomes available.
if not index_lock.try_acquire():
_log.debug(
"Another process is indexing this project; "
"skipping %s", rel,
)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #139. Multiple
cce serveprocesses (one per project per AI session) exhaust system memory and cause 10–20 minute desktop freezes on Linux. The reported case had 68 CCE processes with 2,179 threads consuming 5.4 GiB RSS, triggering kernel OOM and PSI pressure spikes above 85%.This PR adds a resource governor (
resource_governor.py) with four mechanisms:ONNX Runtime thread caps: each
cce serveprocess defaults to 2 intra-op threads instead of cpu_count. Override viaCCE_ORT_THREADSenv var orserve.max_ort_threadsconfig key. Also setsTOKENIZERS_PARALLELISM=falseto prevent the tokenizers library from spawning additional threads.Per-project index lock: file-lock (
fcntl.flock) so when multiple AI sessions spawn duplicatecce serveprocesses for the same project, only one indexes at a time. Others skip and retry on the next file-change event. No-op on Windows (advisory lock not available).Linux PSI memory-pressure detection: reads
/proc/pressure/memoryand defers re-indexing whensome avg10exceeds 25%. This prevents CCE from contributing to the cascading page-cache thrashing described in the issue. No-op on non-Linux platforms.Idle auto-shutdown:
cce serveexits after 30 minutes of MCP inactivity (nocall_toolinvocations). This prevents zombie processes from accumulating when AI sessions disconnect or go idle. Configurable viaCCE_IDLE_TIMEOUT_MINUTESenv var,serve.idle_timeout_minutesconfig key, or set to 0 to disable.New config keys
Impact on the reported scenario
With the defaults from this PR, the 68-process scenario from the issue would see: