Make cache readTimeout configurable.

Signed-off-by: Eric Peterson <ericpeterson@spotify.com>
This commit is contained in:
Eric Peterson
2021-11-12 15:01:37 +01:00
committed by Eric Peterson
parent da0867f952
commit ef04b09f75
5 changed files with 65 additions and 5 deletions
+25 -2
View File
@@ -15,6 +15,7 @@
*/
import { CacheClient, getVoidLogger } from '@backstage/backend-common';
import { ConfigReader } from '@backstage/config';
import { CacheInvalidationError, TechDocsCache } from './TechDocsCache';
const cached = (str: string): string => {
@@ -31,7 +32,7 @@ describe('TechDocsCache', () => {
set: jest.fn(),
delete: jest.fn(),
};
CacheUnderTest = new TechDocsCache({
CacheUnderTest = TechDocsCache.fromConfig(new ConfigReader({}), {
cache: MockClient,
logger: getVoidLogger(),
});
@@ -55,7 +56,7 @@ describe('TechDocsCache', () => {
expect(actual).toBe(undefined);
});
it('returns undefined if no response after 1s', async () => {
it('returns undefined if no response after 1s by default', async () => {
const expectedPath = 'some/index.html';
MockClient.get.mockImplementationOnce(() => {
return new Promise(resolve => {
@@ -66,6 +67,28 @@ describe('TechDocsCache', () => {
expect(actual).toBe(undefined);
});
it('returns undefined if no response after configured readTimeout', async () => {
const expectedPath = 'some/index.html';
MockClient.get.mockImplementationOnce(() => {
return new Promise(resolve => {
setTimeout(() => resolve(cached('value')), 20);
});
});
CacheUnderTest = TechDocsCache.fromConfig(
new ConfigReader({
techdocs: { cache: { readTimeout: 10 } },
}),
{
cache: MockClient,
logger: getVoidLogger(),
},
);
const actual = await CacheUnderTest.get(expectedPath);
expect(actual).toBe(undefined);
});
it('returns data if cache get returns it', async () => {
const expectedPath = 'some/index.html';
MockClient.get.mockResolvedValueOnce(cached('expected value'));
+24 -2
View File
@@ -14,6 +14,8 @@
* limitations under the License.
*/
import { CacheClient } from '@backstage/backend-common';
import { assertError } from '@backstage/errors';
import { Config } from '@backstage/config';
import { Logger } from 'winston';
export class CacheInvalidationError extends Error {
@@ -28,10 +30,29 @@ export class CacheInvalidationError extends Error {
export class TechDocsCache {
protected readonly cache: CacheClient;
protected readonly logger: Logger;
protected readonly readTimeout: number;
constructor({ cache, logger }: { cache: CacheClient; logger: Logger }) {
private constructor({
cache,
logger,
readTimeout,
}: {
cache: CacheClient;
logger: Logger;
readTimeout: number;
}) {
this.cache = cache;
this.logger = logger;
this.readTimeout = readTimeout;
}
static fromConfig(
config: Config,
{ cache, logger }: { cache: CacheClient; logger: Logger },
) {
const readTimeout =
config.getOptionalNumber('techdocs.cache.readTimeout') || 1000;
return new TechDocsCache({ cache, logger, readTimeout });
}
async get(path: string): Promise<Buffer | undefined> {
@@ -40,7 +61,7 @@ export class TechDocsCache {
// temporarily unreachable.
const response = (await Promise.race([
this.cache.get(path),
new Promise(cancelAfter => setTimeout(cancelAfter, 1000)),
new Promise(cancelAfter => setTimeout(cancelAfter, this.readTimeout)),
])) as string | undefined;
if (response !== undefined) {
@@ -51,6 +72,7 @@ export class TechDocsCache {
this.logger.debug(`Cache miss: ${path}`);
return response;
} catch (e) {
assertError(e);
this.logger.warn(`Error getting cache entry ${path}: ${e.message}`);
this.logger.debug(e.stack);
return undefined;
@@ -91,7 +91,7 @@ export async function createRouter(
const defaultTtl = config.getOptionalNumber('techdocs.cache.ttl');
if (isOutOfTheBoxOption(options) && options.cache && defaultTtl) {
const cacheClient = options.cache.getClient({ defaultTtl });
cache = new TechDocsCache({ cache: cacheClient, logger });
cache = TechDocsCache.fromConfig(config, { cache: cacheClient, logger });
}
const scmIntegrations = ScmIntegrations.fromConfig(config);