Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
648
resources/js/modules/api-manager/WorkerManager.js
Normal file
648
resources/js/modules/api-manager/WorkerManager.js
Normal file
@@ -0,0 +1,648 @@
|
||||
// modules/api-manager/WorkerManager.js
|
||||
import { Logger } from '../../core/logger.js';
|
||||
|
||||
/**
|
||||
* Worker APIs Manager - Web Workers, Service Workers, Shared Workers
|
||||
*/
|
||||
export class WorkerManager {
|
||||
constructor(config = {}) {
|
||||
this.config = config;
|
||||
this.activeWorkers = new Map();
|
||||
this.messageHandlers = new Map();
|
||||
this.workerScripts = new Map();
|
||||
|
||||
// Check API support
|
||||
this.support = {
|
||||
webWorkers: 'Worker' in window,
|
||||
serviceWorker: 'serviceWorker' in navigator,
|
||||
sharedWorker: 'SharedWorker' in window,
|
||||
offscreenCanvas: 'OffscreenCanvas' in window
|
||||
};
|
||||
|
||||
Logger.info('[WorkerManager] Initialized with support:', this.support);
|
||||
}
|
||||
|
||||
/**
|
||||
* Web Workers for background processing
|
||||
*/
|
||||
web = {
|
||||
// Create a new Web Worker
|
||||
create: (script, options = {}) => {
|
||||
if (!this.support.webWorkers) {
|
||||
throw new Error('Web Workers not supported');
|
||||
}
|
||||
|
||||
let worker;
|
||||
const workerId = this.generateId('worker');
|
||||
|
||||
try {
|
||||
// Handle different script types
|
||||
if (typeof script === 'string') {
|
||||
// URL string
|
||||
worker = new Worker(script, options);
|
||||
} else if (typeof script === 'function') {
|
||||
// Function to blob
|
||||
const blob = this.createWorkerBlob(script);
|
||||
worker = new Worker(URL.createObjectURL(blob), options);
|
||||
} else if (script instanceof Blob) {
|
||||
// Blob directly
|
||||
worker = new Worker(URL.createObjectURL(script), options);
|
||||
} else {
|
||||
throw new Error('Invalid script type');
|
||||
}
|
||||
|
||||
// Enhanced worker wrapper
|
||||
const workerWrapper = {
|
||||
id: workerId,
|
||||
worker,
|
||||
|
||||
// Send message to worker
|
||||
send: (message, transfer = null) => {
|
||||
if (transfer) {
|
||||
worker.postMessage(message, transfer);
|
||||
} else {
|
||||
worker.postMessage(message);
|
||||
}
|
||||
Logger.info(`[WorkerManager] Message sent to worker: ${workerId}`);
|
||||
},
|
||||
|
||||
// Listen for messages
|
||||
onMessage: (callback) => {
|
||||
worker.addEventListener('message', (event) => {
|
||||
callback(event.data, event);
|
||||
});
|
||||
},
|
||||
|
||||
// Handle errors
|
||||
onError: (callback) => {
|
||||
worker.addEventListener('error', callback);
|
||||
worker.addEventListener('messageerror', callback);
|
||||
},
|
||||
|
||||
// Terminate worker
|
||||
terminate: () => {
|
||||
worker.terminate();
|
||||
this.activeWorkers.delete(workerId);
|
||||
Logger.info(`[WorkerManager] Worker terminated: ${workerId}`);
|
||||
},
|
||||
|
||||
// Execute function in worker
|
||||
execute: (fn, data = null) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const messageId = this.generateId('msg');
|
||||
|
||||
const handler = (event) => {
|
||||
if (event.data.id === messageId) {
|
||||
worker.removeEventListener('message', handler);
|
||||
|
||||
if (event.data.error) {
|
||||
reject(new Error(event.data.error));
|
||||
} else {
|
||||
resolve(event.data.result);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
worker.addEventListener('message', handler);
|
||||
|
||||
worker.postMessage({
|
||||
id: messageId,
|
||||
type: 'execute',
|
||||
function: fn.toString(),
|
||||
data
|
||||
});
|
||||
|
||||
// Timeout after 30 seconds
|
||||
setTimeout(() => {
|
||||
worker.removeEventListener('message', handler);
|
||||
reject(new Error('Worker execution timeout'));
|
||||
}, 30000);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
this.activeWorkers.set(workerId, workerWrapper);
|
||||
Logger.info(`[WorkerManager] Web Worker created: ${workerId}`);
|
||||
|
||||
return workerWrapper;
|
||||
|
||||
} catch (error) {
|
||||
Logger.error('[WorkerManager] Worker creation failed:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Create worker pool for parallel processing
|
||||
createPool: (script, poolSize = navigator.hardwareConcurrency || 4, options = {}) => {
|
||||
const workers = [];
|
||||
|
||||
for (let i = 0; i < poolSize; i++) {
|
||||
workers.push(this.web.create(script, options));
|
||||
}
|
||||
|
||||
let currentWorker = 0;
|
||||
|
||||
const pool = {
|
||||
workers,
|
||||
|
||||
// Execute task on next available worker
|
||||
execute: async (fn, data = null) => {
|
||||
const worker = workers[currentWorker];
|
||||
currentWorker = (currentWorker + 1) % workers.length;
|
||||
|
||||
return worker.execute(fn, data);
|
||||
},
|
||||
|
||||
// Broadcast message to all workers
|
||||
broadcast: (message) => {
|
||||
workers.forEach(worker => {
|
||||
worker.send(message);
|
||||
});
|
||||
},
|
||||
|
||||
// Terminate all workers
|
||||
terminate: () => {
|
||||
workers.forEach(worker => {
|
||||
worker.terminate();
|
||||
});
|
||||
workers.length = 0;
|
||||
Logger.info('[WorkerManager] Worker pool terminated');
|
||||
}
|
||||
};
|
||||
|
||||
Logger.info(`[WorkerManager] Worker pool created with ${poolSize} workers`);
|
||||
return pool;
|
||||
},
|
||||
|
||||
// Common worker tasks
|
||||
tasks: {
|
||||
// Heavy computation
|
||||
compute: (fn, data) => {
|
||||
const workerCode = `
|
||||
self.addEventListener('message', function(e) {
|
||||
const { id, function: fnString, data } = e.data;
|
||||
|
||||
try {
|
||||
const fn = new Function('return ' + fnString)();
|
||||
const result = fn(data);
|
||||
self.postMessage({ id, result });
|
||||
} catch (error) {
|
||||
self.postMessage({ id, error: error.message });
|
||||
}
|
||||
});
|
||||
`;
|
||||
|
||||
const worker = this.web.create(workerCode);
|
||||
return worker.execute(fn, data);
|
||||
},
|
||||
|
||||
// Image processing
|
||||
processImage: (imageData, filters) => {
|
||||
const workerCode = `
|
||||
self.addEventListener('message', function(e) {
|
||||
const { id, data: { imageData, filters } } = e.data;
|
||||
|
||||
try {
|
||||
const pixels = imageData.data;
|
||||
|
||||
for (let i = 0; i < pixels.length; i += 4) {
|
||||
// Apply filters
|
||||
if (filters.brightness) {
|
||||
pixels[i] *= filters.brightness; // R
|
||||
pixels[i + 1] *= filters.brightness; // G
|
||||
pixels[i + 2] *= filters.brightness; // B
|
||||
}
|
||||
|
||||
if (filters.contrast) {
|
||||
const factor = (259 * (filters.contrast * 255 + 255)) / (255 * (259 - filters.contrast * 255));
|
||||
pixels[i] = factor * (pixels[i] - 128) + 128;
|
||||
pixels[i + 1] = factor * (pixels[i + 1] - 128) + 128;
|
||||
pixels[i + 2] = factor * (pixels[i + 2] - 128) + 128;
|
||||
}
|
||||
|
||||
if (filters.grayscale) {
|
||||
const gray = 0.299 * pixels[i] + 0.587 * pixels[i + 1] + 0.114 * pixels[i + 2];
|
||||
pixels[i] = gray;
|
||||
pixels[i + 1] = gray;
|
||||
pixels[i + 2] = gray;
|
||||
}
|
||||
}
|
||||
|
||||
self.postMessage({ id, result: imageData }, [imageData.data.buffer]);
|
||||
} catch (error) {
|
||||
self.postMessage({ id, error: error.message });
|
||||
}
|
||||
});
|
||||
`;
|
||||
|
||||
const worker = this.web.create(workerCode);
|
||||
return worker.execute(null, { imageData, filters });
|
||||
},
|
||||
|
||||
// Data processing
|
||||
processData: (data, processor) => {
|
||||
const workerCode = `
|
||||
self.addEventListener('message', function(e) {
|
||||
const { id, data, function: processorString } = e.data;
|
||||
|
||||
try {
|
||||
const processor = new Function('return ' + processorString)();
|
||||
const result = data.map(processor);
|
||||
self.postMessage({ id, result });
|
||||
} catch (error) {
|
||||
self.postMessage({ id, error: error.message });
|
||||
}
|
||||
});
|
||||
`;
|
||||
|
||||
const worker = this.web.create(workerCode);
|
||||
return worker.execute(processor, data);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Service Workers for caching and background sync
|
||||
*/
|
||||
service = {
|
||||
// Register service worker
|
||||
register: async (scriptURL, options = {}) => {
|
||||
if (!this.support.serviceWorker) {
|
||||
throw new Error('Service Worker not supported');
|
||||
}
|
||||
|
||||
try {
|
||||
const registration = await navigator.serviceWorker.register(scriptURL, options);
|
||||
|
||||
Logger.info('[WorkerManager] Service Worker registered:', scriptURL);
|
||||
|
||||
// Enhanced registration wrapper
|
||||
return {
|
||||
registration,
|
||||
scope: registration.scope,
|
||||
|
||||
// Update service worker
|
||||
update: () => registration.update(),
|
||||
|
||||
// Unregister service worker
|
||||
unregister: () => registration.unregister(),
|
||||
|
||||
// Send message to service worker
|
||||
postMessage: (message) => {
|
||||
if (registration.active) {
|
||||
registration.active.postMessage(message);
|
||||
}
|
||||
},
|
||||
|
||||
// Listen for updates
|
||||
onUpdate: (callback) => {
|
||||
registration.addEventListener('updatefound', () => {
|
||||
const newWorker = registration.installing;
|
||||
|
||||
newWorker.addEventListener('statechange', () => {
|
||||
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
|
||||
callback(newWorker);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// Check for updates
|
||||
checkForUpdates: () => {
|
||||
return registration.update();
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
Logger.error('[WorkerManager] Service Worker registration failed:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Get registration
|
||||
getRegistration: async (scope = '/') => {
|
||||
if (!this.support.serviceWorker) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return await navigator.serviceWorker.getRegistration(scope);
|
||||
} catch (error) {
|
||||
Logger.error('[WorkerManager] Get registration failed:', error);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
// Get all registrations
|
||||
getRegistrations: async () => {
|
||||
if (!this.support.serviceWorker) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
return await navigator.serviceWorker.getRegistrations();
|
||||
} catch (error) {
|
||||
Logger.error('[WorkerManager] Get registrations failed:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Shared Workers for cross-tab communication
|
||||
*/
|
||||
shared = {
|
||||
// Create shared worker
|
||||
create: (script, options = {}) => {
|
||||
if (!this.support.sharedWorker) {
|
||||
throw new Error('Shared Worker not supported');
|
||||
}
|
||||
|
||||
try {
|
||||
const sharedWorker = new SharedWorker(script, options);
|
||||
const port = sharedWorker.port;
|
||||
const workerId = this.generateId('shared');
|
||||
|
||||
// Start the port
|
||||
port.start();
|
||||
|
||||
const workerWrapper = {
|
||||
id: workerId,
|
||||
worker: sharedWorker,
|
||||
port,
|
||||
|
||||
// Send message
|
||||
send: (message, transfer = null) => {
|
||||
if (transfer) {
|
||||
port.postMessage(message, transfer);
|
||||
} else {
|
||||
port.postMessage(message);
|
||||
}
|
||||
},
|
||||
|
||||
// Listen for messages
|
||||
onMessage: (callback) => {
|
||||
port.addEventListener('message', (event) => {
|
||||
callback(event.data, event);
|
||||
});
|
||||
},
|
||||
|
||||
// Handle errors
|
||||
onError: (callback) => {
|
||||
sharedWorker.addEventListener('error', callback);
|
||||
port.addEventListener('messageerror', callback);
|
||||
},
|
||||
|
||||
// Close connection
|
||||
close: () => {
|
||||
port.close();
|
||||
this.activeWorkers.delete(workerId);
|
||||
Logger.info(`[WorkerManager] Shared Worker closed: ${workerId}`);
|
||||
}
|
||||
};
|
||||
|
||||
this.activeWorkers.set(workerId, workerWrapper);
|
||||
Logger.info(`[WorkerManager] Shared Worker created: ${workerId}`);
|
||||
|
||||
return workerWrapper;
|
||||
} catch (error) {
|
||||
Logger.error('[WorkerManager] Shared Worker creation failed:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Offscreen Canvas for worker-based rendering
|
||||
*/
|
||||
offscreen = {
|
||||
// Create offscreen canvas worker
|
||||
create: (canvas, workerScript = null) => {
|
||||
if (!this.support.offscreenCanvas) {
|
||||
throw new Error('Offscreen Canvas not supported');
|
||||
}
|
||||
|
||||
try {
|
||||
const offscreenCanvas = canvas.transferControlToOffscreen();
|
||||
|
||||
const defaultWorkerScript = `
|
||||
self.addEventListener('message', function(e) {
|
||||
const { canvas, type, data } = e.data;
|
||||
|
||||
if (type === 'init') {
|
||||
self.canvas = canvas;
|
||||
self.ctx = canvas.getContext('2d');
|
||||
}
|
||||
|
||||
if (type === 'draw' && self.ctx) {
|
||||
// Basic drawing operations
|
||||
const { operations } = data;
|
||||
|
||||
operations.forEach(op => {
|
||||
switch (op.type) {
|
||||
case 'fillRect':
|
||||
self.ctx.fillRect(...op.args);
|
||||
break;
|
||||
case 'strokeRect':
|
||||
self.ctx.strokeRect(...op.args);
|
||||
break;
|
||||
case 'fillText':
|
||||
self.ctx.fillText(...op.args);
|
||||
break;
|
||||
case 'setFillStyle':
|
||||
self.ctx.fillStyle = op.value;
|
||||
break;
|
||||
case 'setStrokeStyle':
|
||||
self.ctx.strokeStyle = op.value;
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
`;
|
||||
|
||||
const script = workerScript || defaultWorkerScript;
|
||||
const worker = this.web.create(script);
|
||||
|
||||
// Initialize worker with canvas
|
||||
worker.send({
|
||||
type: 'init',
|
||||
canvas: offscreenCanvas
|
||||
}, [offscreenCanvas]);
|
||||
|
||||
return {
|
||||
worker,
|
||||
|
||||
// Draw operations
|
||||
draw: (operations) => {
|
||||
worker.send({
|
||||
type: 'draw',
|
||||
data: { operations }
|
||||
});
|
||||
},
|
||||
|
||||
// Send custom message
|
||||
send: (message) => worker.send(message),
|
||||
|
||||
// Listen for messages
|
||||
onMessage: (callback) => worker.onMessage(callback),
|
||||
|
||||
// Terminate worker
|
||||
terminate: () => worker.terminate()
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
Logger.error('[WorkerManager] Offscreen Canvas creation failed:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Helper methods
|
||||
|
||||
createWorkerBlob(fn) {
|
||||
const code = `
|
||||
(function() {
|
||||
const workerFunction = ${fn.toString()};
|
||||
|
||||
if (typeof workerFunction === 'function') {
|
||||
// If function expects to be called immediately
|
||||
if (workerFunction.length === 0) {
|
||||
workerFunction();
|
||||
}
|
||||
}
|
||||
|
||||
// Standard worker message handling
|
||||
self.addEventListener('message', function(e) {
|
||||
if (e.data.type === 'execute' && e.data.function) {
|
||||
try {
|
||||
const fn = new Function('return ' + e.data.function)();
|
||||
const result = fn(e.data.data);
|
||||
self.postMessage({
|
||||
id: e.data.id,
|
||||
result
|
||||
});
|
||||
} catch (error) {
|
||||
self.postMessage({
|
||||
id: e.data.id,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
`;
|
||||
|
||||
return new Blob([code], { type: 'application/javascript' });
|
||||
}
|
||||
|
||||
generateId(prefix = 'worker') {
|
||||
return `${prefix}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Terminate all active workers
|
||||
*/
|
||||
terminateAll() {
|
||||
this.activeWorkers.forEach(worker => {
|
||||
if (worker.terminate) {
|
||||
worker.terminate();
|
||||
} else if (worker.close) {
|
||||
worker.close();
|
||||
}
|
||||
});
|
||||
this.activeWorkers.clear();
|
||||
Logger.info('[WorkerManager] All workers terminated');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get worker statistics
|
||||
*/
|
||||
getStats() {
|
||||
const workers = Array.from(this.activeWorkers.values());
|
||||
|
||||
return {
|
||||
total: workers.length,
|
||||
byType: workers.reduce((acc, worker) => {
|
||||
const type = worker.id.split('_')[0];
|
||||
acc[type] = (acc[type] || 0) + 1;
|
||||
return acc;
|
||||
}, {}),
|
||||
support: this.support,
|
||||
hardwareConcurrency: navigator.hardwareConcurrency || 'unknown'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create common worker utilities
|
||||
*/
|
||||
utils = {
|
||||
// Benchmark function performance
|
||||
benchmark: async (fn, data, iterations = 1000) => {
|
||||
const worker = this.web.create(() => {
|
||||
self.addEventListener('message', (e) => {
|
||||
const { id, function: fnString, data, iterations } = e.data;
|
||||
|
||||
try {
|
||||
const fn = new Function('return ' + fnString)();
|
||||
const start = performance.now();
|
||||
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
fn(data);
|
||||
}
|
||||
|
||||
const end = performance.now();
|
||||
const totalTime = end - start;
|
||||
const avgTime = totalTime / iterations;
|
||||
|
||||
self.postMessage({
|
||||
id,
|
||||
result: {
|
||||
totalTime,
|
||||
avgTime,
|
||||
iterations,
|
||||
opsPerSecond: 1000 / avgTime
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
self.postMessage({ id, error: error.message });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const result = await worker.execute(fn, data, iterations);
|
||||
worker.terminate();
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
// Parallel array processing
|
||||
parallelMap: async (array, fn, chunkSize = null) => {
|
||||
const chunks = chunkSize || Math.ceil(array.length / (navigator.hardwareConcurrency || 4));
|
||||
const pool = this.web.createPool(() => {
|
||||
self.addEventListener('message', (e) => {
|
||||
const { id, data: { chunk, function: fnString } } = e.data;
|
||||
|
||||
try {
|
||||
const fn = new Function('return ' + fnString)();
|
||||
const result = chunk.map(fn);
|
||||
self.postMessage({ id, result });
|
||||
} catch (error) {
|
||||
self.postMessage({ id, error: error.message });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const promises = [];
|
||||
|
||||
for (let i = 0; i < array.length; i += chunks) {
|
||||
const chunk = array.slice(i, i + chunks);
|
||||
promises.push(pool.execute(fn, chunk));
|
||||
}
|
||||
|
||||
const results = await Promise.all(promises);
|
||||
pool.terminate();
|
||||
|
||||
return results.flat();
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user