diff --git a/back/api/script.ts b/back/api/script.ts index 1ecce902..d47ca99f 100644 --- a/back/api/script.ts +++ b/back/api/script.ts @@ -206,6 +206,7 @@ export default (app: Router) => { }), }), async (req: Request, res: Response, next: NextFunction) => { + const logger: Logger = Container.get('logger'); try { let { filename, content, path } = req.body as { filename: string; @@ -223,6 +224,7 @@ export default (app: Router) => { await writeFileWithLock(filePath, content); return res.send({ code: 200 }); } catch (e) { + logger.error('🔥 error saving script: %o', e); return next(e); } }, diff --git a/back/services/http.ts b/back/services/http.ts index 5391c842..0bd8fcd7 100644 --- a/back/services/http.ts +++ b/back/services/http.ts @@ -16,6 +16,17 @@ export class HttpServerService { metricsService.record('http_service_start', 1, { port: port.toString(), }); + + // Set server timeouts to prevent premature connection drops + if (this.server) { + // Timeout for receiving the entire request (including body) - 5 minutes + this.server.requestTimeout = 300000; + // Timeout for headers - 2 minutes + this.server.headersTimeout = 120000; + // Keep-alive timeout - 65 seconds (slightly more than typical load balancer timeout) + this.server.keepAliveTimeout = 65000; + } + resolve(this.server); }); diff --git a/back/shared/utils.ts b/back/shared/utils.ts index fb344111..967f3d8d 100644 --- a/back/shared/utils.ts +++ b/back/shared/utils.ts @@ -3,6 +3,7 @@ import os from 'os'; import path from 'path'; import { writeFile, open, chmod } from 'fs/promises'; import { fileExist } from '../config/util'; +import Logger from '../loaders/logger'; function getUniqueLockPath(filePath: string) { const sanitizedPath = filePath @@ -19,24 +20,48 @@ export async function writeFileWithLock( if (typeof options === 'string') { options = { encoding: options }; } - if (!(await fileExist(filePath))) { - const fileHandle = await open(filePath, 'w'); - fileHandle.close(); + + try { + if (!(await fileExist(filePath))) { + const fileHandle = await open(filePath, 'w'); + await fileHandle.close(); + } + } catch (error) { + throw new Error(`Failed to create file ${filePath}: ${error}`); } + const lockfilePath = getUniqueLockPath(filePath); + let release: (() => Promise) | null = null; - const release = await lock(filePath, { - retries: { - retries: 10, - factor: 2, - minTimeout: 100, - maxTimeout: 3000, - }, - lockfilePath, - }); - await writeFile(filePath, content, { encoding: 'utf8', ...options }); - if (options?.mode) { - await chmod(filePath, options.mode); + try { + release = await lock(filePath, { + retries: { + retries: 10, + factor: 2, + minTimeout: 100, + maxTimeout: 3000, + }, + lockfilePath, + }); + } catch (error) { + throw new Error(`Failed to acquire lock for ${filePath}: ${error}`); + } + + try { + await writeFile(filePath, content, { encoding: 'utf8', ...options }); + if (options?.mode) { + await chmod(filePath, options.mode); + } + } catch (error) { + throw new Error(`Failed to write to file ${filePath}: ${error}`); + } finally { + if (release) { + try { + await release(); + } catch (error) { + // Log but don't throw on release failure + Logger.error(`Failed to release lock for ${filePath}:`, error); + } + } } - await release(); }