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

refactoring loading cache and metrics #127

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
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
109 changes: 30 additions & 79 deletions src/builder/CacheBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { NoopMetrics, StandardMetrics } from '../cache';
import { BoundedCache } from '../cache/bounded/BoundedCache';
import { BoundlessCache } from '../cache/boundless/BoundlessCache';
import { Cache } from '../cache/Cache';
import { Expirable } from '../cache/expiration/Expirable';
import { ExpirationCache } from '../cache/expiration/ExpirationCache';
import { MaxAgeDecider } from '../cache/expiration/MaxAgeDecider';
import { KeyType } from '../cache/KeyType';
import { DefaultLoadingCache } from '../cache/loading/DefaultLoadingCache';
import { Loader } from '../cache/loading/Loader';
import { LoadingCache } from '../cache/loading/LoadingCache';
import { MetricsCache } from '../cache/metrics/MetricsCache';
import { RemovalListener } from '../cache/RemovalListener';
import { Weigher } from '../cache/Weigher';

Expand All @@ -17,24 +16,26 @@ export interface CacheBuilder<K extends KeyType, V> {
* Set a listener that will be called every time something is removed
* from the cache.
*/
withRemovalListener(listener: RemovalListener<K, V>): this;
withRemovalListener(listener: RemovalListener<K, V>): CacheBuilder<K, V>;

/**
* Set the maximum number of items to keep in the cache before evicting
* something.
*/
maxSize(size: number): this;
maxSize(size: number): CacheBuilder<K, V>;

/**
* Set a function to use to determine the size of a cached object.
*/
withWeigher(weigher: Weigher<K, V>): this;
withWeigher(weigher: Weigher<K, V>): CacheBuilder<K, V>;

/**
* Change to a cache where get can also resolve values if provided with
* a function as the second argument.
*
* @deprecated - all caches support loading with a second parameter
*/
loading(): LoadingCacheBuilder<K, V>;
loading(): CacheBuilder<K, V>;

/**
* Change to a loading cache, where the get-method will return instances
Expand All @@ -46,18 +47,18 @@ export interface CacheBuilder<K extends KeyType, V> {
* Set that the cache should expire items some time after they have been
* written to the cache.
*/
expireAfterWrite(time: number | MaxAgeDecider<K, V>): this;
expireAfterWrite(time: number | MaxAgeDecider<K, V>): CacheBuilder<K, V>;

/**
* Set that the cache should expire items some time after they have been
* read from the cache.
*/
expireAfterRead(time: number | MaxAgeDecider<K, V>): this;
expireAfterRead(time: number | MaxAgeDecider<K, V>): CacheBuilder<K, V>;

/**
* Activate tracking of metrics for this cache.
*/
metrics(): this;
metrics(): CacheBuilder<K, V>;

/**
* Build the cache.
Expand All @@ -82,6 +83,7 @@ export class CacheBuilderImpl<K extends KeyType, V> implements CacheBuilder<K, V
private optMaxWriteAge?: MaxAgeDecider<K, V>;
private optMaxNoReadAge?: MaxAgeDecider<K, V>;
private optMetrics: boolean = false;
private optLoader?: Loader<K, V>;

/**
* Set a listener that will be called every time something is removed
Expand Down Expand Up @@ -129,9 +131,11 @@ export class CacheBuilderImpl<K extends KeyType, V> implements CacheBuilder<K, V
* a function as the second argument.
*
* @returns self
*
* @deprecated all caches accept a loader as a parameter.
*/
public loading(): LoadingCacheBuilder<K, V> {
return new LoadingCacheBuilderImpl(this, null);
public loading(): CacheBuilder<K, V> {
return this;
}

/**
Expand All @@ -146,7 +150,8 @@ export class CacheBuilderImpl<K extends KeyType, V> implements CacheBuilder<K, V
if(typeof loader !== 'function') {
throw new Error('Loader should be a function that takes a key and returns a value or a promise that resolves to a value');
}
return new LoadingCacheBuilderImpl(this, loader);
this.optLoader = loader;
return this as LoadingCacheBuilder<K, V>;
}

/**
Expand Down Expand Up @@ -208,6 +213,10 @@ export class CacheBuilderImpl<K extends KeyType, V> implements CacheBuilder<K, V
*/
public build() {
let cache: Cache<K, V>;

const metrics = this.optMetrics ? new StandardMetrics() : NoopMetrics;
const loader = this.optLoader;

if(typeof this.optMaxWriteAge !== 'undefined' || typeof this.optMaxNoReadAge !== 'undefined') {
/*
* Requested expiration - wrap the base cache a bit as it needs
Expand All @@ -230,90 +239,32 @@ export class CacheBuilderImpl<K extends KeyType, V> implements CacheBuilder<K, V

removalListener: this.optRemovalListener,

parent: parentCache
parent: parentCache,
metrics,
loader,
});
} else {
if(this.optMaxSize) {
cache = new BoundedCache({
maxSize: this.optMaxSize,
weigher: this.optWeigher,
removalListener: this.optRemovalListener
removalListener: this.optRemovalListener,
metrics,
loader,
});
} else {
cache = new BoundlessCache({
removalListener: this.optRemovalListener
removalListener: this.optRemovalListener,
metrics,
loader,
});
}
}

if(this.optMetrics) {
// Collect metrics if requested
cache = new MetricsCache({
parent: cache
});
}

return cache;
}
}

class LoadingCacheBuilderImpl<K extends KeyType, V> implements LoadingCacheBuilder<K, V> {
private parent: CacheBuilder<K, V>;
private loader: Loader<K, V> | null;

public constructor(parent: CacheBuilder<K, V>, loader: Loader<K, V> | null) {
this.parent = parent;
this.loader = loader;
}

public withRemovalListener(listener: RemovalListener<K, V>): this {
this.parent.withRemovalListener(listener);
return this;
}

public maxSize(size: number): this {
this.parent.maxSize(size);
return this;
}

public withWeigher(weigher: Weigher<K, V>): this {
this.parent.withWeigher(weigher);
return this;
}

public loading(): LoadingCacheBuilder<K, V> {
throw new Error('Already building a loading cache');
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
public withLoader(loader: Loader<K, V>): LoadingCacheBuilder<K, V> {
throw new Error('Already building a loading cache');
}

public expireAfterWrite(time: number | MaxAgeDecider<K, V>): this {
this.parent.expireAfterWrite(time);
return this;
}

public expireAfterRead(time: number | MaxAgeDecider<K, V>): this {
this.parent.expireAfterRead(time);
return this;
}

public metrics(): this {
this.parent.metrics();
return this;
}

public build(): LoadingCache<K, V> {
return new DefaultLoadingCache({
loader: this.loader,

parent: this.parent.build()
});
}
}

/**
* Helper function to create a weigher that uses an Expirable object.
*
Expand Down
3 changes: 3 additions & 0 deletions src/cache/AbstractCache.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Cache } from './Cache';
import { KeyType } from './KeyType';
import { Loader } from './loading';
import { LoaderResult } from './loading/LoaderManager';
import { Metrics } from './metrics/Metrics';

/**
Expand All @@ -12,6 +14,7 @@ export abstract class AbstractCache<K extends KeyType, V> implements Cache<K, V>
public abstract weightedSize: number;

public abstract set(key: K, value: V): V | null;
public abstract get<R extends V | undefined | null>(key: K, loader?: Loader<K, V> | undefined): Promise<LoaderResult<R>> ;
public abstract getIfPresent(key: K): V | null;
public abstract peek(key: K): V | null;
public abstract has(key: K): boolean;
Expand Down
16 changes: 15 additions & 1 deletion src/cache/Cache.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { KeyType } from './KeyType';
import { Loader } from './loading/Loader';
import { LoaderResult } from './loading/LoaderManager';
import { Metrics } from './metrics/Metrics';

/**
* Cache for a mapping between keys and values.
*/
export interface Cache<K extends KeyType, V> {

/**
* The maximum size the cache can be. Will be -1 if the cache is unbounded.
*/
Expand Down Expand Up @@ -46,6 +47,19 @@ export interface Cache<K extends KeyType, V> {
*/
getIfPresent(key: K): V | null;

/**
* Get cached value or load it if not currently cached. Updates the usage
* of the key.
*
* @param key -
* key to get
* @param loader -
* optional loader to use for loading the object
* @returns
* promise that resolves to the loaded value
*/
get<R extends V | null | undefined>(key: K, loader: Loader<K, V>): Promise<LoaderResult<R>>;

/**
* Peek to see if a key is present without updating the usage of the
* key. Returns the value associated with the key or `null` if the key
Expand Down
Loading