fix(ollama): stop infinite subprocess spawning from useEffect re-render loop (#1716)
* fix(graphiti): migrate graphiti_memory imports to canonical paths
The `graphiti_memory.py` shim was removed during the backend refactor
(commit 11fcdf42) but consumers were never updated. This caused all
`from graphiti_memory import ...` to fail silently (caught by
try/except ImportError), preventing the Graphiti memory system from
initializing for any project.
Migrate the 3 remaining import sites to use the canonical module path
`integrations.graphiti.memory` instead of the deleted shim.
Closes #1220
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* style: combine docstring import example into single line
Address Gemini Code Assist review suggestion to merge two import
examples from the same module into one line.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(ollama): stop infinite subprocess spawning from useEffect re-render loop
OllamaModelSelector's checkInstalledModels was a plain async function used
as a useEffect dependency. Since the function reference changed on every
render, the effect fired continuously — each iteration spawning 2-3 Python
subprocesses via executeOllamaDetector, causing hundreds of processes.
- Wrap checkInstalledModels in useCallback with [baseUrl] dependency so
the useEffect only re-runs when baseUrl actually changes
- Add a 2s deduplication cache to executeOllamaDetector as a safety net:
identical command+baseUrl calls within the TTL return the same promise
instead of spawning a new subprocess
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Andy <119136210+AndyMik90@users.noreply.github.com>
This commit is contained in:
@@ -201,6 +201,10 @@ function getOllamaInstallCommand(): string {
|
||||
* Spawns a subprocess to run Ollama detection/management commands with a 10-second timeout.
|
||||
* Used to check Ollama status, list models, and manage downloads.
|
||||
*
|
||||
* Includes deduplication: identical command+baseUrl requests within 2s return the cached
|
||||
* result/promise instead of spawning a new subprocess. This prevents runaway subprocess
|
||||
* spawning from React re-render loops.
|
||||
*
|
||||
* Supported commands:
|
||||
* - 'check-status': Verify Ollama service is running
|
||||
* - 'list-models': Get all available models
|
||||
@@ -212,9 +216,43 @@ function getOllamaInstallCommand(): string {
|
||||
* @param {string} [baseUrl] - Optional Ollama API base URL (defaults to http://localhost:11434)
|
||||
* @returns {Promise<{success, data?, error?}>} Result object with success flag and data/error
|
||||
*/
|
||||
// Deduplication cache to prevent rapid-fire subprocess spawning (e.g., from React re-render loops)
|
||||
const ollamaDetectorCache = new Map<string, { promise: Promise<{ success: boolean; data?: unknown; error?: string }>; timestamp: number }>();
|
||||
const OLLAMA_CACHE_TTL_MS = 2000; // Cache results for 2 seconds
|
||||
|
||||
async function executeOllamaDetector(
|
||||
command: string,
|
||||
baseUrl?: string
|
||||
): Promise<{ success: boolean; data?: unknown; error?: string }> {
|
||||
// Deduplication: return cached promise for identical requests within TTL
|
||||
const cacheKey = `${command}:${baseUrl || 'default'}`;
|
||||
const cached = ollamaDetectorCache.get(cacheKey);
|
||||
if (cached && Date.now() - cached.timestamp < OLLAMA_CACHE_TTL_MS) {
|
||||
if (process.env.DEBUG) {
|
||||
console.log('[OllamaDetector] Returning cached result for:', command);
|
||||
}
|
||||
return cached.promise;
|
||||
}
|
||||
|
||||
const promise = executeOllamaDetectorImpl(command, baseUrl);
|
||||
ollamaDetectorCache.set(cacheKey, { promise, timestamp: Date.now() });
|
||||
|
||||
// Clean up cache entry after TTL
|
||||
promise.finally(() => {
|
||||
setTimeout(() => {
|
||||
const entry = ollamaDetectorCache.get(cacheKey);
|
||||
if (entry && entry.promise === promise) {
|
||||
ollamaDetectorCache.delete(cacheKey);
|
||||
}
|
||||
}, OLLAMA_CACHE_TTL_MS);
|
||||
});
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
async function executeOllamaDetectorImpl(
|
||||
command: string,
|
||||
baseUrl?: string
|
||||
): Promise<{ success: boolean; data?: unknown; error?: string }> {
|
||||
// Use configured Python path (venv if ready, otherwise bundled/system)
|
||||
// Note: ollama_model_detector.py doesn't require dotenv, but using venv is safer
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Check,
|
||||
@@ -133,7 +133,7 @@ export function OllamaModelSelector({
|
||||
* @param {AbortSignal} [abortSignal] - Optional abort signal to cancel the request
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const checkInstalledModels = async (abortSignal?: AbortSignal) => {
|
||||
const checkInstalledModels = useCallback(async (abortSignal?: AbortSignal) => {
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
setOllamaState('checking');
|
||||
@@ -217,7 +217,7 @@ export function OllamaModelSelector({
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
}, [baseUrl]);
|
||||
|
||||
/**
|
||||
* Install Ollama by opening terminal with the official install command.
|
||||
|
||||
Reference in New Issue
Block a user