Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions back/api/script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
}
},
Expand Down
11 changes: 11 additions & 0 deletions back/services/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});

Expand Down
70 changes: 54 additions & 16 deletions back/shared/utils.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { lock } from 'proper-lockfile';
import os from 'os';
import path from 'path';
import { writeFile, open, chmod } from 'fs/promises';
import { writeFile, open, chmod, FileHandle } from 'fs/promises';
import { fileExist } from '../config/util';
import Logger from '../loaders/logger';

function getUniqueLockPath(filePath: string) {
const sanitizedPath = filePath
Expand All @@ -19,24 +20,61 @@ export async function writeFileWithLock(
if (typeof options === 'string') {
options = { encoding: options };
}

// Ensure file exists before locking
if (!(await fileExist(filePath))) {
const fileHandle = await open(filePath, 'w');
fileHandle.close();
let fileHandle: FileHandle | undefined;
try {
fileHandle = await open(filePath, 'w');
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to create file ${filePath}: ${errorMessage}`);
} finally {
if (fileHandle !== undefined) {
try {
await fileHandle.close();
} catch (closeError) {
// Log close error but don't throw to avoid masking the original error
Logger.error(`Failed to close file handle for ${filePath}:`, closeError);
}
}
}
}

const lockfilePath = getUniqueLockPath(filePath);
let release: (() => Promise<void>) | undefined;

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) {
const errorMessage = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to acquire lock for ${filePath}: ${errorMessage}`);
}

try {
await writeFile(filePath, content, { encoding: 'utf8', ...options });
if (options?.mode) {
await chmod(filePath, options.mode);
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to write to file ${filePath}: ${errorMessage}`);
} 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();
}