Skip to main content
Glama

Electron Terminal MCP Server

test_mutex.js9.09 kB
import * as lockfile from 'lockfile'; import { promisify } from 'util'; import path from 'path'; import os from 'os'; import fs from 'fs'; import { spawn } from 'child_process'; import { fileURLToPath } from 'url'; // Added for ES module __filename const __filename = fileURLToPath(import.meta.url); // Define __filename for ES module const MUTEX_FILE = path.join(os.tmpdir(), 'electron-mcp-mutex.lock'); const MUTEX_DIR = os.tmpdir(); // Function to perform a single lock/unlock cycle async function singleLockCycle() { const processId = process.pid; console.log(`Process ${processId} attempting to acquire mutex.`); // Ensure the directory exists (still good practice) try { if (!fs.existsSync(MUTEX_DIR)) { fs.mkdirSync(MUTEX_DIR, { recursive: true }); } } catch (e) { console.error(`Process ${processId}: Error ensuring mutex directory ${MUTEX_DIR}: ${e.message}`); return; } const lock = promisify(lockfile.lock); const unlock = promisify(lockfile.unlock); try { const lockOptions = { wait: 10 * 1000, pollPeriod: 100, stale: 5 * 1000, retries: 100, // Increased retries for contention retryWait: 100 }; console.log(`Process ${processId} trying to lock with options: ${JSON.stringify(lockOptions)}`); await lock(MUTEX_FILE, lockOptions); console.log(`Process ${processId} acquired mutex.`); if (fs.existsSync(MUTEX_FILE)) { console.log(`Process ${processId}: Mutex file EXISTS immediately after lock acquisition.`); } else { console.log(`Process ${processId}: Mutex file does NOT exist immediately after lock acquisition.`); } // Simulate some work while holding the lock const workDuration = Math.random() * 1000 + 200; // 200ms to 1200ms console.log(`Process ${processId} working for ${workDuration.toFixed(0)}ms.`); await new Promise(resolve => setTimeout(resolve, workDuration)); console.log(`Process ${processId} attempting to release mutex.`); if (fs.existsSync(MUTEX_FILE)) { console.log(`Process ${processId}: Mutex file exists before unlock.`); await unlock(MUTEX_FILE); console.log(`Process ${processId} released mutex.`); } else { console.log(`Process ${processId}: Mutex file does NOT exist before unlock. Cannot release.`); } } catch (error) { console.error(`Process ${processId} failed to acquire or release mutex: ${error.message}`); if (error.code === 'EEXIST') { console.error(`Process ${processId}: Lock file already exists (EEXIST) - this indicates contention.`); } } } // Simulate different scenarios based on command line arguments const scenario = process.argv[2]; if (scenario === 'normal') { singleLockCycle(); } else if (scenario === 'single_run') { // New case for child processes singleLockCycle(); } else if (scenario === 'concurrent') { const numChildren = 3; console.log(`Main process ${process.pid} spawning ${numChildren} child processes for concurrent test...`); for (let i = 0; i < numChildren; i++) { const child = spawn('node', [__filename, 'single_run'], { stdio: 'inherit' }); child.on('error', (err) => { console.error(`Failed to start child process ${i + 1}: ${err}`); }); child.on('exit', (code, signal) => { console.log(`Child process ${i + 1} (PID: ${child.pid}) exited with code ${code}, signal ${signal}`); }); } // Main process waits for a bit for children to run, then exits. // A more robust solution would wait for all children to complete. setTimeout(() => { console.log(`Main process ${process.pid} exiting after spawning children.`); }, numChildren * 2000 + 2000); // Wait roughly for children to finish } else if (scenario === 'crash') { // Modified to crash while holding the lock (async () => { const processId = process.pid; console.log(`Process ${processId} attempting to acquire mutex for CRASH scenario.`); const lock = promisify(lockfile.lock); try { await lock(MUTEX_FILE, { stale: 5000, wait: 1000 }); // Short wait, rely on stale console.log(`Process ${processId} acquired mutex, working, then crashing...`); await new Promise(resolve => setTimeout(resolve, 500)); // Simulate work console.log(`Process ${processId} CRASHING NOW while holding lock.`); process.exit(1); // Crash before unlock } catch (error) { console.error(`Process ${processId} CRASH scenario: failed to acquire lock: ${error.message}`); process.exit(1); // Exit if lock not acquired } })(); } else if (scenario === 'terminate') { singleLockCycle().then(() => { console.log(`Process ${process.pid} acquired and released, now terminating normally.`); process.exit(0); // Simulate normal termination after releasing }); } else if (scenario === 'sigint') { const currentProcessId = process.pid; // Capture pid for the handler process.on('SIGINT', async () => { const unlockSignal = promisify(lockfile.unlock); console.log(`Process ${currentProcessId} received SIGINT, attempting to release mutex.`); // Check if lock file exists before attempting to unlock in signal handler if (fs.existsSync(MUTEX_FILE)) { await unlockSignal(MUTEX_FILE).catch(err => console.error(`Process ${currentProcessId} error releasing on SIGINT: ${err.message}`)); console.log(`Process ${currentProcessId} released mutex on SIGINT.`); } else { console.log(`Process ${currentProcessId}: Mutex file not found on SIGINT, no release needed.`); } process.exit(0); // Exit after handling SIGINT }); // For SIGINT test, acquire lock and hold it, relying on SIGINT handler to release (async () => { const lock = promisify(lockfile.lock); try { await lock(MUTEX_FILE, { stale: 5000, wait: 1000 }); // Acquire lock console.log(`Process ${currentProcessId} acquired lock for SIGINT test, holding...`); // Keep the process alive to receive signal console.log(`Process ${currentProcessId} running for SIGINT test. Send SIGINT (Ctrl+C) or wait for timeout.`); setTimeout(() => { console.log(`Process ${currentProcessId} SIGINT test timed out. Lock should still be held if no SIGINT.`); // If it times out, the lock is NOT released by this path, relying on staleness for next acquisition. // Or, we could attempt a release here if that's the desired timeout behavior. // For now, let timeout imply the lock remains, to test staleness if SIGINT isn't caught. // However, the SIGINT handler calls process.exit(0), so this timeout might not always be hit if SIGINT is sent. // Let's ensure the process exits cleanly on timeout. process.exit(0); }, 15000); // 15 seconds timeout } catch (error) { console.error(`Process ${currentProcessId} SIGINT scenario: failed to acquire lock: ${error.message}`); process.exit(1); } })(); } else if (scenario === 'rapid') { async function rapidCycles() { for (let i = 0; i < 5; i++) { // Reduced cycles for faster test console.log(`Rapid cycle ${i + 1} starting...`); await singleLockCycle(); await new Promise(resolve => setTimeout(resolve, 20)); // Shorter delay } console.log('Rapid cycles complete.'); } rapidCycles(); } else { console.log('Usage: node test_mutex.js [normal|single_run|concurrent|crash|terminate|sigint|rapid]'); } // Clean up mutex file on exit for scenarios that don't handle it process.on('exit', () => { if (fs.existsSync(MUTEX_FILE)) { // In a real scenario, proper-lockfile should handle this on clean exit // This is just for ensuring cleanup in test scenarios that might not exit cleanly // console.log(`Process ${process.pid} cleaning up mutex file on exit.`); // fs.unlinkSync(MUTEX_FILE); } }); // Ensure the mutex is released on unexpected exits as well for testing process.on('uncaughtException', async (err) => { // Promisify for signal handlers too const unlockSignal = promisify(lockfile.unlock); console.error(`Process ${process.pid} caught uncaught exception: ${err.message}`); // Check if lock file exists before attempting to unlock if (fs.existsSync(MUTEX_FILE)) { await unlockSignal(MUTEX_FILE).catch(e => console.error(`Process ${process.pid} error releasing on uncaughtException: ${e.message}`)); } process.exit(1); }); process.on('unhandledRejection', async (reason, promise) => { // Promisify for signal handlers too const unlockSignal = promisify(lockfile.unlock); console.error(`Process ${process.pid} caught unhandled rejection: ${reason}`); // Check if lock file exists before attempting to unlock if (fs.existsSync(MUTEX_FILE)) { await unlockSignal(MUTEX_FILE).catch(e => console.error(`Process ${process.pid} error releasing on unhandledRejection: ${e.message}`)); } process.exit(1); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/nexon33/console-terminal-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server