Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 | 2x 2x 2x 15x 15x 15x 15x 15x 15x 15x 15x 10x 10x 9x 9x 10x 2x 2x 2x 1x 1x 2x 2x 1x 2x 1x 1x 1x 1x 15x 1x 1x 1x 1x 1x 1x 1x 2x 2x 1x 1x 1x | import { IDisposable } from '../shared/IDisposable';
import { IAgingCache, AgingCacheWriteStatus, KeyValueArray, IAgingCacheWrite } from './IAgingCache';
import { IAgedQueue } from '../queue/IAgedQueue';
import { Logger } from '../shared/Logger';
import { IStorageHierarchy } from '../storage/IStorageHierarchy';
import { IAgingCacheDeleteStrategy, IAgingCacheSetStrategy } from './IAgingCacheWriteStrategy';
/**
* A cache that will replace entries in the order specified by the input IAgedQueue
*/
export class AgingCache<TKey, TValue> implements IAgingCache<TKey, TValue>, IDisposable {
private readonly logger = Logger.get(AgingCache.name);
private readonly purgeInterval: number;
private purgeTimer?: NodeJS.Timeout;
private purgePromise?: Promise<void>;
/**
* @param hierarchy The storage hierarchy to operate on
* @param evictQueue The keys in the order to evict
* @param setStrategy The implementation for setting keys
* @param deleteStrategy The implementation for deleting keys
* @param purgeInterval The interval to check for old entries in seconds
*/
constructor(
private readonly hierarchy: IStorageHierarchy<TKey, TValue>,
private readonly evictQueue: IAgedQueue<TKey>,
private readonly setStrategy: IAgingCacheSetStrategy<TKey, TValue>,
private readonly deleteStrategy: IAgingCacheDeleteStrategy<TKey, TValue>,
private readonly evictAtLevel?: number,
purgeInterval = 30
) {
this.purgeInterval = purgeInterval * 1000;
// eslint-disable-next-line @typescript-eslint/no-misused-promises
this.purgeTimer = setInterval(this.purge, this.purgeInterval);
}
/**
* Clean up the object when it's no longer used. After a dispose(), an object
* is no longer guaranteed to be usable.
*/
public dispose(): Promise<void> | void {
this.logger.info(`Cleaning up cache`);
if (this.purgeTimer) {
clearInterval(this.purgeTimer);
this.purgeTimer = undefined;
}
return this.purgePromise;
}
/**
* @param key The key to retrieve
* @returns The value if it's in the cache or undefined
*/
public get(key: TKey, force = false): Promise<TValue | null> {
this.logger.debug(`Getting Key: ${key}`);
return this.hierarchy.getAtLevel(key, undefined, !force).then(agedValue => {
if (agedValue) {
return agedValue.value;
}
return null;
});
}
/**
* @param key The key to set
* @param value The value to set
* @returns If setting the value was successful
*/
public set(key: TKey, value: TValue, force = false): Promise<IAgingCacheWrite<TValue>> {
this.logger.debug(`Setting Key: ${key}`);
if (this.evictQueue.isNextExpired()) {
void this.evict();
}
return this.setStrategy.set(key, value, force);
}
/**
* @param key The key to the value to delete
* @returns If deleting the value was successful
*/
public delete(key: TKey, force = false): Promise<IAgingCacheWrite<TValue>> {
this.logger.debug(`Deleting Key: ${key}`);
return this.deleteStrategy.delete(key, force);
}
/**
* @returns The keys that are currently in the cache
*/
public keys(): Promise<TKey[]> {
this.logger.debug('Getting Key List');
return this.hierarchy.getKeysAtTopLevel();
}
/**
* @param key The key to the value to clear from cache layers
* @param force If true write to levels below the persistence layer
* @returns If the write succeeded or the error condition
*/
public clear(key: TKey, force?: boolean): Promise<IAgingCacheWrite<TValue>> {
return this.deleteStrategy.evict(key, this.evictAtLevel, force);
}
/**
* @returns The next value that's set to expire or null when nothing will expire
*/
public peek(): Promise<TValue | null> {
const nextKey = this.evictQueue.next();
Iif (nextKey) {
return this.hierarchy.getValueAtBottomLevel(nextKey).then(v => (v ? v.value : null));
}
return Promise.resolve(null);
}
public load(keyValues: KeyValueArray<TKey, TValue>): Promise<number> {
const promises = keyValues.map(kv => {
return this.setStrategy.load(kv.key, kv.val, this.evictAtLevel);
});
return Promise.all(promises).then(all => {
return all.reduce((acc, next) => {
return next.status === AgingCacheWriteStatus.Success ||
next.status === AgingCacheWriteStatus.PartialWrite
? acc++
: acc;
}, 0);
});
}
/**
* Purge the cache of stale entries instead of waiting for a periodic check
* @return A promise to track when the purge finishes
*/
public purge = (): Promise<void> => {
if (!this.purgePromise) {
this.logger.debug(`Starting Purge: ${Date.now()}`);
this.purgePromise = this.purgeNext().then(() => (this.purgePromise = undefined));
}
return this.purgePromise;
};
private purgeNext(): Promise<void> {
if (this.evictQueue.isNextExpired()) {
return this.evict().then(write => {
Iif (write?.status === AgingCacheWriteStatus.Success) {
return this.purgeNext();
}
});
}
return Promise.resolve();
}
private evict(): Promise<IAgingCacheWrite<TValue> | null> {
const nextKey = this.evictQueue.next();
if (nextKey) {
this.logger.debug(`Evicting Key: ${nextKey}`);
return this.deleteStrategy.evict(nextKey, this.evictAtLevel);
}
return Promise.resolve(null);
}
}
|