mirror of
https://github.com/fosrl/pangolin.git
synced 2026-05-08 05:17:23 +00:00
fix: memory improvements
- SQLite: enable WAL mode and PRAGMA performance settings - ws.ts (public + private): fix clientConfigVersions memory leak - internal server: add rate limiting and request timeouts - audit log: fix flush re-queue feedback loop - memory: add monitoring instrumentation - security: remove debug log of full request body
This commit is contained in:
@@ -41,7 +41,7 @@ export async function exchangeSession(
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<any> {
|
||||
logger.debug("Exchange session: Badger sent", req.body);
|
||||
logger.debug("Exchange session: Badger request received");
|
||||
|
||||
const parsedBody = exchangeSessionBodySchema.safeParse(req.body);
|
||||
|
||||
|
||||
@@ -84,14 +84,14 @@ async function flushAuditLogs() {
|
||||
logger.debug(`Flushed ${logsToWrite.length} audit logs to database`);
|
||||
} catch (error) {
|
||||
logger.error("Error flushing audit logs:", error);
|
||||
// On transaction error, put logs back at the front of the buffer to retry
|
||||
// but only if buffer isn't too large
|
||||
if (auditLogBuffer.length < MAX_BUFFER_SIZE - logsToWrite.length) {
|
||||
auditLogBuffer.unshift(...logsToWrite);
|
||||
logger.info(`Re-queued ${logsToWrite.length} audit logs for retry`);
|
||||
} else {
|
||||
logger.error(`Buffer full, dropped ${logsToWrite.length} audit logs`);
|
||||
}
|
||||
// On transaction error, drop the logs rather than re-queuing them.
|
||||
// The previous re-queue approach created a positive feedback loop:
|
||||
// failed flush → re-queue → larger next flush → longer DB lock →
|
||||
// higher chance of next failure → repeat. This caused unbounded
|
||||
// memory growth on SQLite where write contention is common.
|
||||
// Audit logs are best-effort telemetry — losing a batch on error
|
||||
// is acceptable; leaking memory until the process crashes is not.
|
||||
logger.warn(`Dropped ${logsToWrite.length} audit logs after flush failure`);
|
||||
} finally {
|
||||
isFlushInProgress = false;
|
||||
// If buffer filled up while we were flushing, flush again
|
||||
|
||||
@@ -80,7 +80,7 @@ export async function verifyResourceSession(
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<any> {
|
||||
logger.debug("Verify session: Badger sent", req.body); // remove when done testing
|
||||
logger.debug("Verify session: Badger request received");
|
||||
|
||||
const parsedBody = verifyResourceSessionSchema.safeParse(req.body);
|
||||
|
||||
|
||||
@@ -80,6 +80,10 @@ const removeClient = async (
|
||||
const updatedClients = existingClients.filter((client) => client !== ws);
|
||||
if (updatedClients.length === 0) {
|
||||
connectedClients.delete(mapKey);
|
||||
// Clean up config version tracking to prevent unbounded memory
|
||||
// growth. Without this, every unique clientId that ever connects
|
||||
// leaves a permanent entry in clientConfigVersions.
|
||||
clientConfigVersions.delete(clientId);
|
||||
|
||||
logger.info(
|
||||
`All connections removed for ${clientType.toUpperCase()} ID: ${clientId}`
|
||||
@@ -507,6 +511,12 @@ const disconnectClient = async (clientId: string): Promise<boolean> => {
|
||||
}
|
||||
});
|
||||
|
||||
// Eagerly clean up tracking maps. The close event handlers will also
|
||||
// call removeClient, but if the socket is already in CLOSING state
|
||||
// the close event may never fire, leaving zombie entries.
|
||||
connectedClients.delete(mapKey);
|
||||
clientConfigVersions.delete(clientId);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user