fix: implement write-lock for agent cache

This commit is contained in:
Aarnav Tale 2025-01-10 14:06:22 +05:30
parent 5569ba4660
commit eb922c9318
No known key found for this signature in database
3 changed files with 18 additions and 10 deletions

View File

@ -159,7 +159,7 @@ export default function MachineRow({ machine, routes, magic, users, stats }: Pro
<p className="font-semibold leading-snug"> <p className="font-semibold leading-snug">
{hinfo.getTSVersion(stats)} {hinfo.getTSVersion(stats)}
</p> </p>
<p className="text-sm text-gray-500 dark:text-gray-300"> <p className="text-sm text-gray-500 dark:text-gray-300 max-w-48 truncate">
{hinfo.getOSInfo(stats)} {hinfo.getOSInfo(stats)}
</p> </p>
</> </>

View File

@ -1,4 +1,4 @@
import type { HostInfo } from '~/utils/types'; import type { HostInfo } from '~/types';
export function getTSVersion(host: HostInfo) { export function getTSVersion(host: HostInfo) {
const { IPNVersion } = host; const { IPNVersion } = host;

View File

@ -4,7 +4,7 @@ import { setTimeout as pSetTimeout } from 'node:timers/promises';
import type { LoaderFunctionArgs } from 'react-router'; import type { LoaderFunctionArgs } from 'react-router';
import type { HostInfo } from '~/types'; import type { HostInfo } from '~/types';
import { WebSocket } from 'ws'; import { WebSocket } from 'ws';
import { log } from './log'; import log from './log';
// Essentially a HashMap which invalidates entries after a certain time. // Essentially a HashMap which invalidates entries after a certain time.
// It also is capable of syncing as a compressed file to disk. // It also is capable of syncing as a compressed file to disk.
@ -13,6 +13,7 @@ class TimedCache<K, V> {
private _timeCache = new Map<K, number>(); private _timeCache = new Map<K, number>();
private defaultTTL: number; private defaultTTL: number;
private filepath: string; private filepath: string;
private writeLock = false;
constructor(defaultTTL: number, filepath: string) { constructor(defaultTTL: number, filepath: string) {
this.defaultTTL = defaultTTL; this.defaultTTL = defaultTTL;
@ -51,21 +52,28 @@ class TimedCache<K, V> {
this._timeCache.set(key, expires); this._timeCache.set(key, expires);
} }
} catch (e) { } catch (e) {
if (e['code'] !== 'ENOENT') { if (e.code === 'ENOENT') {
// log.error('CACH', 'Failed to load cache from file', e); log.debug('CACH', 'Cache file not found, creating new cache');
return; return;
} }
// log.debug('CACH', 'Cache file not found, creating new cache'); log.error('CACH', 'Failed to load cache from file', e);
} }
} }
private async syncToFile() { private async syncToFile() {
while (this.writeLock) {
await pSetTimeout(100);
}
this.writeLock = true;
const data = Array.from(this._cache.entries()).map(([key, value]) => { const data = Array.from(this._cache.entries()).map(([key, value]) => {
return { key, value, expires: this._timeCache.get(key) } return { key, value, expires: this._timeCache.get(key) }
}); });
await writeFile(this.filepath, JSON.stringify(data), 'utf-8'); await writeFile(this.filepath, JSON.stringify(data), 'utf-8');
await this.loadFromFile();
this.writeLock = false;
} }
} }
@ -92,7 +100,7 @@ export async function queryAgent(nodes: string[]) {
const cached: Record<string, HostInfo> = {}; const cached: Record<string, HostInfo> = {};
await Promise.all(nodes.map(async node => { await Promise.all(nodes.map(async node => {
const cachedData = await cache.get(node); const cachedData = await cache?.get(node);
if (cachedData) { if (cachedData) {
cached[node] = cachedData; cached[node] = cachedData;
} }
@ -114,17 +122,17 @@ export async function queryAgent(nodes: string[]) {
agentSocket.send(JSON.stringify({ NodeIDs: uncached })); agentSocket.send(JSON.stringify({ NodeIDs: uncached }));
const returnData = await new Promise<Record<string, HostInfo> | void>((resolve, reject) => { const returnData = await new Promise<Record<string, HostInfo> | void>((resolve, reject) => {
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
agentSocket.removeAllListeners('message'); agentSocket?.removeAllListeners('message');
resolve(); resolve();
}, 3000); }, 3000);
agentSocket.on('message', async (message: string) => { agentSocket?.on('message', async (message: string) => {
const data = JSON.parse(message.toString()); const data = JSON.parse(message.toString());
if (Object.keys(data).length === 0) { if (Object.keys(data).length === 0) {
resolve(); resolve();
} }
agentSocket.removeAllListeners('message'); agentSocket?.removeAllListeners('message');
resolve(data); resolve(data);
}); });
}); });