-
-
Notifications
You must be signed in to change notification settings - Fork 31
Description
I've encountered an issue where calling deleteByTag does not properly invalidate cached values when a grace period is configured. After invalidation, the cache still returns the stale ("old") value for getOrSet.
Initially, I suspected this behavior was limited to distributed setups using a bus, but it also occurs with a single cache instance. This suggests the issue is not related to multi-node synchronization but possibly to how deleteByTag interacts with the grace logic.
For reproduction, I quickly wrote two test cases in tagging.spec.ts on my local machine:
test('remove by tags with grace period', async ({ assert }) => {
const [cache] = new CacheFactory().withL1L2Config().create()
const v1 = await cache.getOrSet({
key: 'baz',
factory: () => 1,
tags: ['x', 'z'],
ttl: '2min',
grace: '10min',
})
assert.deepEqual(v1, 1)
await cache.deleteByTag({ tags: ['x'] })
const v2get = await cache.get({ key: 'baz' })
const v2getorset = await cache.getOrSet({
key: 'baz',
factory: () => 2,
tags: ['x', 'z'],
ttl: '2min',
grace: '10min',
})
assert.isUndefined(v2get)
assert.deepEqual(v2getorset, 2)
})
test('remove by tags with bus and grace period', async ({ assert }) => {
const [readCache] = new CacheFactory().withL1L2Config().create()
const [invalidationCache] = new CacheFactory().withL1L2Config().create()
const v1 = await readCache.getOrSet({
key: 'baz',
factory: () => 1,
tags: ['x', 'z'],
ttl: '2min',
grace: '10min',
})
assert.deepEqual(v1, 1)
await invalidationCache.deleteByTag({ tags: ['x'] })
//await invalidationCache.delete({ key: 'baz' }) <- this works btw.
const v2get = await readCache.get({ key: 'baz' })
const v2getorset = await readCache.getOrSet({
key: 'baz',
factory: () => 2,
tags: ['x', 'z'],
ttl: '2min',
grace: '10min',
})
assert.isUndefined(v2get)
assert.deepEqual(v2getorset, 2)
})Result:
> cross-env NODE_NO_WARNINGS=1 node --enable-source-maps --loader=ts-node/esm bin/test.ts "--suite=unit" "--files=tests/tagging.spec.ts"
unit / Tagging | Internals (tests/tagging.spec.ts)
✔ deleteByTag should store invalidated tags with timestamps (7.08ms)
✔ set should store value with tags (2.1ms)
unit / Tagging | deleteByTag (tests/tagging.spec.ts)
✔ basic (2.94ms)
✔ can remove by tag (6.51ms)
✔ can remove multiple tags (3.6ms)
✔ remove by tags with bus (6.01ms)
✖ remove by tags with grace period (2.74ms)
✖ remove by tags with bus and grace period (2.99ms)
✔ remove multiple tags with bus (8.59ms)
✔ tags and namespaces (3.56ms)
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ERRORS ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
❯ Tagging | deleteByTag / remove by tags with grace period
- Expected - 1
+ Received + 1
- 2
+ 1
ℹ AssertionError: expected 1 to deeply equal 2
⁃ at Assert.deepEqual (/Users/yss/Dev/open-source/bentocache/node_modules/.pnpm/@japa+assert@4.0.1_@japa+runner@4.2.0/node_modules/@japa/assert/build/index.js:282:19)
⁃ at Object.executor (tests/tagging.spec.ts:192:12)
187 ┃ ttl: '2min',
188 ┃ grace: '10min',
189 ┃ })
190 ┃
191 ┃ assert.isUndefined(v2get)
❯ 192 ┃ assert.deepEqual(v2getorset, 2)
193 ┃ })
194 ┃
195 ┃ test('remove by tags with bus and grace period', async ({ assert }) => {
196 ┃ const [readCache] = new CacheFactory().withL1L2Config().create()
197 ┃ const [invalidationCache] = new CacheFactory().withL1L2Config().create()
⁃ at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
⁃ at async #wrapTestInTimeout (/Users/yss/Dev/open-source/bentocache/node_modules/.pnpm/@japa+core@10.3.0/node_modules/@japa/core/build/index.js:1054:7)
⁃ at async TestRunner.run (/Users/yss/Dev/open-source/bentocache/node_modules/.pnpm/@japa+core@10.3.0/node_modules/@japa/core/build/index.js:1102:7)
⁃ at async Test.exec (/Users/yss/Dev/open-source/bentocache/node_modules/.pnpm/@japa+core@10.3.0/node_modules/@japa/core/build/index.js:1482:5)
⁃ at async GroupRunner.run (/Users/yss/Dev/open-source/bentocache/node_modules/.pnpm/@japa+core@10.3.0/node_modules/@japa/core/build/index.js:345:7)
⁃ at async Group.exec (/Users/yss/Dev/open-source/bentocache/node_modules/.pnpm/@japa+core@10.3.0/node_modules/@japa/core/build/index.js:513:5)
⁃ at async SuiteRunner.run (/Users/yss/Dev/open-source/bentocache/node_modules/.pnpm/@japa+core@10.3.0/node_modules/@japa/core/build/index.js:1809:7)
⁃ at async Suite.exec (/Users/yss/Dev/open-source/bentocache/node_modules/.pnpm/@japa+core@10.3.0/node_modules/@japa/core/build/index.js:1936:5)
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[1/2]─
❯ Tagging | deleteByTag / remove by tags with bus and grace period
- Expected - 1
+ Received + 1
- 2
+ 1
ℹ AssertionError: expected 1 to deeply equal 2
⁃ at Assert.deepEqual (/Users/yss/Dev/open-source/bentocache/node_modules/.pnpm/@japa+assert@4.0.1_@japa+runner@4.2.0/node_modules/@japa/assert/build/index.js:282:19)
⁃ at Object.executor (tests/tagging.spec.ts:222:12)
217 ┃ ttl: '2min',
218 ┃ grace: '10min',
219 ┃ })
220 ┃
221 ┃ assert.isUndefined(v2get)
❯ 222 ┃ assert.deepEqual(v2getorset, 2)
223 ┃ })
At this point, I'm not sure whether this is intended behavior or a bug. However, I would lean toward it being a bug—especially since the interaction between deleteByTag and the grace period doesn’t appear to be covered by the existing test suite.