diff --git a/src/adapter/basic.ts b/src/adapter/basic.ts index 2cfe01bd..ca41356c 100644 --- a/src/adapter/basic.ts +++ b/src/adapter/basic.ts @@ -123,7 +123,11 @@ export class Adapter< this.dependencies = this.initializeDependencies(dependencies) - if (this.config.settings.EA_MODE !== 'reader' && this.dependencies.cache.lock) { + if ( + this.config.settings.EA_MODE !== 'reader' && + this.config.settings.CACHE_LOCK_ENABLED && + this.dependencies.cache.lock + ) { const [lockAcquiredPromise, lockAcquiredResolve, lockAcquiredReject] = deferredPromise() this.lockAcquiredPromise = lockAcquiredPromise diff --git a/src/config/index.ts b/src/config/index.ts index 17a93d92..0d868256 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -21,6 +21,12 @@ export const BaseSettingsDefinition = { default: '/', sensitive: false, }, + CACHE_LOCK_ENABLED: { + description: + 'Flag to enable or disable the cache lock mechanism. When disabled, multiple adapter instances can write to the same cache keys concurrently.', + type: 'boolean', + default: true, + }, CACHE_LOCK_DURATION: { description: 'Time (in ms) used as a baseline for the acquisition and extension of cache locks', type: 'number', diff --git a/test/cache/cache-lock.test.ts b/test/cache/cache-lock.test.ts index eeb0cbb5..f292e48f 100644 --- a/test/cache/cache-lock.test.ts +++ b/test/cache/cache-lock.test.ts @@ -10,6 +10,75 @@ import { test } from './helper' // This test needs to use expose to wait for the cache lock promise in the basic EA flow process.env['EA_PORT'] = '0' +test.serial( + 'An adapter with CACHE_LOCK_ENABLED=false should skip lock acquisition even with duplicate names', + async (t) => { + const config = new AdapterConfig( + {}, + { + envDefaultOverrides: { + CACHE_TYPE: 'redis', + CACHE_REDIS_PORT: 6000, + CACHE_LOCK_ENABLED: false, + CACHE_LOCK_DEFERRAL_MS: 0, + }, + }, + ) + const adapter = new Adapter({ + name: 'TEST', + defaultEndpoint: 'test', + config, + endpoints: [ + new AdapterEndpoint({ + name: 'nowork', + transport: new NopTransport(), + }), + ], + }) + + const config2 = new AdapterConfig( + {}, + { + envDefaultOverrides: { + CACHE_TYPE: 'redis', + CACHE_REDIS_PORT: 6000, + CACHE_LOCK_ENABLED: false, + CACHE_LOCK_DEFERRAL_MS: 0, + }, + }, + ) + const adapter2 = new Adapter({ + name: 'TEST', + defaultEndpoint: 'test', + config: config2, + endpoints: [ + new AdapterEndpoint({ + name: 'nowork', + transport: new NopTransport(), + }), + ], + }) + + const redisClient = new RedisMock() as unknown as Redis + const cache = new RedisCache(redisClient, 10000) + const dependencies: Partial = { + cache, + redisClient, + } + + try { + await expose(adapter, dependencies) + await expose(adapter2, dependencies) + + t.is(adapter.lockAcquiredPromise, undefined) + t.is(adapter2.lockAcquiredPromise, undefined) + t.pass() + } catch (error) { + t.fail(`No error should have been thrown with CACHE_LOCK_ENABLED=false: ${error}`) + } + }, +) + test.serial( 'An adapter with a duplicate name and no cache prefix should fail to acquire a lock', async (t) => { @@ -19,6 +88,7 @@ test.serial( envDefaultOverrides: { CACHE_TYPE: 'redis', CACHE_REDIS_PORT: 6000, + CACHE_PREFIX: 'PREFIX', CACHE_LOCK_DURATION: 2000, CACHE_LOCK_RETRIES: 2, CACHE_LOCK_DEFERRAL_MS: 0, @@ -43,6 +113,7 @@ test.serial( envDefaultOverrides: { CACHE_TYPE: 'redis', CACHE_REDIS_PORT: 6000, + CACHE_PREFIX: 'PREFIX', CACHE_LOCK_DURATION: 2000, CACHE_LOCK_RETRIES: 2, CACHE_LOCK_DEFERRAL_MS: 0,