/** * Utility functions for executing functions in Worker Threads (Node.js) or Web Workers (Browser) * This implementation leverages Node.js 22's stable Worker Threads API for maximum reliability */ import { isBrowser, isNode } from './environment.js' import { prodLog } from './logger.js' // Worker pool to reuse workers const workerPool: Map = new Map() const MAX_POOL_SIZE = 4 // Adjust based on system capabilities /** * Execute a function in a separate thread * * @param fnString The function to execute as a string * @param args The arguments to pass to the function * @returns A promise that resolves with the result of the function */ export function executeInThread(fnString: string, args: any): Promise { if (isNode()) { return executeInNodeWorker(fnString, args) } else if (isBrowser() && typeof window !== 'undefined' && window.Worker) { return executeInWebWorker(fnString, args) } else { // Fallback to main thread execution try { // Try different approaches to create a function from string let fn try { // First try with 'return' prefix fn = new Function('return ' + fnString)() } catch (functionError) { console.warn( 'Fallback: Error creating function with return syntax, trying alternative approaches', functionError ) try { // Try wrapping in parentheses for function expressions fn = new Function('return (' + fnString + ')')() } catch (wrapError) { console.warn( 'Fallback: Error creating function with parentheses wrapping', wrapError ) try { // Try direct approach for named functions fn = new Function(fnString)() } catch (directError) { console.warn( 'Fallback: Direct approach failed, trying with function wrapper', directError ) try { // Try wrapping in a function that returns the function expression fn = new Function( 'return function(args) { return (' + fnString + ')(args); }' )() } catch (wrapperError) { console.error( 'Fallback: All approaches to create function failed', wrapperError ) throw new Error( 'Failed to create function from string: ' + (functionError as Error).message ) } } } } return Promise.resolve(fn(args) as T) } catch (error) { return Promise.reject(error) } } } /** * Execute a function in a Node.js Worker Thread * Optimized for Node.js 22 LTS with stable Worker Threads performance */ function executeInNodeWorker(fnString: string, args: any): Promise { return new Promise((resolve, reject) => { try { // Dynamically import worker_threads (Node.js only) import('node:worker_threads') .then(({ Worker, isMainThread, parentPort, workerData }) => { if (!isMainThread && parentPort) { // We're inside a worker, execute the function const fn = new Function('return ' + workerData.fnString)() const result = fn(workerData.args) parentPort.postMessage({ result }) return } // Get a worker from the pool or create a new one const workerId = `worker-${Math.random().toString(36).substring(2, 9)}` let worker: any if (workerPool.size < MAX_POOL_SIZE) { // Create a new worker worker = new Worker( ` import { parentPort, workerData } from 'node:worker_threads'; // Add TensorFlow.js platform patch for Node.js if (typeof global !== 'undefined') { try { // Define a custom PlatformNode class class PlatformNode { constructor() { // Create a util object with necessary methods this.util = { // Add isFloat32Array and isTypedArray directly to util isFloat32Array: (arr) => { return !!( arr instanceof Float32Array || (arr && Object.prototype.toString.call(arr) === '[object Float32Array]') ); }, isTypedArray: (arr) => { return !!(ArrayBuffer.isView(arr) && !(arr instanceof DataView)); }, // Use native TextEncoder and TextDecoder TextEncoder: TextEncoder, TextDecoder: TextDecoder }; // Initialize encoders using native constructors this.textEncoder = new TextEncoder(); this.textDecoder = new TextDecoder(); } // Define isFloat32Array directly on the instance isFloat32Array(arr) { return !!( arr instanceof Float32Array || (arr && Object.prototype.toString.call(arr) === '[object Float32Array]') ); } // Define isTypedArray directly on the instance isTypedArray(arr) { return !!(ArrayBuffer.isView(arr) && !(arr instanceof DataView)); } } // Assign the PlatformNode class to the global object global.PlatformNode = PlatformNode; // Also create an instance and assign it to global.platformNode global.platformNode = new PlatformNode(); // Ensure global.util exists and has the necessary methods if (!global.util) { global.util = {}; } // Add isFloat32Array method if it doesn't exist if (!global.util.isFloat32Array) { global.util.isFloat32Array = (arr) => { return !!( arr instanceof Float32Array || (arr && Object.prototype.toString.call(arr) === '[object Float32Array]') ); }; } // Add isTypedArray method if it doesn't exist if (!global.util.isTypedArray) { global.util.isTypedArray = (arr) => { return !!(ArrayBuffer.isView(arr) && !(arr instanceof DataView)); }; } } catch (error) { console.warn('Failed to apply TensorFlow.js platform patch:', error); } } const fn = new Function('return ' + workerData.fnString)(); const result = fn(workerData.args); parentPort.postMessage({ result }); `, { eval: true, workerData: { fnString, args } } ) workerPool.set(workerId, worker) } else { // Reuse an existing worker const poolKeys = Array.from(workerPool.keys()) const randomKey = poolKeys[Math.floor(Math.random() * poolKeys.length)] worker = workerPool.get(randomKey) // Terminate and recreate if the worker is busy if (worker._busy) { worker.terminate() worker = new Worker( ` import { parentPort, workerData } from 'node:worker_threads'; // Add TensorFlow.js platform patch for Node.js if (typeof global !== 'undefined') { try { // Define a custom PlatformNode class class PlatformNode { constructor() { // Create a util object with necessary methods this.util = { // Use native TextEncoder and TextDecoder TextEncoder: TextEncoder, TextDecoder: TextDecoder }; // Initialize encoders using native constructors this.textEncoder = new TextEncoder(); this.textDecoder = new TextDecoder(); } // Define isFloat32Array directly on the instance isFloat32Array(arr) { return !!( arr instanceof Float32Array || (arr && Object.prototype.toString.call(arr) === '[object Float32Array]') ); } // Define isTypedArray directly on the instance isTypedArray(arr) { return !!(ArrayBuffer.isView(arr) && !(arr instanceof DataView)); } } // Assign the PlatformNode class to the global object global.PlatformNode = PlatformNode; // Also create an instance and assign it to global.platformNode global.platformNode = new PlatformNode(); // Ensure global.util exists and has the necessary methods if (!global.util) { global.util = {}; } // Add isFloat32Array method if it doesn't exist if (!global.util.isFloat32Array) { global.util.isFloat32Array = (arr) => { return !!( arr instanceof Float32Array || (arr && Object.prototype.toString.call(arr) === '[object Float32Array]') ); }; } // Add isTypedArray method if it doesn't exist if (!global.util.isTypedArray) { global.util.isTypedArray = (arr) => { return !!(ArrayBuffer.isView(arr) && !(arr instanceof DataView)); }; } } catch (error) { console.warn('Failed to apply TensorFlow.js platform patch:', error); } } const fn = new Function('return ' + workerData.fnString)(); const result = fn(workerData.args); parentPort.postMessage({ result }); `, { eval: true, workerData: { fnString, args } } ) workerPool.set(randomKey, worker) } worker._busy = true } worker.on('message', (message: any) => { worker._busy = false resolve(message.result as T) }) worker.on('error', (err: any) => { worker._busy = false reject(err) }) worker.on('exit', (code: number) => { if (code !== 0) { worker._busy = false reject(new Error(`Worker stopped with exit code ${code}`)) } }) }) .catch(reject) } catch (error) { reject(error) } }) } /** * Execute a function in a Web Worker (Browser environment) */ function executeInWebWorker(fnString: string, args: any): Promise { return new Promise((resolve, reject) => { try { // Use the dedicated worker.js file instead of creating a blob // Try different approaches to locate the worker.js file let workerPath = './worker.js' try { // First try to use the import.meta.url if available (modern browsers) if (typeof import.meta !== 'undefined' && import.meta.url) { const baseUrl = import.meta.url.substring( 0, import.meta.url.lastIndexOf('/') + 1 ) workerPath = `${baseUrl}worker.js` } // Fallback to a relative path based on the unified.js location else if (typeof document !== 'undefined') { // Find the script tag that loaded unified.js const scripts = document.getElementsByTagName('script') for (let i = 0; i < scripts.length; i++) { const src = scripts[i].src if (src && src.includes('unified.js')) { // Get the directory path workerPath = src.substring(0, src.lastIndexOf('/') + 1) + 'worker.js' break } } } } catch (e) { console.warn( 'Could not determine worker path from import.meta.url, using relative path', e ) } // If we couldn't determine the path, try some common locations if (workerPath === './worker.js' && typeof window !== 'undefined') { // Try to find the worker.js in the same directory as the current page const pageUrl = window.location.href const pageDir = pageUrl.substring(0, pageUrl.lastIndexOf('/') + 1) workerPath = `${pageDir}worker.js` // Also check for dist/worker.js if (typeof document !== 'undefined') { const distWorkerPath = `${pageDir}dist/worker.js` // Create a test request to see if the file exists const xhr = new XMLHttpRequest() xhr.open('HEAD', distWorkerPath, false) try { xhr.send() if (xhr.status >= 200 && xhr.status < 300) { workerPath = distWorkerPath } } catch (e) { // Ignore errors, we'll use the default path } } } console.log('Using worker path:', workerPath) // Try to create a worker, but fall back to inline worker or main thread execution if it fails let worker: Worker try { worker = new Worker(workerPath) } catch (error) { console.warn( 'Failed to create Web Worker from file, trying inline worker:', error ) try { // Create an inline worker using a Blob const workerCode = ` // Brainy Inline Worker Script console.log('Brainy Inline Worker: Started'); self.onmessage = function (e) { try { console.log('Brainy Inline Worker: Received message', e.data ? 'with data' : 'without data'); if (!e.data || !e.data.fnString) { throw new Error('Invalid message: missing function string'); } console.log('Brainy Inline Worker: Creating function from string'); const fn = new Function('return ' + e.data.fnString)(); console.log('Brainy Inline Worker: Executing function with args'); const result = fn(e.data.args); console.log('Brainy Inline Worker: Function executed successfully, posting result'); self.postMessage({ result: result }); } catch (error) { console.error('Brainy Inline Worker: Error executing function', error); self.postMessage({ error: error.message, stack: error.stack }); } }; ` const blob = new Blob([workerCode], { type: 'application/javascript' }) const blobUrl = URL.createObjectURL(blob) worker = new Worker(blobUrl) console.log('Created inline worker using Blob URL') } catch (inlineWorkerError) { console.warn( 'Failed to create inline Web Worker, falling back to main thread execution:', inlineWorkerError ) // Execute in main thread as fallback try { const fn = new Function('return ' + fnString)() resolve(fn(args) as T) return } catch (mainThreadError) { reject(mainThreadError) return } } } // Set a timeout to prevent hanging const timeoutId = setTimeout(() => { console.warn( 'Web Worker execution timed out, falling back to main thread' ) worker.terminate() // Execute in main thread as fallback try { const fn = new Function('return ' + fnString)() resolve(fn(args) as T) } catch (mainThreadError) { reject(mainThreadError) } }, 25000) // 25 second timeout (less than the 30 second test timeout) worker.onmessage = function (e) { clearTimeout(timeoutId) if (e.data.error) { reject(new Error(e.data.error)) } else { resolve(e.data.result as T) } worker.terminate() } worker.onerror = function (e) { clearTimeout(timeoutId) console.warn( 'Web Worker error, falling back to main thread execution:', e.message ) worker.terminate() // Execute in main thread as fallback try { const fn = new Function('return ' + fnString)() resolve(fn(args) as T) } catch (mainThreadError) { reject(mainThreadError) } } worker.postMessage({ fnString, args }) } catch (error) { reject(error) } }) } /** * Clean up all worker pools * This should be called when the application is shutting down */ export function cleanupWorkerPools(): void { if (isNode()) { import('node:worker_threads') .then(({ Worker }) => { for (const worker of workerPool.values()) { worker.terminate() } workerPool.clear() console.log('Worker pools cleaned up') }) .catch(console.error) } }