UNPKG

@sirmrmarty/n8n-nodes-tmux-orchestrator

Version:

n8n nodes for orchestrating Claude AI agents through tmux sessions

580 lines (511 loc) 11.9 kB
/** * Thread-Safe State Management Utility * Provides thread-safe operations for shared state in autonomous systems */ import { EventEmitter } from 'events'; /** * Thread-safe Map implementation with locking mechanism */ export class ThreadSafeMap<K, V> extends EventEmitter { private map = new Map<K, V>(); private locks = new Set<string>(); private readonly lockTimeout: number; constructor(lockTimeout: number = 5000) { super(); this.lockTimeout = lockTimeout; } /** * Acquire a lock for a specific key */ private async acquireLock(key: K): Promise<string> { const lockId = `${String(key)}_${Date.now()}_${Math.random()}`; const keyStr = String(key); // Wait for existing lock to be released while (this.locks.has(keyStr)) { await new Promise(resolve => setTimeout(resolve, 10)); } this.locks.add(keyStr); // Set timeout to prevent deadlocks setTimeout(() => { if (this.locks.has(keyStr)) { console.warn(`Lock timeout for key ${keyStr}, forcibly releasing`); this.locks.delete(keyStr); } }, this.lockTimeout); return lockId; } /** * Release a lock for a specific key */ private releaseLock(key: K): void { this.locks.delete(String(key)); } /** * Safely set a value with locking */ public async safeSet(key: K, value: V): Promise<void> { await this.acquireLock(key); try { const oldValue = this.map.get(key); this.map.set(key, value); this.emit('change', { key, oldValue, newValue: value, operation: 'set' }); } finally { this.releaseLock(key); } } /** * Safely get a value */ public safeGet(key: K): V | undefined { return this.map.get(key); } /** * Safely delete a value with locking */ public async safeDelete(key: K): Promise<boolean> { await this.acquireLock(key); try { const oldValue = this.map.get(key); const deleted = this.map.delete(key); if (deleted) { this.emit('change', { key, oldValue, newValue: undefined, operation: 'delete' }); } return deleted; } finally { this.releaseLock(key); } } /** * Safely check if key exists */ public safeHas(key: K): boolean { return this.map.has(key); } /** * Safely get all keys */ public safeKeys(): K[] { return Array.from(this.map.keys()); } /** * Safely get all values */ public safeValues(): V[] { return Array.from(this.map.values()); } /** * Safely get all entries */ public safeEntries(): [K, V][] { return Array.from(this.map.entries()); } /** * Safely get size */ public safeSize(): number { return this.map.size; } /** * Safely clear all entries */ public async safeClear(): Promise<void> { // Lock all existing keys const keys = this.safeKeys(); for (const key of keys) { await this.acquireLock(key); } try { this.map.clear(); this.emit('change', { operation: 'clear' }); } finally { // Release all locks for (const key of keys) { this.releaseLock(key); } } } /** * Safely update a value with a function */ public async safeUpdate(key: K, updateFn: (currentValue: V | undefined) => V): Promise<V> { await this.acquireLock(key); try { const oldValue = this.map.get(key); const newValue = updateFn(oldValue); this.map.set(key, newValue); this.emit('change', { key, oldValue, newValue, operation: 'update' }); return newValue; } finally { this.releaseLock(key); } } /** * Safely compute a value if absent */ public async safeComputeIfAbsent(key: K, computeFn: () => Promise<V> | V): Promise<V> { await this.acquireLock(key); try { if (this.map.has(key)) { return this.map.get(key)!; } const value = await Promise.resolve(computeFn()); this.map.set(key, value); this.emit('change', { key, oldValue: undefined, newValue: value, operation: 'compute' }); return value; } finally { this.releaseLock(key); } } } /** * Thread-safe Set implementation */ export class ThreadSafeSet<T> extends EventEmitter { private set = new Set<T>(); private locks = new Set<string>(); private readonly lockTimeout: number; constructor(lockTimeout: number = 5000) { super(); this.lockTimeout = lockTimeout; } /** * Acquire a lock for set operations */ private async acquireLock(): Promise<void> { const lockId = `set_${Date.now()}_${Math.random()}`; // Wait for existing lock to be released while (this.locks.size > 0) { await new Promise(resolve => setTimeout(resolve, 10)); } this.locks.add(lockId); // Set timeout to prevent deadlocks setTimeout(() => { if (this.locks.has(lockId)) { console.warn(`Set lock timeout, forcibly releasing`); this.locks.delete(lockId); } }, this.lockTimeout); } /** * Release the set lock */ private releaseLock(): void { this.locks.clear(); } /** * Safely add a value */ public async safeAdd(value: T): Promise<boolean> { await this.acquireLock(); try { const hadValue = this.set.has(value); this.set.add(value); if (!hadValue) { this.emit('change', { value, operation: 'add' }); } return !hadValue; } finally { this.releaseLock(); } } /** * Safely delete a value */ public async safeDelete(value: T): Promise<boolean> { await this.acquireLock(); try { const deleted = this.set.delete(value); if (deleted) { this.emit('change', { value, operation: 'delete' }); } return deleted; } finally { this.releaseLock(); } } /** * Safely check if value exists */ public safeHas(value: T): boolean { return this.set.has(value); } /** * Safely get all values */ public safeValues(): T[] { return Array.from(this.set.values()); } /** * Safely get size */ public safeSize(): number { return this.set.size; } /** * Safely clear all values */ public async safeClear(): Promise<void> { await this.acquireLock(); try { this.set.clear(); this.emit('change', { operation: 'clear' }); } finally { this.releaseLock(); } } } /** * Thread-safe Counter with atomic operations */ export class ThreadSafeCounter extends EventEmitter { private value: number = 0; private lock: boolean = false; /** * Atomically increment the counter */ public async increment(amount: number = 1): Promise<number> { while (this.lock) { await new Promise(resolve => setTimeout(resolve, 1)); } this.lock = true; try { const oldValue = this.value; this.value += amount; this.emit('change', { oldValue, newValue: this.value, operation: 'increment' }); return this.value; } finally { this.lock = false; } } /** * Atomically decrement the counter */ public async decrement(amount: number = 1): Promise<number> { return this.increment(-amount); } /** * Get current value */ public getValue(): number { return this.value; } /** * Atomically set the value */ public async setValue(newValue: number): Promise<number> { while (this.lock) { await new Promise(resolve => setTimeout(resolve, 1)); } this.lock = true; try { const oldValue = this.value; this.value = newValue; this.emit('change', { oldValue, newValue, operation: 'set' }); return this.value; } finally { this.lock = false; } } /** * Atomically compare and swap */ public async compareAndSwap(expected: number, newValue: number): Promise<boolean> { while (this.lock) { await new Promise(resolve => setTimeout(resolve, 1)); } this.lock = true; try { if (this.value === expected) { const oldValue = this.value; this.value = newValue; this.emit('change', { oldValue, newValue, operation: 'compareAndSwap' }); return true; } return false; } finally { this.lock = false; } } } /** * Thread-safe Queue implementation */ export class ThreadSafeQueue<T> extends EventEmitter { private queue: T[] = []; private lock: boolean = false; /** * Acquire queue lock */ private async acquireLock(): Promise<void> { while (this.lock) { await new Promise(resolve => setTimeout(resolve, 1)); } this.lock = true; } /** * Release queue lock */ private releaseLock(): void { this.lock = false; } /** * Safely enqueue an item */ public async enqueue(item: T): Promise<void> { await this.acquireLock(); try { this.queue.push(item); this.emit('enqueue', item); } finally { this.releaseLock(); } } /** * Safely dequeue an item */ public async dequeue(): Promise<T | undefined> { await this.acquireLock(); try { const item = this.queue.shift(); if (item !== undefined) { this.emit('dequeue', item); } return item; } finally { this.releaseLock(); } } /** * Safely peek at the front item */ public peek(): T | undefined { return this.queue[0]; } /** * Get queue size */ public size(): number { return this.queue.length; } /** * Check if queue is empty */ public isEmpty(): boolean { return this.queue.length === 0; } /** * Safely clear the queue */ public async clear(): Promise<void> { await this.acquireLock(); try { this.queue = []; this.emit('clear'); } finally { this.releaseLock(); } } } /** * State Management Registry for centralized state tracking */ export class StateManager { private static instance: StateManager; private maps = new Map<string, ThreadSafeMap<any, any>>(); private sets = new Map<string, ThreadSafeSet<any>>(); private counters = new Map<string, ThreadSafeCounter>(); private queues = new Map<string, ThreadSafeQueue<any>>(); private constructor() {} public static getInstance(): StateManager { if (!StateManager.instance) { StateManager.instance = new StateManager(); } return StateManager.instance; } /** * Get or create a thread-safe map */ public getMap<K, V>(name: string): ThreadSafeMap<K, V> { if (!this.maps.has(name)) { this.maps.set(name, new ThreadSafeMap<K, V>()); } return this.maps.get(name)!; } /** * Get or create a thread-safe set */ public getSet<T>(name: string): ThreadSafeSet<T> { if (!this.sets.has(name)) { this.sets.set(name, new ThreadSafeSet<T>()); } return this.sets.get(name)!; } /** * Get or create a thread-safe counter */ public getCounter(name: string): ThreadSafeCounter { if (!this.counters.has(name)) { this.counters.set(name, new ThreadSafeCounter()); } return this.counters.get(name)!; } /** * Get or create a thread-safe queue */ public getQueue<T>(name: string): ThreadSafeQueue<T> { if (!this.queues.has(name)) { this.queues.set(name, new ThreadSafeQueue<T>()); } return this.queues.get(name)!; } /** * Remove a managed state */ public removeState(name: string): boolean { const removed = this.maps.delete(name) || this.sets.delete(name) || this.counters.delete(name) || this.queues.delete(name); return removed; } /** * Get statistics about managed state */ public getStats(): { maps: number; sets: number; counters: number; queues: number; total: number; } { return { maps: this.maps.size, sets: this.sets.size, counters: this.counters.size, queues: this.queues.size, total: this.maps.size + this.sets.size + this.counters.size + this.queues.size }; } /** * Clear all managed state */ public async clearAll(): Promise<void> { const promises: Promise<void>[] = []; for (const map of this.maps.values()) { promises.push(map.safeClear()); } for (const set of this.sets.values()) { promises.push(set.safeClear()); } for (const queue of this.queues.values()) { promises.push(queue.clear()); } await Promise.all(promises); this.maps.clear(); this.sets.clear(); this.counters.clear(); this.queues.clear(); } } // Global state manager instance export const stateManager = StateManager.getInstance();