CRITICAL CHECKPOINT - DO NOT PUSH TO GITHUB Recovery Status: - Successfully recovered brainy.ts from compiled JavaScript - All core v3.0 API methods functional (add, get, update, delete, relate, find, etc.) - Neural subsystem intact (562KB embedded patterns, NLP working) - Augmentation pipeline operational (20+ augmentations) - HNSW clustering system complete - Triple Intelligence compiled (needs constructor fix) - Test suite validates functionality Changes preserved: - 898 files with changes from last 3 days - 144,475 insertions - All augmentation improvements - All test coverage enhancements - Complete v3.0 feature set This is a LOCAL checkpoint only - contains recovered work after corruption incident. Created backup in .backups/brainy-full-20250910-151314.tar.gz Branch: recovery-checkpoint-20250910-151433 Date: Wed Sep 10 03:18:04 PM PDT 2025
166 lines
No EOL
6.5 KiB
JavaScript
166 lines
No EOL
6.5 KiB
JavaScript
/**
|
||
* Universal Memory Manager for Embeddings
|
||
*
|
||
* Works in ALL environments: Node.js, browsers, serverless, workers
|
||
* Solves transformers.js memory leak with environment-specific strategies
|
||
*/
|
||
import { getModelPrecision } from '../config/modelPrecisionManager.js';
|
||
// Environment detection
|
||
const isNode = typeof process !== 'undefined' && process.versions?.node;
|
||
const isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined';
|
||
const isServerless = typeof process !== 'undefined' && (process.env.VERCEL ||
|
||
process.env.NETLIFY ||
|
||
process.env.AWS_LAMBDA_FUNCTION_NAME ||
|
||
process.env.FUNCTIONS_WORKER_RUNTIME);
|
||
export class UniversalMemoryManager {
|
||
constructor() {
|
||
// CRITICAL FIX: Never use worker threads with ONNX Runtime
|
||
// Worker threads cause HandleScope V8 API errors due to isolate issues
|
||
// Always use direct embedding on main thread for ONNX compatibility
|
||
this.embeddingFunction = null;
|
||
this.embedCount = 0;
|
||
this.restartCount = 0;
|
||
this.lastRestart = 0;
|
||
if (isServerless) {
|
||
this.strategy = 'serverless-restart';
|
||
this.maxEmbeddings = 50; // Restart frequently in serverless
|
||
}
|
||
else if (isNode && !isBrowser) {
|
||
// CHANGED: Use direct strategy instead of node-worker to avoid V8 isolate issues
|
||
this.strategy = 'node-direct';
|
||
this.maxEmbeddings = 200; // Main thread can handle more with single model instance
|
||
}
|
||
else if (isBrowser) {
|
||
this.strategy = 'browser-dispose';
|
||
this.maxEmbeddings = 25; // Browser memory is limited
|
||
}
|
||
else {
|
||
this.strategy = 'fallback-dispose';
|
||
this.maxEmbeddings = 75;
|
||
}
|
||
console.log(`🧠 Universal Memory Manager: Using ${this.strategy} strategy`);
|
||
console.log('✅ UNIVERSAL: Memory-safe embedding system initialized');
|
||
}
|
||
async getEmbeddingFunction() {
|
||
return async (data) => {
|
||
return this.embed(data);
|
||
};
|
||
}
|
||
async embed(data) {
|
||
// Check if we need to restart/cleanup
|
||
await this.checkMemoryLimits();
|
||
// Ensure embedding function is available
|
||
await this.ensureEmbeddingFunction();
|
||
// Perform embedding
|
||
const result = await this.embeddingFunction.embed(data);
|
||
this.embedCount++;
|
||
return result;
|
||
}
|
||
async checkMemoryLimits() {
|
||
if (this.embedCount >= this.maxEmbeddings) {
|
||
console.log(`🔄 Memory cleanup: ${this.embedCount} embeddings processed`);
|
||
await this.cleanup();
|
||
}
|
||
}
|
||
async ensureEmbeddingFunction() {
|
||
if (this.embeddingFunction) {
|
||
return;
|
||
}
|
||
switch (this.strategy) {
|
||
case 'node-direct':
|
||
await this.initNodeDirect();
|
||
break;
|
||
case 'serverless-restart':
|
||
await this.initServerless();
|
||
break;
|
||
case 'browser-dispose':
|
||
await this.initBrowser();
|
||
break;
|
||
default:
|
||
await this.initFallback();
|
||
}
|
||
}
|
||
async initNodeDirect() {
|
||
if (isNode) {
|
||
// CRITICAL: Use direct embedding to avoid worker thread V8 isolate issues
|
||
// This prevents HandleScope errors and ensures single model instance
|
||
console.log('✅ Using Node.js direct embedding (main thread - ONNX compatible)');
|
||
await this.initDirect();
|
||
}
|
||
}
|
||
async initServerless() {
|
||
// In serverless, use direct embedding but restart more aggressively
|
||
await this.initDirect();
|
||
console.log('✅ Using serverless strategy with aggressive cleanup');
|
||
}
|
||
async initBrowser() {
|
||
// In browser, use direct embedding with disposal
|
||
await this.initDirect();
|
||
console.log('✅ Using browser strategy with disposal');
|
||
}
|
||
async initFallback() {
|
||
await this.initDirect();
|
||
console.log('✅ Using fallback direct embedding strategy');
|
||
}
|
||
async initDirect() {
|
||
try {
|
||
// Dynamic import to handle different environments
|
||
const { TransformerEmbedding } = await import('../utils/embedding.js');
|
||
this.embeddingFunction = new TransformerEmbedding({
|
||
verbose: false,
|
||
precision: getModelPrecision(), // Use centrally managed precision
|
||
localFilesOnly: process.env.BRAINY_ALLOW_REMOTE_MODELS !== 'true'
|
||
});
|
||
await this.embeddingFunction.init();
|
||
console.log('✅ Direct embedding function initialized');
|
||
}
|
||
catch (error) {
|
||
throw new Error(`Failed to initialize embedding function: ${error instanceof Error ? error.message : String(error)}`);
|
||
}
|
||
}
|
||
async cleanup() {
|
||
const startTime = Date.now();
|
||
// SingletonModelManager persists - we just reset our counters
|
||
// The singleton model stays alive for consistency across all operations
|
||
// Reset counters
|
||
this.embedCount = 0;
|
||
this.restartCount++;
|
||
this.lastRestart = Date.now();
|
||
const cleanupTime = Date.now() - startTime;
|
||
console.log(`🧹 Memory counters reset in ${cleanupTime}ms (strategy: ${this.strategy})`);
|
||
console.log('ℹ️ Singleton model persists for consistency across all operations');
|
||
}
|
||
getMemoryStats() {
|
||
let memoryUsage = 'unknown';
|
||
// Get memory stats based on environment
|
||
if (isNode && typeof process !== 'undefined') {
|
||
const mem = process.memoryUsage();
|
||
memoryUsage = `${(mem.heapUsed / 1024 / 1024).toFixed(2)} MB`;
|
||
}
|
||
else if (isBrowser && performance.memory) {
|
||
const mem = performance.memory;
|
||
memoryUsage = `${(mem.usedJSHeapSize / 1024 / 1024).toFixed(2)} MB`;
|
||
}
|
||
return {
|
||
embeddings: this.embedCount,
|
||
memoryUsage,
|
||
restarts: this.restartCount,
|
||
strategy: this.strategy
|
||
};
|
||
}
|
||
async dispose() {
|
||
// SingletonModelManager persists - nothing to dispose
|
||
console.log('ℹ️ Universal Memory Manager: Singleton model persists');
|
||
}
|
||
}
|
||
// Export singleton instance
|
||
export const universalMemoryManager = new UniversalMemoryManager();
|
||
// Export convenience function
|
||
export async function getUniversalEmbeddingFunction() {
|
||
return universalMemoryManager.getEmbeddingFunction();
|
||
}
|
||
// Export memory stats function
|
||
export function getEmbeddingMemoryStats() {
|
||
return universalMemoryManager.getMemoryStats();
|
||
}
|
||
//# sourceMappingURL=universal-memory-manager.js.map
|