2025-08-26 12:32:21 -07:00
/ * *
* Utility functions for executing functions in Worker Threads ( Node . js ) or Web Workers ( Browser )
2025-10-20 11:43:31 -07:00
* This implementation leverages Node . js 22 ' s stable Worker Threads API for maximum reliability
2025-08-26 12:32:21 -07:00
* /
import { isBrowser , isNode } from './environment.js'
import { prodLog } from './logger.js'
// Worker pool to reuse workers
const workerPool : Map < string , any > = 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 < T > ( fnString : string , args : any ) : Promise < T > {
if ( isNode ( ) ) {
return executeInNodeWorker < T > ( fnString , args )
} else if ( isBrowser ( ) && typeof window !== 'undefined' && window . Worker ) {
return executeInWebWorker < T > ( 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
2025-10-20 11:43:31 -07:00
* Optimized for Node . js 22 LTS with stable Worker Threads performance
2025-08-26 12:32:21 -07:00
* /
function executeInNodeWorker < T > ( fnString : string , args : any ) : Promise < T > {
return new Promise < T > ( ( 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 < T > ( fnString : string , args : any ) : Promise < T > {
return new Promise < T > ( ( 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 )
}
}