@sirmrmarty/n8n-nodes-tmux-orchestrator
Version:
n8n nodes for orchestrating Claude AI agents through tmux sessions
580 lines (511 loc) • 11.9 kB
text/typescript
/**
* 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();