cachette 6.0.3 → 6.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CachableValue, CacheInstance } from './CacheInstance';
|
|
1
|
+
import { CachableValue, CacheInstance, Lock } from './CacheInstance';
|
|
2
2
|
/**
|
|
3
3
|
* Write-through cache, using Redis and a local LRU cache with aligned TTLs.
|
|
4
4
|
*
|
|
@@ -71,9 +71,17 @@ export declare class WriteThroughCache extends CacheInstance {
|
|
|
71
71
|
clearMemory(): Promise<void>;
|
|
72
72
|
/**
|
|
73
73
|
* @inheritdoc
|
|
74
|
-
* Locking
|
|
75
|
-
*
|
|
76
|
-
* -
|
|
74
|
+
* Locking delegates to the Redis primary: it is the only shared backing store,
|
|
75
|
+
* so it is the only one that can provide cross-instance mutual exclusion. The
|
|
76
|
+
* per-process localCache could not. Lock keys therefore live only in Redis.
|
|
77
77
|
*/
|
|
78
78
|
isLockingSupported(): boolean;
|
|
79
|
+
/**
|
|
80
|
+
* @inheritdoc
|
|
81
|
+
*/
|
|
82
|
+
lock(resource: string, ttlMs: number, retry?: boolean): Promise<Lock>;
|
|
83
|
+
/**
|
|
84
|
+
* @inheritdoc
|
|
85
|
+
*/
|
|
86
|
+
hasLock(prefix: string): Promise<boolean>;
|
|
79
87
|
}
|
|
@@ -159,12 +159,24 @@ class WriteThroughCache extends CacheInstance_1.CacheInstance {
|
|
|
159
159
|
}
|
|
160
160
|
/**
|
|
161
161
|
* @inheritdoc
|
|
162
|
-
* Locking
|
|
163
|
-
*
|
|
164
|
-
* -
|
|
162
|
+
* Locking delegates to the Redis primary: it is the only shared backing store,
|
|
163
|
+
* so it is the only one that can provide cross-instance mutual exclusion. The
|
|
164
|
+
* per-process localCache could not. Lock keys therefore live only in Redis.
|
|
165
165
|
*/
|
|
166
166
|
isLockingSupported() {
|
|
167
|
-
return
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* @inheritdoc
|
|
171
|
+
*/
|
|
172
|
+
lock(resource, ttlMs, retry) {
|
|
173
|
+
return this.redisCacheForWriting.lock(resource, ttlMs, retry);
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* @inheritdoc
|
|
177
|
+
*/
|
|
178
|
+
hasLock(prefix) {
|
|
179
|
+
return this.redisCacheForWriting.hasLock(prefix);
|
|
168
180
|
}
|
|
169
181
|
}
|
|
170
182
|
exports.WriteThroughCache = WriteThroughCache;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WriteThroughCache.js","sourceRoot":"","sources":["../../../src/lib/WriteThroughCache.ts"],"names":[],"mappings":";;;AAAA,
|
|
1
|
+
{"version":3,"file":"WriteThroughCache.js","sourceRoot":"","sources":["../../../src/lib/WriteThroughCache.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CachableValue, CacheInstance } from './CacheInstance';
|
|
1
|
+
import { CachableValue, CacheInstance, Lock } from './CacheInstance';
|
|
2
2
|
import { RedisCache } from './RedisCache';
|
|
3
3
|
import { LocalCache } from './LocalCache';
|
|
4
4
|
|
|
@@ -190,11 +190,25 @@ export class WriteThroughCache extends CacheInstance {
|
|
|
190
190
|
|
|
191
191
|
/**
|
|
192
192
|
* @inheritdoc
|
|
193
|
-
* Locking
|
|
194
|
-
*
|
|
195
|
-
* -
|
|
193
|
+
* Locking delegates to the Redis primary: it is the only shared backing store,
|
|
194
|
+
* so it is the only one that can provide cross-instance mutual exclusion. The
|
|
195
|
+
* per-process localCache could not. Lock keys therefore live only in Redis.
|
|
196
196
|
*/
|
|
197
197
|
public isLockingSupported(): boolean {
|
|
198
|
-
return
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* @inheritdoc
|
|
203
|
+
*/
|
|
204
|
+
public lock(resource: string, ttlMs: number, retry?: boolean): Promise<Lock> {
|
|
205
|
+
return this.redisCacheForWriting.lock(resource, ttlMs, retry);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* @inheritdoc
|
|
210
|
+
*/
|
|
211
|
+
public hasLock(prefix: string): Promise<boolean> {
|
|
212
|
+
return this.redisCacheForWriting.hasLock(prefix);
|
|
199
213
|
}
|
|
200
214
|
}
|
|
@@ -155,6 +155,99 @@ describe('WriteThroughCache', () => {
|
|
|
155
155
|
});
|
|
156
156
|
});
|
|
157
157
|
|
|
158
|
+
describe('locking', () => {
|
|
159
|
+
it('reports locking as supported', () => {
|
|
160
|
+
const cache = makeFakeWriteThroughCache();
|
|
161
|
+
expect(cache.isLockingSupported()).to.be.true;
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('delegates lock() to the writing Redis instance', async () => {
|
|
165
|
+
const cache = makeFakeWriteThroughCache();
|
|
166
|
+
const spy = sinon.spy(cache['redisCacheForWriting'], 'lock');
|
|
167
|
+
|
|
168
|
+
const lock = await cache.lock('test-resource', 10000, false);
|
|
169
|
+
|
|
170
|
+
sinon.assert.calledOnceWithExactly(spy, 'test-resource', 10000, false);
|
|
171
|
+
expect(lock).to.have.property('value');
|
|
172
|
+
expect(lock.unlock).to.be.a('function');
|
|
173
|
+
|
|
174
|
+
await cache.unlock(lock);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('delegates hasLock() to the writing Redis instance', async () => {
|
|
178
|
+
const cache = makeFakeWriteThroughCache();
|
|
179
|
+
const spy = sinon.spy(cache['redisCacheForWriting'], 'hasLock');
|
|
180
|
+
|
|
181
|
+
const resource = 'test-resource-haslock';
|
|
182
|
+
const lock = await cache.lock(resource, 10000, false);
|
|
183
|
+
expect(await cache.hasLock(resource)).to.be.true;
|
|
184
|
+
sinon.assert.calledWith(spy, resource);
|
|
185
|
+
|
|
186
|
+
await cache.unlock(lock);
|
|
187
|
+
expect(await cache.hasLock(resource)).to.be.false;
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('does not write lock keys to the local cache', async () => {
|
|
191
|
+
const cache = makeFakeWriteThroughCache();
|
|
192
|
+
const resource = 'test-resource-local';
|
|
193
|
+
|
|
194
|
+
const lock = await cache.lock(resource, 10000);
|
|
195
|
+
expect(await cache['localCache'].getValue(resource)).to.be.undefined;
|
|
196
|
+
|
|
197
|
+
await cache.unlock(lock);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('acquires, blocks a contender, releases, and re-acquires against real Redis', async function (): Promise<void> {
|
|
201
|
+
if (!process.env.TEST_REDIS_URL) {
|
|
202
|
+
this.skip();
|
|
203
|
+
}
|
|
204
|
+
const cache = new WriteThroughCache(process.env.TEST_REDIS_URL as string);
|
|
205
|
+
await cache.isReady();
|
|
206
|
+
const resource = `wtc-lock-${Math.random()}`;
|
|
207
|
+
|
|
208
|
+
const lock = await cache.lock(resource, 10000);
|
|
209
|
+
expect(await cache.hasLock(resource)).to.be.true;
|
|
210
|
+
|
|
211
|
+
// A non-retrying contender must fail while the lock is held.
|
|
212
|
+
let contended = false;
|
|
213
|
+
try {
|
|
214
|
+
await cache.lock(resource, 10000, false);
|
|
215
|
+
} catch {
|
|
216
|
+
contended = true;
|
|
217
|
+
}
|
|
218
|
+
expect(contended).to.be.true;
|
|
219
|
+
|
|
220
|
+
await cache.unlock(lock);
|
|
221
|
+
expect(await cache.hasLock(resource)).to.be.false;
|
|
222
|
+
|
|
223
|
+
// Re-acquire succeeds once released.
|
|
224
|
+
const lock2 = await cache.lock(resource, 10000, false);
|
|
225
|
+
expect(lock2).to.have.property('value');
|
|
226
|
+
await cache.unlock(lock2);
|
|
227
|
+
|
|
228
|
+
await cache.quit();
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('honors lockTtl in getOrFetchValue against real Redis', async function (): Promise<void> {
|
|
232
|
+
if (!process.env.TEST_REDIS_URL) {
|
|
233
|
+
this.skip();
|
|
234
|
+
}
|
|
235
|
+
const cache = new WriteThroughCache(process.env.TEST_REDIS_URL as string);
|
|
236
|
+
await cache.isReady();
|
|
237
|
+
const lockSpy = sinon.spy(cache['redisCacheForWriting'], 'lock');
|
|
238
|
+
const key = `wtc-fetch-${Math.random()}`;
|
|
239
|
+
|
|
240
|
+
const value = await cache.getOrFetchValue(key, 60, async () => 'fetched', 5);
|
|
241
|
+
|
|
242
|
+
expect(value).to.equal('fetched');
|
|
243
|
+
sinon.assert.calledOnce(lockSpy);
|
|
244
|
+
sinon.assert.calledWith(lockSpy, `lock__${key}`, 5000);
|
|
245
|
+
|
|
246
|
+
await cache.delValue(key);
|
|
247
|
+
await cache.quit();
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
158
251
|
describe('metrics', () => {
|
|
159
252
|
let sinonClock: sinon.SinonFakeTimers;
|
|
160
253
|
|