Add diff cache size setting

This commit is contained in:
Andras Schmelczer 2025-11-18 21:50:06 +00:00
parent c92aa63d71
commit 0bfd8a6df4
5 changed files with 75 additions and 19 deletions

View file

@ -9,6 +9,7 @@ export interface SyncSettings {
maxFileSizeMB: number; maxFileSizeMB: number;
ignorePatterns: string[]; ignorePatterns: string[];
webSocketRetryIntervalMs: number; webSocketRetryIntervalMs: number;
diffCacheSizeMB: number;
} }
export const DEFAULT_SETTINGS: SyncSettings = { export const DEFAULT_SETTINGS: SyncSettings = {
@ -19,7 +20,8 @@ export const DEFAULT_SETTINGS: SyncSettings = {
isSyncEnabled: false, isSyncEnabled: false,
maxFileSizeMB: 10, maxFileSizeMB: 10,
ignorePatterns: [], ignorePatterns: [],
webSocketRetryIntervalMs: 3500 webSocketRetryIntervalMs: 3500,
diffCacheSizeMB: 4
}; };
export class Settings { export class Settings {

View file

@ -38,7 +38,8 @@ export class SyncClient {
private readonly _logger: Logger, private readonly _logger: Logger,
private readonly connectionStatus: ConnectionStatus, private readonly connectionStatus: ConnectionStatus,
private readonly cursorTracker: CursorTracker, private readonly cursorTracker: CursorTracker,
private readonly fileChangeNotifier: FileChangeNotifier private readonly fileChangeNotifier: FileChangeNotifier,
private readonly contentCache: FixedSizeDocumentCache
) { ) {
this.settings.addOnSettingsChangeListener( this.settings.addOnSettingsChangeListener(
async (newSettings, oldSettings) => { async (newSettings, oldSettings) => {
@ -53,6 +54,14 @@ export class SyncClient {
this.stop(); this.stop();
} }
} }
if (
newSettings.diffCacheSizeMB !== oldSettings.diffCacheSizeMB
) {
this.contentCache.resize(
newSettings.diffCacheSizeMB * 1024 * 1024
);
}
} }
); );
} }
@ -65,6 +74,10 @@ export class SyncClient {
return this.database.length; return this.database.length;
} }
public get isWebSocketConnected(): boolean {
return this.webSocketManager.isWebSocketConnected;
}
public static async create({ public static async create({
fs, fs,
persistence, persistence,
@ -152,8 +165,7 @@ export class SyncClient {
settings, settings,
syncService, syncService,
fileOperations, fileOperations,
unrestrictedSyncer, unrestrictedSyncer
contentCache
); );
const webSocketManager = new WebSocketManager( const webSocketManager = new WebSocketManager(
@ -182,7 +194,8 @@ export class SyncClient {
logger, logger,
connectionStatus, connectionStatus,
cursorTracker, cursorTracker,
fileChangeNotifier fileChangeNotifier,
contentCache
); );
logger.info("SyncClient initialised"); logger.info("SyncClient initialised");
@ -235,6 +248,7 @@ export class SyncClient {
public async reset(): Promise<void> { public async reset(): Promise<void> {
this.stop(); this.stop();
this.connectionStatus.startReset(); this.connectionStatus.startReset();
this.contentCache.clear();
await this.syncer.reset(); await this.syncer.reset();
this.history.reset(); this.history.reset();
this.database.reset(); this.database.reset();

View file

@ -34,8 +34,7 @@ export class Syncer {
settings: Settings, settings: Settings,
private readonly syncService: SyncService, private readonly syncService: SyncService,
private readonly operations: FileOperations, private readonly operations: FileOperations,
private readonly internalSyncer: UnrestrictedSyncer, private readonly internalSyncer: UnrestrictedSyncer
private readonly contentCache: FixedSizeDocumentCache
) { ) {
this.syncQueue = new PQueue({ this.syncQueue = new PQueue({
concurrency: settings.getSettings().syncConcurrency concurrency: settings.getSettings().syncConcurrency
@ -252,7 +251,6 @@ export class Syncer {
public async reset(): Promise<void> { public async reset(): Promise<void> {
await this.waitUntilFinished(); await this.waitUntilFinished();
this.contentCache.clear();
} }
public async syncRemotelyUpdatedFile( public async syncRemotelyUpdatedFile(

View file

@ -236,4 +236,40 @@ describe("fixedSizeDocumentCache", () => {
assert.equal(cache.get(2), doc2); assert.equal(cache.get(2), doc2);
assert.equal(cache.get(3), doc3); assert.equal(cache.get(3), doc3);
}); });
it("resizeToLargerSizeNoEviction", async () => {
const cache = new FixedSizeDocumentCache(4);
const doc1 = new Uint8Array([1, 2]);
const doc2 = new Uint8Array([3, 4]);
cache.put(1, doc1);
cache.put(2, doc2);
cache.resize(10);
assert.equal(cache.get(1), doc1);
assert.equal(cache.get(2), doc2);
});
it("resizeCausesMultipleEvictions", async () => {
const cache = new FixedSizeDocumentCache(10);
const doc1 = new Uint8Array([1, 2]);
const doc2 = new Uint8Array([3, 4]);
const doc3 = new Uint8Array([5, 6]);
const doc4 = new Uint8Array([7, 8]);
cache.put(1, doc1);
cache.put(2, doc2);
cache.put(3, doc3);
cache.put(4, doc4);
// Cache has 8 bytes total
cache.resize(2);
// Should evict doc1, doc2, doc3 to get down to 2 bytes
assert.equal(cache.get(1), undefined);
assert.equal(cache.get(2), undefined);
assert.equal(cache.get(3), undefined);
assert.equal(cache.get(4), doc4);
});
}); });

View file

@ -14,14 +14,12 @@ class LRUNode {
// evicting the least recently used documents when the size limit is exceeded. // evicting the least recently used documents when the size limit is exceeded.
export class FixedSizeDocumentCache { export class FixedSizeDocumentCache {
private readonly maxSizeInBytes: number;
private currentSizeInBytes: number; private currentSizeInBytes: number;
private readonly cache: Map<VaultUpdateId, LRUNode>; private readonly cache: Map<VaultUpdateId, LRUNode>;
private head: LRUNode | null; // Least recently used private head: LRUNode | null; // Least recently used
private tail: LRUNode | null; // Most recently used private tail: LRUNode | null; // Most recently used
public constructor(maxSizeInBytes: number) { public constructor(private maxSizeInBytes: number) {
this.maxSizeInBytes = maxSizeInBytes;
this.currentSizeInBytes = 0; this.currentSizeInBytes = 0;
this.cache = new Map(); this.cache = new Map();
this.head = null; this.head = null;
@ -56,14 +54,7 @@ export class FixedSizeDocumentCache {
this.cache.set(updateId, newNode); this.cache.set(updateId, newNode);
this.addToTail(newNode); this.addToTail(newNode);
this.currentSizeInBytes += content.byteLength; this.currentSizeInBytes += content.byteLength;
this.fitBelowMaxSize();
// Evict least recently used documents if over size limit
while (this.currentSizeInBytes > this.maxSizeInBytes && this.head) {
const lruNode = this.head;
this.removeNode(lruNode);
this.cache.delete(lruNode.key);
this.currentSizeInBytes -= lruNode.value.byteLength;
}
} }
public clear(): void { public clear(): void {
@ -73,6 +64,21 @@ export class FixedSizeDocumentCache {
this.currentSizeInBytes = 0; this.currentSizeInBytes = 0;
} }
public resize(newMaxSizeInBytes: number): void {
this.maxSizeInBytes = newMaxSizeInBytes;
this.fitBelowMaxSize();
}
private fitBelowMaxSize(): void {
// Evict least recently used documents if over size limit
while (this.currentSizeInBytes > this.maxSizeInBytes && this.head) {
const lruNode = this.head;
this.removeNode(lruNode);
this.cache.delete(lruNode.key);
this.currentSizeInBytes -= lruNode.value.byteLength;
}
}
private removeNode(node: LRUNode): void { private removeNode(node: LRUNode): void {
if (node.prev) { if (node.prev) {
node.prev.next = node.next; node.prev.next = node.next;