fix: memory leak hunting and reconnecting on data error
This commit is contained in:
@@ -41,7 +41,14 @@ const RETRYABLE_SAVE_ERROR_CODES = new Set([
|
||||
'EBUSY'
|
||||
]);
|
||||
|
||||
let saveQueue: Promise<void> = Promise.resolve();
|
||||
interface PendingSaveWaiter {
|
||||
reject: (error: unknown) => void;
|
||||
resolve: () => void;
|
||||
}
|
||||
|
||||
let pendingSaveSnapshot: Buffer | null = null;
|
||||
let pendingSaveWaiters: PendingSaveWaiter[] = [];
|
||||
let saveInProgress = false;
|
||||
|
||||
function wait(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
@@ -146,16 +153,51 @@ async function writeDatabaseSnapshot(snapshot: Buffer): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
function settleSaveWaiters(waiters: PendingSaveWaiter[], error?: unknown): void {
|
||||
for (const waiter of waiters) {
|
||||
if (error === undefined) {
|
||||
waiter.resolve();
|
||||
} else {
|
||||
waiter.reject(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function drainDatabaseSaveQueue(): Promise<void> {
|
||||
if (saveInProgress) {
|
||||
return;
|
||||
}
|
||||
|
||||
saveInProgress = true;
|
||||
|
||||
try {
|
||||
while (pendingSaveSnapshot) {
|
||||
const snapshot = pendingSaveSnapshot;
|
||||
const waiters = pendingSaveWaiters;
|
||||
|
||||
pendingSaveSnapshot = null;
|
||||
pendingSaveWaiters = [];
|
||||
|
||||
try {
|
||||
await writeDatabaseSnapshot(snapshot);
|
||||
settleSaveWaiters(waiters);
|
||||
} catch (error) {
|
||||
settleSaveWaiters(waiters, error);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
saveInProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function atomicSave(data: Uint8Array): Promise<void> {
|
||||
const snapshot = Buffer.from(data);
|
||||
const saveTask = saveQueue.then(
|
||||
() => writeDatabaseSnapshot(snapshot),
|
||||
() => writeDatabaseSnapshot(snapshot)
|
||||
);
|
||||
|
||||
saveQueue = saveTask.catch(() => {});
|
||||
|
||||
return saveTask;
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
pendingSaveSnapshot = snapshot;
|
||||
pendingSaveWaiters.push({ resolve, reject });
|
||||
void drainDatabaseSaveQueue();
|
||||
});
|
||||
}
|
||||
|
||||
export async function initializeDatabase(): Promise<void> {
|
||||
|
||||
@@ -62,6 +62,9 @@ import { listRunningProcessNames } from '../process-list';
|
||||
import { detectActiveGame } from '../game-detection';
|
||||
|
||||
const DEFAULT_MIME_TYPE = 'application/octet-stream';
|
||||
const MAX_ACTIVE_DESKTOP_NOTIFICATIONS = 20;
|
||||
const activeDesktopNotifications = new Set<Notification>();
|
||||
const desktopNotificationCleanups = new Map<Notification, () => void>();
|
||||
const FILE_CLIPBOARD_FORMATS = [
|
||||
'x-special/gnome-copied-files',
|
||||
'text/uri-list',
|
||||
@@ -399,9 +402,16 @@ export function setupSystemHandlers(): void {
|
||||
icon: getWindowIconPath(),
|
||||
silent: true
|
||||
});
|
||||
|
||||
notification.on('click', () => {
|
||||
const cleanup = () => {
|
||||
notification.removeListener('click', handleClick);
|
||||
notification.removeListener('close', cleanup);
|
||||
notification.removeListener('failed', cleanup);
|
||||
activeDesktopNotifications.delete(notification);
|
||||
desktopNotificationCleanups.delete(notification);
|
||||
};
|
||||
const handleClick = () => {
|
||||
if (!mainWindow) {
|
||||
cleanup();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -414,7 +424,26 @@ export function setupSystemHandlers(): void {
|
||||
}
|
||||
|
||||
mainWindow.focus();
|
||||
});
|
||||
cleanup();
|
||||
notification.close();
|
||||
};
|
||||
|
||||
notification.on('click', handleClick);
|
||||
notification.once('close', cleanup);
|
||||
notification.once('failed', cleanup);
|
||||
activeDesktopNotifications.add(notification);
|
||||
desktopNotificationCleanups.set(notification, cleanup);
|
||||
|
||||
while (activeDesktopNotifications.size > MAX_ACTIVE_DESKTOP_NOTIFICATIONS) {
|
||||
const oldestNotification = activeDesktopNotifications.values().next().value;
|
||||
|
||||
if (!oldestNotification) {
|
||||
break;
|
||||
}
|
||||
|
||||
desktopNotificationCleanups.get(oldestNotification)?.();
|
||||
oldestNotification.close();
|
||||
}
|
||||
|
||||
notification.show();
|
||||
} catch {
|
||||
|
||||
Reference in New Issue
Block a user