Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft of MapWithExpiration class #9420

Closed
wants to merge 1 commit into from
Closed
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
72 changes: 64 additions & 8 deletions packages/drivers/odsp-driver/src/odspCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* Licensed under the MIT License.
*/

import { IDisposable } from "@fluidframework/common-definitions";
import { PromiseCache } from "@fluidframework/common-utils";
import {
IOdspResolvedUrl,
Expand Down Expand Up @@ -58,15 +59,74 @@ class GarbageCollector<TKey> {
}
}

class MapWithExpiration<TKey, TValue> extends Map<TKey, TValue> implements IDisposable {
public disposed: boolean = false;
private readonly expirationTimeouts = new Map<TKey, ReturnType<typeof setTimeout>>();

Copy link
Contributor

@NicholasCouri NicholasCouri Mar 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking more about it and I like it - We could simply rename the PromiseCache's GarbageCollector to MapWithExpiration + add the IDisposable ?

constructor(
private readonly expiryMs: number,
) {
super();
}
private scheduleExpiration(key: TKey) {
this.expirationTimeouts.set(
key,
setTimeout(
() => { this.delete(key); },
this.expiryMs,
),
);
}

private cancelExpiration(key: TKey) {
const timeout = this.expirationTimeouts.get(key);
if (timeout !== undefined) {
clearTimeout(timeout);
this.expirationTimeouts.delete(key);
}
}

get(key: TKey): TValue | undefined {
return super.get(key);
}

set(key: TKey, value: TValue): this {
// Sliding window expiration policy (on write)
this.cancelExpiration(key);
this.scheduleExpiration(key);

return super.set(key, value);
}

delete(key: TKey): boolean {
this.cancelExpiration(key);
return super.delete(key);
}

dispose(_error?: Error): void {
if (this.disposed) {
return;
}
this.disposed = true;
Array.from(this).forEach(([key]) => this.cancelExpiration(key));
}
}

/**
* Default local-only implementation of IPersistedCache,
* used if no persisted cache is provided by the host
*/
export class LocalPersistentCache implements IPersistedCache {
private readonly cache = new Map<string, any>();
private readonly gc = new GarbageCollector<string>((key) => this.cache.delete(key));
export class LocalPersistentCache implements IPersistedCache, IDisposable {
public get disposed(): boolean { return this.cache.disposed; }
private readonly cache: MapWithExpiration<string, any>;

public constructor(private readonly snapshotExpiryPolicy = 30 * 1000) {}
public constructor(snapshotExpiryPolicy = 30 * 1000) {
this.cache = new MapWithExpiration<string, any>(snapshotExpiryPolicy);
}

public dispose(error?: Error): void {
this.cache.dispose(error);
}

async get(entry: ICacheEntry): Promise<any> {
const key = this.keyFromEntry(entry);
Expand All @@ -77,10 +137,6 @@ export class LocalPersistentCache implements IPersistedCache {
async put(entry: ICacheEntry, value: any) {
const key = this.keyFromEntry(entry);
this.cache.set(key, value);

// Do not keep items too long in memory
this.gc.cancel(key);
this.gc.schedule(key, this.snapshotExpiryPolicy);
}

async removeEntries(file: IFileEntry): Promise<void> {
Expand Down