javascript

exercises

exercises.js
/**
 * Web Workers - Exercises
 * Practice working with background threads
 *
 * Note: These exercises use mock workers for Node.js compatibility.
 * In browsers, you would use actual Web Workers.
 */

// Mock Worker for exercises
class MockWorker {
  constructor(workerFn) {
    this.workerFn = workerFn;
    this.onmessage = null;
    this.onerror = null;
  }

  postMessage(data) {
    setTimeout(() => {
      try {
        const result = this.workerFn(data);
        if (this.onmessage) {
          this.onmessage({ data: result });
        }
      } catch (error) {
        if (this.onerror) {
          this.onerror(error);
        }
      }
    }, 0);
  }

  terminate() {
    this.onmessage = null;
    this.onerror = null;
  }
}

// =============================================================================
// EXERCISE 1: Promise-Based Worker Wrapper
// Create a wrapper that makes workers return promises
// =============================================================================

/*
 * TODO: Create promisifyWorker(workerFn) that:
 * - Returns a function that accepts data and returns a Promise
 * - Resolves with worker result
 * - Rejects on worker error
 * - Supports timeout option
 */

function promisifyWorker(workerFn, options = {}) {
  // YOUR CODE HERE:
}

// SOLUTION:
/*
function promisifyWorker(workerFn, options = {}) {
    const { timeout = 30000 } = options;
    
    return function(data) {
        return new Promise((resolve, reject) => {
            const worker = new MockWorker(workerFn);
            
            const timeoutId = setTimeout(() => {
                worker.terminate();
                reject(new Error(`Worker timeout after ${timeout}ms`));
            }, timeout);
            
            worker.onmessage = (event) => {
                clearTimeout(timeoutId);
                resolve(event.data);
            };
            
            worker.onerror = (error) => {
                clearTimeout(timeoutId);
                reject(error);
            };
            
            worker.postMessage(data);
        });
    };
}
*/

// =============================================================================
// EXERCISE 2: Worker Pool with Load Balancing
// Implement a pool that distributes work evenly
// =============================================================================

/*
 * TODO: Create WorkerPool class that:
 * - Manages multiple workers
 * - Distributes tasks round-robin or least-busy
 * - Tracks worker utilization
 * - Supports dynamic scaling
 */

class WorkerPool {
  // YOUR CODE HERE:
}

// SOLUTION:
/*
class WorkerPool {
    constructor(workerFn, options = {}) {
        this.workerFn = workerFn;
        this.minWorkers = options.minWorkers || 1;
        this.maxWorkers = options.maxWorkers || 8;
        this.workers = [];
        this.taskQueue = [];
        this.roundRobinIndex = 0;
        
        // Initialize minimum workers
        for (let i = 0; i < this.minWorkers; i++) {
            this.addWorker();
        }
    }
    
    addWorker() {
        if (this.workers.length >= this.maxWorkers) return false;
        
        const worker = {
            instance: new MockWorker(this.workerFn),
            busy: false,
            tasksCompleted: 0,
            currentTask: null
        };
        
        this.workers.push(worker);
        return true;
    }
    
    removeWorker() {
        if (this.workers.length <= this.minWorkers) return false;
        
        const idleWorker = this.workers.find(w => !w.busy);
        if (idleWorker) {
            idleWorker.instance.terminate();
            this.workers = this.workers.filter(w => w !== idleWorker);
            return true;
        }
        return false;
    }
    
    getNextWorker(strategy = 'round-robin') {
        if (strategy === 'round-robin') {
            let attempts = 0;
            while (attempts < this.workers.length) {
                const worker = this.workers[this.roundRobinIndex];
                this.roundRobinIndex = (this.roundRobinIndex + 1) % this.workers.length;
                
                if (!worker.busy) {
                    return worker;
                }
                attempts++;
            }
            return null; // All busy
        }
        
        if (strategy === 'least-busy') {
            return this.workers
                .filter(w => !w.busy)
                .sort((a, b) => a.tasksCompleted - b.tasksCompleted)[0] || null;
        }
        
        return null;
    }
    
    async execute(data) {
        return new Promise((resolve, reject) => {
            const task = { data, resolve, reject };
            
            const worker = this.getNextWorker();
            
            if (worker) {
                this.runTask(worker, task);
            } else {
                // Scale up if possible
                if (this.addWorker()) {
                    this.runTask(this.workers[this.workers.length - 1], task);
                } else {
                    // Queue the task
                    this.taskQueue.push(task);
                }
            }
        });
    }
    
    runTask(worker, task) {
        worker.busy = true;
        worker.currentTask = task;
        
        worker.instance.onmessage = (event) => {
            worker.busy = false;
            worker.tasksCompleted++;
            worker.currentTask = null;
            task.resolve(event.data);
            
            // Process queued task
            if (this.taskQueue.length > 0) {
                const nextTask = this.taskQueue.shift();
                this.runTask(worker, nextTask);
            }
        };
        
        worker.instance.onerror = (error) => {
            worker.busy = false;
            worker.currentTask = null;
            task.reject(error);
            
            // Process queued task
            if (this.taskQueue.length > 0) {
                const nextTask = this.taskQueue.shift();
                this.runTask(worker, nextTask);
            }
        };
        
        worker.instance.postMessage(task.data);
    }
    
    getStats() {
        return {
            totalWorkers: this.workers.length,
            busyWorkers: this.workers.filter(w => w.busy).length,
            idleWorkers: this.workers.filter(w => !w.busy).length,
            queuedTasks: this.taskQueue.length,
            totalTasksCompleted: this.workers.reduce((sum, w) => sum + w.tasksCompleted, 0)
        };
    }
    
    terminate() {
        this.workers.forEach(w => w.instance.terminate());
        this.workers = [];
        this.taskQueue = [];
    }
}
*/

// =============================================================================
// EXERCISE 3: Worker RPC System
// Implement a remote procedure call system over workers
// =============================================================================

/*
 * TODO: Create WorkerRPC class that:
 * - Allows calling named methods on worker
 * - Supports request/response with IDs
 * - Handles timeouts and errors
 * - Supports method registration on worker side
 */

class WorkerRPC {
  // YOUR CODE HERE:
}

// SOLUTION:
/*
class WorkerRPC {
    constructor(methods = {}) {
        this.methods = methods;
        this.pending = new Map();
        this.nextId = 0;
        
        // Worker handler function
        this.workerHandler = (message) => {
            const { id, method, args } = message;
            
            if (!this.methods[method]) {
                return { id, error: `Unknown method: ${method}` };
            }
            
            try {
                const result = this.methods[method](...args);
                return { id, result };
            } catch (error) {
                return { id, error: error.message };
            }
        };
        
        this.worker = new MockWorker(this.workerHandler);
        
        this.worker.onmessage = (event) => {
            const { id, result, error } = event.data;
            
            if (this.pending.has(id)) {
                const { resolve, reject } = this.pending.get(id);
                this.pending.delete(id);
                
                if (error) {
                    reject(new Error(error));
                } else {
                    resolve(result);
                }
            }
        };
    }
    
    register(name, fn) {
        this.methods[name] = fn;
    }
    
    call(method, ...args) {
        const id = this.nextId++;
        
        return new Promise((resolve, reject) => {
            const timeout = setTimeout(() => {
                this.pending.delete(id);
                reject(new Error(`RPC timeout: ${method}`));
            }, 5000);
            
            this.pending.set(id, {
                resolve: (result) => {
                    clearTimeout(timeout);
                    resolve(result);
                },
                reject: (error) => {
                    clearTimeout(timeout);
                    reject(error);
                }
            });
            
            this.worker.postMessage({ id, method, args });
        });
    }
    
    createProxy() {
        return new Proxy({}, {
            get: (_, method) => {
                return (...args) => this.call(method, ...args);
            }
        });
    }
}
*/

// =============================================================================
// EXERCISE 4: Parallel Map with Workers
// Implement parallel array processing using workers
// =============================================================================

/*
 * TODO: Create parallelMap(array, mapFn, options) that:
 * - Splits array into chunks
 * - Processes chunks in parallel workers
 * - Combines results in correct order
 * - Supports progress callback
 */

async function parallelMap(array, mapFn, options = {}) {
  // YOUR CODE HERE:
}

// SOLUTION:
/*
async function parallelMap(array, mapFn, options = {}) {
    const {
        workerCount = 4,
        chunkSize = Math.ceil(array.length / workerCount),
        onProgress = () => {}
    } = options;
    
    // Create worker function
    const workerFn = (data) => {
        const { chunk, startIndex } = data;
        // Note: In real worker, mapFn would need to be serialized
        const results = chunk.map((item, i) => ({
            index: startIndex + i,
            value: item * 2 // Simplified - in real code, use mapFn
        }));
        return results;
    };
    
    // Create chunks
    const chunks = [];
    for (let i = 0; i < array.length; i += chunkSize) {
        chunks.push({
            chunk: array.slice(i, i + chunkSize),
            startIndex: i
        });
    }
    
    // Process in parallel
    const pool = new WorkerPool(workerFn, { maxWorkers: workerCount });
    let completed = 0;
    
    const chunkResults = await Promise.all(
        chunks.map(async (chunk) => {
            const result = await pool.execute(chunk);
            completed++;
            onProgress({
                completed,
                total: chunks.length,
                percent: Math.round((completed / chunks.length) * 100)
            });
            return result;
        })
    );
    
    pool.terminate();
    
    // Combine results in order
    const results = new Array(array.length);
    for (const chunkResult of chunkResults) {
        for (const { index, value } of chunkResult) {
            results[index] = value;
        }
    }
    
    return results;
}
*/

// =============================================================================
// EXERCISE 5: Worker Coordinator
// Coordinate multiple workers for a complex task
// =============================================================================

/*
 * TODO: Create WorkerCoordinator class that:
 * - Manages workers with different roles
 * - Supports pipeline processing (output of one → input of next)
 * - Handles fan-out/fan-in patterns
 * - Tracks overall progress
 */

class WorkerCoordinator {
  // YOUR CODE HERE:
}

// SOLUTION:
/*
class WorkerCoordinator {
    constructor() {
        this.stages = new Map();
        this.connections = [];
    }
    
    addStage(name, workerFn, options = {}) {
        this.stages.set(name, {
            workerFn,
            instances: options.instances || 1,
            workers: [],
            options
        });
        return this;
    }
    
    connect(from, to) {
        this.connections.push({ from, to });
        return this;
    }
    
    async initialize() {
        for (const [name, stage] of this.stages) {
            for (let i = 0; i < stage.instances; i++) {
                stage.workers.push(new MockWorker(stage.workerFn));
            }
        }
    }
    
    async execute(initialData, onProgress = () => {}) {
        const results = new Map();
        let data = initialData;
        
        // Topological sort of stages
        const order = this.getExecutionOrder();
        
        let completed = 0;
        for (const stageName of order) {
            const stage = this.stages.get(stageName);
            
            if (stage.instances === 1) {
                // Single worker
                data = await this.runWorker(stage.workers[0], data);
            } else {
                // Fan-out/fan-in
                const chunks = this.splitData(data, stage.instances);
                const partialResults = await Promise.all(
                    chunks.map((chunk, i) => 
                        this.runWorker(stage.workers[i], chunk)
                    )
                );
                data = this.mergeResults(partialResults);
            }
            
            results.set(stageName, data);
            completed++;
            
            onProgress({
                stage: stageName,
                completed,
                total: order.length,
                percent: Math.round((completed / order.length) * 100)
            });
        }
        
        return { final: data, intermediate: results };
    }
    
    runWorker(worker, data) {
        return new Promise((resolve, reject) => {
            worker.onmessage = (e) => resolve(e.data);
            worker.onerror = reject;
            worker.postMessage(data);
        });
    }
    
    getExecutionOrder() {
        // Simple order based on connections
        const order = [];
        const visited = new Set();
        
        const visit = (name) => {
            if (visited.has(name)) return;
            visited.add(name);
            
            for (const conn of this.connections) {
                if (conn.to === name) {
                    visit(conn.from);
                }
            }
            
            order.push(name);
        };
        
        for (const name of this.stages.keys()) {
            visit(name);
        }
        
        return order;
    }
    
    splitData(data, count) {
        if (!Array.isArray(data)) return [data];
        
        const chunks = [];
        const chunkSize = Math.ceil(data.length / count);
        
        for (let i = 0; i < count; i++) {
            chunks.push(data.slice(i * chunkSize, (i + 1) * chunkSize));
        }
        
        return chunks;
    }
    
    mergeResults(results) {
        if (results.every(Array.isArray)) {
            return results.flat();
        }
        return results;
    }
    
    terminate() {
        for (const stage of this.stages.values()) {
            stage.workers.forEach(w => w.terminate());
        }
    }
}
*/

// =============================================================================
// EXERCISE 6: Shared State Simulation
// Simulate shared state between workers
// =============================================================================

/*
 * TODO: Create SharedState class that:
 * - Simulates shared memory between workers
 * - Supports atomic operations
 * - Notifies workers of state changes
 * - Handles race conditions
 */

class SharedState {
  // YOUR CODE HERE:
}

// SOLUTION:
/*
class SharedState {
    constructor(initialState = {}) {
        this.state = new Map(Object.entries(initialState));
        this.subscribers = new Map();
        this.locks = new Map();
        this.version = 0;
    }
    
    get(key) {
        return this.state.get(key);
    }
    
    set(key, value) {
        this.version++;
        this.state.set(key, value);
        this.notify(key, value);
        return value;
    }
    
    async atomicUpdate(key, updateFn) {
        // Acquire lock
        while (this.locks.get(key)) {
            await new Promise(resolve => setTimeout(resolve, 1));
        }
        
        this.locks.set(key, true);
        
        try {
            const oldValue = this.state.get(key);
            const newValue = updateFn(oldValue);
            this.set(key, newValue);
            return newValue;
        } finally {
            this.locks.delete(key);
        }
    }
    
    compareAndSwap(key, expectedValue, newValue) {
        if (this.state.get(key) === expectedValue) {
            this.set(key, newValue);
            return true;
        }
        return false;
    }
    
    increment(key, delta = 1) {
        const current = this.state.get(key) || 0;
        return this.set(key, current + delta);
    }
    
    subscribe(key, callback) {
        if (!this.subscribers.has(key)) {
            this.subscribers.set(key, []);
        }
        this.subscribers.get(key).push(callback);
        
        return () => {
            const subs = this.subscribers.get(key);
            const index = subs.indexOf(callback);
            if (index > -1) subs.splice(index, 1);
        };
    }
    
    notify(key, value) {
        const subs = this.subscribers.get(key) || [];
        subs.forEach(callback => callback(value, key, this.version));
    }
    
    getState() {
        return Object.fromEntries(this.state);
    }
}
*/

// =============================================================================
// EXERCISE 7: Worker Health Monitor
// Monitor worker health and auto-recover
// =============================================================================

/*
 * TODO: Create WorkerHealthMonitor class that:
 * - Monitors worker responsiveness
 * - Detects stuck/dead workers
 * - Auto-restarts failed workers
 * - Reports health statistics
 */

class WorkerHealthMonitor {
  // YOUR CODE HERE:
}

// SOLUTION:
/*
class WorkerHealthMonitor {
    constructor(pool, options = {}) {
        this.pool = pool;
        this.checkInterval = options.checkInterval || 5000;
        this.timeout = options.timeout || 10000;
        this.maxRestarts = options.maxRestarts || 3;
        
        this.workerStats = new Map();
        this.intervalId = null;
    }
    
    start() {
        this.intervalId = setInterval(() => this.checkHealth(), this.checkInterval);
        return this;
    }
    
    stop() {
        if (this.intervalId) {
            clearInterval(this.intervalId);
            this.intervalId = null;
        }
    }
    
    async checkHealth() {
        console.log('Checking worker health...');
        
        for (const worker of this.pool.workers || []) {
            const stats = this.getWorkerStats(worker);
            
            // Check if worker is responsive
            const isResponsive = await this.pingWorker(worker);
            
            if (!isResponsive) {
                stats.failedChecks++;
                
                if (stats.failedChecks >= 3 && stats.restarts < this.maxRestarts) {
                    console.log('Restarting unresponsive worker');
                    await this.restartWorker(worker);
                    stats.restarts++;
                    stats.failedChecks = 0;
                }
            } else {
                stats.failedChecks = 0;
                stats.lastHealthy = Date.now();
            }
        }
    }
    
    async pingWorker(worker) {
        return new Promise((resolve) => {
            const pingTimeout = setTimeout(() => resolve(false), this.timeout);
            
            // Simulate ping
            setTimeout(() => {
                clearTimeout(pingTimeout);
                resolve(true);
            }, 100);
        });
    }
    
    async restartWorker(worker) {
        worker.instance.terminate();
        worker.instance = new MockWorker(this.pool.workerFn);
        worker.busy = false;
    }
    
    getWorkerStats(worker) {
        if (!this.workerStats.has(worker)) {
            this.workerStats.set(worker, {
                failedChecks: 0,
                restarts: 0,
                lastHealthy: Date.now()
            });
        }
        return this.workerStats.get(worker);
    }
    
    getHealthReport() {
        const report = {
            totalWorkers: this.pool.workers?.length || 0,
            healthy: 0,
            unhealthy: 0,
            restarted: 0,
            workers: []
        };
        
        for (const [worker, stats] of this.workerStats) {
            const isHealthy = stats.failedChecks === 0;
            
            if (isHealthy) report.healthy++;
            else report.unhealthy++;
            
            if (stats.restarts > 0) report.restarted++;
            
            report.workers.push({
                healthy: isHealthy,
                failedChecks: stats.failedChecks,
                restarts: stats.restarts,
                lastHealthy: new Date(stats.lastHealthy).toISOString()
            });
        }
        
        return report;
    }
}
*/

// =============================================================================
// EXERCISE 8: Task Scheduler
// Schedule and prioritize worker tasks
// =============================================================================

/*
 * TODO: Create TaskScheduler class that:
 * - Schedules tasks with priorities
 * - Supports delayed execution
 * - Allows task cancellation
 * - Handles dependencies between tasks
 */

class TaskScheduler {
  // YOUR CODE HERE:
}

// SOLUTION:
/*
class TaskScheduler {
    constructor(pool) {
        this.pool = pool;
        this.tasks = new Map();
        this.nextId = 0;
        this.running = new Set();
    }
    
    schedule(task, options = {}) {
        const {
            priority = 0,
            delay = 0,
            dependencies = [],
            onComplete = () => {},
            onError = () => {}
        } = options;
        
        const id = this.nextId++;
        
        const taskInfo = {
            id,
            task,
            priority,
            scheduledTime: Date.now() + delay,
            dependencies,
            onComplete,
            onError,
            status: 'pending',
            result: null,
            error: null
        };
        
        this.tasks.set(id, taskInfo);
        
        if (delay > 0) {
            setTimeout(() => this.tryRun(id), delay);
        } else {
            this.tryRun(id);
        }
        
        return {
            id,
            cancel: () => this.cancel(id),
            getStatus: () => this.getTaskStatus(id),
            result: () => this.getTaskResult(id)
        };
    }
    
    async tryRun(id) {
        const taskInfo = this.tasks.get(id);
        if (!taskInfo || taskInfo.status !== 'pending') return;
        
        // Check dependencies
        for (const depId of taskInfo.dependencies) {
            const dep = this.tasks.get(depId);
            if (!dep || dep.status !== 'completed') {
                // Wait for dependencies
                setTimeout(() => this.tryRun(id), 100);
                return;
            }
        }
        
        // Check if it's time
        if (Date.now() < taskInfo.scheduledTime) {
            setTimeout(() => this.tryRun(id), taskInfo.scheduledTime - Date.now());
            return;
        }
        
        // Run the task
        taskInfo.status = 'running';
        this.running.add(id);
        
        try {
            const result = await this.pool.execute(taskInfo.task);
            taskInfo.status = 'completed';
            taskInfo.result = result;
            taskInfo.onComplete(result);
        } catch (error) {
            taskInfo.status = 'failed';
            taskInfo.error = error;
            taskInfo.onError(error);
        } finally {
            this.running.delete(id);
        }
    }
    
    cancel(id) {
        const taskInfo = this.tasks.get(id);
        if (taskInfo && taskInfo.status === 'pending') {
            taskInfo.status = 'cancelled';
            return true;
        }
        return false;
    }
    
    getTaskStatus(id) {
        const taskInfo = this.tasks.get(id);
        return taskInfo ? taskInfo.status : null;
    }
    
    getTaskResult(id) {
        const taskInfo = this.tasks.get(id);
        if (!taskInfo) return null;
        
        if (taskInfo.status === 'completed') {
            return { success: true, result: taskInfo.result };
        } else if (taskInfo.status === 'failed') {
            return { success: false, error: taskInfo.error };
        }
        return null;
    }
    
    getSchedulerStats() {
        const stats = {
            pending: 0,
            running: 0,
            completed: 0,
            failed: 0,
            cancelled: 0
        };
        
        for (const task of this.tasks.values()) {
            stats[task.status]++;
        }
        
        return stats;
    }
}
*/

// =============================================================================
// TEST YOUR IMPLEMENTATIONS
// =============================================================================

async function runTests() {
  console.log('Testing Web Worker Implementations...\n');

  // Test worker function
  const testWorkerFn = (data) => {
    if (data.delay) {
      // Simulate delay (in real worker, would use actual setTimeout)
    }
    return data.value * 2;
  };

  console.log('1. Testing Promise Worker:');
  // Test code here

  console.log('\n2. Testing Worker Pool:');
  // Test code here

  console.log('\n3. Testing Worker RPC:');
  // Test code here

  console.log('\nAll tests complete!');
}

// Uncomment to run tests
// runTests();
Exercises - JavaScript Tutorial | DeepML