Merge pull request #3176 from Dokploy/2974-tail-error-tail-inotify-cannot-be-used-reverting-to-polling-too-many-open-files

fix(wss): close read deployment and container logs connections
This commit is contained in:
Mauricio Siu
2025-12-06 15:22:03 -06:00
committed by GitHub
3 changed files with 111 additions and 27 deletions

View File

@@ -71,7 +71,9 @@ export const setupDockerContainerLogsWebSocketServer = (
const command = search
? `${baseCommand} 2>&1 | grep --line-buffered -iF "${escapedSearch}"`
: baseCommand;
client.exec(command, (err, stream) => {
// Use pty: true to ensure the remote process receives SIGHUP when SSH connection closes
// This is crucial for terminating docker logs processes when the connection is closed
client.exec(command, { pty: true }, (err, stream) => {
if (err) {
console.error("Execution error:", err);
ws.close();

View File

@@ -58,7 +58,12 @@ export const setupDockerContainerTerminalWebSocketServer = (
`docker exec -it -w / ${containerId} ${activeWay}`,
{ pty: true },
(err, stream) => {
if (err) throw err;
if (err) {
console.error("SSH exec error:", err);
ws.close();
conn.end();
return;
}
stream
.on("close", (code: number, _signal: string) => {
@@ -93,10 +98,20 @@ export const setupDockerContainerTerminalWebSocketServer = (
ws.on("close", () => {
stream.end();
// Ensure SSH connection is closed when WebSocket closes
conn.end();
});
},
);
})
.on("error", (err) => {
console.error("SSH connection error:", err);
if (ws.readyState === ws.OPEN) {
ws.send(`SSH error: ${err.message}`);
ws.close();
}
conn.end();
})
.connect({
host: server.ipAddress,
port: server.port,

View File

@@ -31,8 +31,11 @@ export const setupDeploymentLogsWebSocketServer = (
const serverId = url.searchParams.get("serverId");
const { user, session } = await validateRequest(req);
// Generate unique connection ID for tracking
const connectionId = `deployment-logs-${Date.now()}-${Math.random().toString(36).substring(7)}`;
if (!logPath) {
console.log("logPath no provided");
console.log(`[${connectionId}] logPath no provided`);
ws.close(4000, "logPath no provided");
return;
}
@@ -42,40 +45,55 @@ export const setupDeploymentLogsWebSocketServer = (
return;
}
let tailProcess: ReturnType<typeof spawn> | null = null;
let sshClient: Client | null = null;
try {
if (serverId) {
const server = await findServerById(serverId);
if (!server.sshKeyId) return;
const client = new Client();
client
if (!server.sshKeyId) {
ws.close();
return;
}
sshClient = new Client();
sshClient
.on("ready", () => {
const command = `
tail -n +1 -f ${logPath};
`;
client.exec(command, (err, stream) => {
sshClient!.exec(command, (err, stream) => {
if (err) {
console.error("Execution error:", err);
sshClient!.end();
ws.close();
return;
}
stream
.on("close", () => {
client.end();
sshClient!.end();
ws.close();
})
.on("data", (data: string) => {
ws.send(data.toString());
if (ws.readyState === ws.OPEN) {
ws.send(data.toString());
}
})
.stderr.on("data", (data) => {
ws.send(data.toString());
if (ws.readyState === ws.OPEN) {
ws.send(data.toString());
}
});
});
})
.on("error", (err) => {
console.error("SSH connection error:", err);
ws.send(`SSH error: ${err.message}`);
ws.close(); // Cierra el WebSocket si hay un error con SSH
if (ws.readyState === ws.OPEN) {
ws.send(`SSH error: ${err.message}`);
ws.close();
}
if (sshClient) {
sshClient.end();
}
})
.connect({
host: server.ipAddress,
@@ -85,26 +103,75 @@ export const setupDeploymentLogsWebSocketServer = (
});
ws.on("close", () => {
client.end();
if (sshClient) {
sshClient.end();
}
});
} else {
const tail = spawn("tail", ["-n", "+1", "-f", logPath]);
tailProcess = spawn("tail", ["-n", "+1", "-f", logPath]);
tail.stdout.on("data", (data) => {
ws.send(data.toString());
});
const stdout = tailProcess.stdout;
const stderr = tailProcess.stderr;
tail.stderr.on("data", (data) => {
ws.send(new Error(`tail error: ${data.toString()}`).message);
});
tail.on("close", () => {
if (stdout) {
stdout.on("data", (data) => {
if (ws.readyState === ws.OPEN) {
ws.send(data.toString());
}
});
}
if (stderr) {
stderr.on("data", (data) => {
if (ws.readyState === ws.OPEN) {
ws.send(new Error(`tail error: ${data.toString()}`).message);
}
});
}
tailProcess.on("close", () => {
ws.close();
});
tailProcess.on("error", () => {
if (ws.readyState === ws.OPEN) {
ws.close();
}
});
ws.on("close", () => {
if (tailProcess && !tailProcess.killed) {
tailProcess.kill("SIGTERM");
// Force kill after a timeout if it doesn't terminate
setTimeout(() => {
if (tailProcess && !tailProcess.killed) {
tailProcess.kill("SIGKILL");
} else {
}
}, 1000);
} else {
}
});
}
} catch (error) {
// Clean up resources on error
if (tailProcess && !tailProcess.killed) {
tailProcess.kill("SIGTERM");
setTimeout(() => {
if (tailProcess && !tailProcess.killed) {
tailProcess.kill("SIGKILL");
}
}, 1000);
}
if (sshClient) {
sshClient.end();
}
if (ws.readyState === ws.OPEN) {
// @ts-ignore
const errorMessage = error?.message as unknown as string;
ws.send(errorMessage || "An error occurred");
ws.close();
}
} catch {
// @ts-ignore
// const errorMessage = error?.message as unknown as string;
// ws.send(errorMessage);
}
});
};