fix: prevent EADDRINUSE double-bind on Holesail client startup

Add global activeClientKeys Set to track all active clients and prevent
race conditions between domain init and cache restore paths. Add EADDRINUSE
error handling in holesail.js, holesail_child.js
This commit is contained in:
Raven Scott
2026-02-20 19:53:16 -05:00
parent 8d0efde1cc
commit 8bed09a537
6 changed files with 125 additions and 8 deletions
+30 -2
View File
@@ -83,6 +83,20 @@ async function startHolesailClient(domain, hash, ip, port, persistent = false) {
logDebug('Holesail', `Holesail client already exists for ${key}`);
return;
}
// Check global activeClientKeys to prevent double-bind (both forked and direct paths)
if (state.activeClientKeys && state.activeClientKeys.has(key)) {
logDebug('Holesail', `Holesail client already starting/active for ${key} (global check), waiting...`);
// Wait for the existing promise to complete
if (state.starting.has(key)) {
await state.starting.get(key);
return;
}
return;
}
// Mark as active before starting
if (state.activeClientKeys) {
state.activeClientKeys.add(key);
}
let startPromise = state.starting.get(key);
if (!startPromise) {
startPromise = (async () => {
@@ -230,10 +244,24 @@ async function startHolesailClient(domain, hash, ip, port, persistent = false) {
state.holesailClientTimeouts.set(key, timeout);
}
} catch (err) {
logError('Holesail', `Error connecting Holesail client for ${key}: ${err.message}`);
if (err.code === 'EADDRINUSE' || err.message.includes('EADDRINUSE')) {
logWarn('Holesail', `Port ${port} on ${ip} already in use (EADDRINUSE) for ${key}, skipping duplicate bind`);
if (state.activeClientKeys) {
state.activeClientKeys.delete(key);
}
} else {
logError('Holesail', `Error connecting Holesail client for ${key}: ${err.message}`);
}
}
} catch (err) {
logError('Holesail', `Failed to start Holesail client for ${key}: ${err.message}`);
if (err.code === 'EADDRINUSE' || err.message.includes('EADDRINUSE')) {
logWarn('Holesail', `Port ${port} on ${ip} already in use (EADDRINUSE) for ${key}, skipping duplicate bind`);
if (state.activeClientKeys) {
state.activeClientKeys.delete(key);
}
} else {
logError('Holesail', `Failed to start Holesail client for ${key}: ${err.message}`);
}
} finally {
state.starting.delete(key);
}
+14 -3
View File
@@ -38,9 +38,20 @@ process.on('message', async (msg) => {
}
const holesail = new Holesail(opts);
await holesail.ready();
process.send({ type: 'ready', info: holesail.info });
// Keep running
try {
await holesail.ready();
process.send({ type: 'ready', info: holesail.info });
// Keep running
} catch (err) {
if (err.code === 'EADDRINUSE' || err.message.includes('EADDRINUSE')) {
console.warn(`Port ${opts.port} on ${opts.host} already in use (EADDRINUSE), exiting gracefully`);
process.send({ type: 'error', message: 'EADDRINUSE: Port already in use' });
process.exit(0); // Graceful exit instead of crash
} else {
process.send({ type: 'error', message: err.message });
process.exit(1);
}
}
} catch (err) {
process.send({ type: 'error', message: err.message });
process.exit(1);