import { createWritable } from './files.js' import { logger } from './logger.js' import * as redis from './redis.js' import { getRedisKey } from './util.js' import { Gauge } from 'prom-client' const cache = {} export const fileCacheSizeGauge = new Gauge({ name: 'file_cache_size', help: 'Number of entries in file cache' }) export function set (key, value) { logger.debug(`[Cache] Set ${key}`) if (cache[key] === value) return cache[key] = value if (redis.isEnabled()) { return redis.client.set(key, value) } } export async function clear () { for (const prop of Object.getOwnPropertyNames(cache)) { delete cache[prop] } fileCacheSizeGauge.reset() } export function get (key, fallback) { if (cache[key]) { logger.debug(`[Cache] Resolve from memory: ${key}`) return cache[key] } const promise = (async () => { if (redis.isEnabled()) { let result = await redis.client.get(key) if (result) { logger.debug(`[Cache] Resolve from redis: ${key}`) result = JSON.parse(result) cache[key] = result return result } } if (!fallback) return const fallbackResult = await fallback() if (fallbackResult) { logger.debug(`[Cache] Found a getter for: ${key}`) cache[key] = fallbackResult if (redis.isEnabled()) redis.client.set(key, JSON.stringify(fallbackResult)) } return fallbackResult })() cache[key] = promise return promise } export function getFile ({ name, version }, fallback) { const key = getRedisKey({ version, name }) // try to get the file synchronously. const data = cache[key] if (data) { logger.debug(`[Cache] Resolve file from memory: ${key}`) return data } // if synchronously does not work, store the async promise for further requests const promise = (async () => { const bodyKey = getRedisKey({ version, name: `${name}:body` }) const metaKey = getRedisKey({ version, name: `${name}:meta` }) if (redis.isEnabled()) { const [body, meta = '{}'] = await Promise.all([ redis.client.getBuffer(bodyKey), redis.client.get(metaKey) ]) if (body) { logger.debug(`[Cache] Resolve file from redis: ${key}`) fileCacheSizeGauge.inc() return (cache[key] = { body, ...JSON.parse(meta) }) } } const dataFromServer = await fallback({ version, name }).catch(err => { if (err.status !== 404) delete cache[key] throw err }) if (redis.isEnabled()) { logger.debug(`[Cache] Store file in redis: ${key}`) const { body, ...rest } = dataFromServer redis.client.set(bodyKey, createWritable(body)) redis.client.set(metaKey, JSON.stringify(rest)) } // overwrite cache with synchronous data logger.debug(`[Cache] Store file in memory: ${key}`) fileCacheSizeGauge.inc() return (cache[key] = dataFromServer) })() // temporary set to promise cache[key] = promise return promise }