refactor: Complete rewrite of worker-utils.ts and cleanup of worker-service.ts
- Removed fragile PM2 string parsing and replaced with direct PM2 restart logic. - Eliminated silent error handling in worker-utils.ts for better error visibility. - Extracted duplicated session auto-creation logic into a new helper method getOrCreateSession() in worker-service.ts. - Centralized configuration values and replaced magic numbers with named constants. - Updated health check logic to ensure worker is restarted if unhealthy. - Removed unnecessary getWorkerPort() wrapper function. - Improved overall code quality and maintainability by applying DRY and YAGNI principles.
This commit is contained in:
+40
-68
@@ -1,16 +1,40 @@
|
||||
import path from "path";
|
||||
import { spawn } from "child_process";
|
||||
import { homedir } from "os";
|
||||
import { existsSync, readFileSync } from "fs";
|
||||
import { execSync } from "child_process";
|
||||
import { getPackageRoot } from "./paths.js";
|
||||
|
||||
const FIXED_PORT = parseInt(process.env.CLAUDE_MEM_WORKER_PORT || "37777", 10);
|
||||
// Named constants for health checks
|
||||
const HEALTH_CHECK_TIMEOUT_MS = 100;
|
||||
const HEALTH_CHECK_POLL_INTERVAL_MS = 100;
|
||||
const HEALTH_CHECK_MAX_WAIT_MS = 10000;
|
||||
|
||||
/**
|
||||
* Get the worker port number
|
||||
* Priority: ~/.claude-mem/settings.json > env var > default
|
||||
*/
|
||||
export function getWorkerPort(): number {
|
||||
try {
|
||||
const settingsPath = path.join(homedir(), '.claude-mem', 'settings.json');
|
||||
if (existsSync(settingsPath)) {
|
||||
const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
|
||||
const port = parseInt(settings.env?.CLAUDE_MEM_WORKER_PORT, 10);
|
||||
if (!isNaN(port)) return port;
|
||||
}
|
||||
} catch {
|
||||
// Fall through to env var or default
|
||||
}
|
||||
return parseInt(process.env.CLAUDE_MEM_WORKER_PORT || '37777', 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if worker is responsive by trying the health endpoint
|
||||
*/
|
||||
async function isWorkerHealthy(timeoutMs: number = 100): Promise<boolean> {
|
||||
async function isWorkerHealthy(): Promise<boolean> {
|
||||
try {
|
||||
const response = await fetch(`http://127.0.0.1:${FIXED_PORT}/health`, {
|
||||
signal: AbortSignal.timeout(timeoutMs)
|
||||
const port = getWorkerPort();
|
||||
const response = await fetch(`http://127.0.0.1:${port}/health`, {
|
||||
signal: AbortSignal.timeout(HEALTH_CHECK_TIMEOUT_MS)
|
||||
});
|
||||
return response.ok;
|
||||
} catch {
|
||||
@@ -21,89 +45,37 @@ async function isWorkerHealthy(timeoutMs: number = 100): Promise<boolean> {
|
||||
/**
|
||||
* Wait for worker to become healthy
|
||||
*/
|
||||
async function waitForWorkerHealth(maxWaitMs: number = 10000): Promise<boolean> {
|
||||
async function waitForWorkerHealth(): Promise<boolean> {
|
||||
const start = Date.now();
|
||||
const checkInterval = 100; // Check every 100ms
|
||||
|
||||
while (Date.now() - start < maxWaitMs) {
|
||||
if (await isWorkerHealthy(1000)) {
|
||||
|
||||
while (Date.now() - start < HEALTH_CHECK_MAX_WAIT_MS) {
|
||||
if (await isWorkerHealthy()) {
|
||||
return true;
|
||||
}
|
||||
// Wait before next check
|
||||
await new Promise(resolve => setTimeout(resolve, checkInterval));
|
||||
await new Promise(resolve => setTimeout(resolve, HEALTH_CHECK_POLL_INTERVAL_MS));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure worker service is running
|
||||
* Checks if worker is already running before attempting to start
|
||||
* This prevents unnecessary restarts that could interrupt mid-action processing
|
||||
* If unhealthy, restarts PM2 and waits for health
|
||||
*/
|
||||
export async function ensureWorkerRunning(): Promise<void> {
|
||||
// First, check if worker is already healthy
|
||||
if (await isWorkerHealthy()) {
|
||||
return; // Worker is already running and responsive
|
||||
return;
|
||||
}
|
||||
|
||||
const packageRoot = getPackageRoot();
|
||||
const pm2Path = path.join(packageRoot, "node_modules", ".bin", "pm2");
|
||||
const ecosystemPath = path.join(packageRoot, "ecosystem.config.cjs");
|
||||
|
||||
// Check PM2 status to see if worker process exists
|
||||
const checkProcess = spawn(pm2Path, ["list", "--no-color"], {
|
||||
execSync(`"${pm2Path}" restart "${ecosystemPath}"`, {
|
||||
cwd: packageRoot,
|
||||
stdio: ["ignore", "pipe", "ignore"],
|
||||
stdio: 'pipe'
|
||||
});
|
||||
|
||||
let output = "";
|
||||
checkProcess.stdout?.on("data", (data) => {
|
||||
output += data.toString();
|
||||
});
|
||||
|
||||
// Wait for PM2 list to complete
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
checkProcess.on("error", (error) => reject(error));
|
||||
checkProcess.on("close", (code) => {
|
||||
// PM2 list can fail, but we should still continue - just assume worker isn't running
|
||||
// This handles cases where PM2 isn't installed yet
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
// Check if 'claude-mem-worker' is in the PM2 list output and is 'online'
|
||||
const isRunning = output.includes("claude-mem-worker") && output.includes("online");
|
||||
|
||||
if (!isRunning) {
|
||||
// Start the worker
|
||||
const startProcess = spawn(pm2Path, ["start", ecosystemPath], {
|
||||
cwd: packageRoot,
|
||||
stdio: "ignore",
|
||||
});
|
||||
|
||||
// Wait for PM2 start command to complete
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
startProcess.on("error", (error) => reject(error));
|
||||
startProcess.on("close", (code) => {
|
||||
if (code !== 0 && code !== null) {
|
||||
reject(new Error(`PM2 start command failed with exit code ${code}`));
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Wait for worker to become healthy (either just started or was starting)
|
||||
const healthy = await waitForWorkerHealth(10000);
|
||||
if (!healthy) {
|
||||
throw new Error("Worker failed to become healthy after starting");
|
||||
if (!await waitForWorkerHealth()) {
|
||||
throw new Error("Worker failed to become healthy after restart");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the worker port number (fixed port)
|
||||
*/
|
||||
export function getWorkerPort(): number {
|
||||
return FIXED_PORT;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user