strapi-pfapi
Version:
a strapi plugin library uses local and redis caches to achieve single digit milliseconds on average api response time.
101 lines (87 loc) • 3.08 kB
JavaScript
'use strict';
const debug = require('debug')('pfapi:expires-watch');
const { get_prefix_key } = require('../utils/redis-keys');
const { on_invalidate, off_invalidate } = require('./redis-invalidate');
/**
* watch EXP key delete and expire events from redis
* push it into refresh priority queue
*/
class ExpiresWatch {
constructor(redis, refresh_queue) {
if (!redis || !refresh_queue) {
throw new Error('missing required redis and/or refresh_queue');
}
this.redis = redis;
this.refresh_queue = refresh_queue;
}
async start() {
this.expiration_client = await this.on_exp_invalidate(async (redis_keys) => {
const keys = [];
for (const redis_key of redis_keys) {
if (!redis_key.startsWith('EXP::')) continue;
if (!await this.is_key_expired(redis_key)) continue;
const {key} = get_prefix_key(redis_key);
if (!key) continue;
debug('key expired', key)
keys.push(key);
}
if (keys.length > 0) {
await this.on_expires(keys);
}
});
}
async is_key_expired(exp_key) {
const client = await this.redis.get_client();
const no_exp_key = 'NO-' + exp_key;
const multi = client.multi();
multi.exists(exp_key);
multi.exists(no_exp_key);
const result = await multi.exec();
if (result.length !== 2) return false;
if (result[0][1] === 1) return false;
if (result[1][1] === 1) return false;
return true;
}
async on_expires(keys) {
await this.refresh_queue.push(...keys);
}
async stop() {
if (!this.expiration_client) return;
await this.turnoff_exp_invalidate();
await this.redis.close(this.expiration_client);
}
// support functions
async on_exp_invalidate(on_event) {
const {subscribe_client, id} = await on_invalidate(this.redis, on_event, {prefix: 'EXP::', noloop: false});
if (subscribe_client) {
this.exp_invalidate_id = id;
return subscribe_client;
}
return null;
}
async turnoff_exp_invalidate() {
if (this.exp_invalidate_id) {
const id = this.exp_invalidate_id;
delete this.exp_invalidate_id;
return await off_invalidate(this.redis, id);
}
return false;
}
async on_data_invalidate(on_event) {
const {subscribe_client, id} = await on_invalidate(this.redis, on_event, {prefix: 'DATA::', noloop: true});
if (subscribe_client) {
this.data_invalidate_id = id;
return subscribe_client;
}
return null;
}
async turnoff_data_invalidate() {
if (this.data_invalidate_id) {
const id = this.data_invalidate_id;
delete this.data_invalidate_id;
return await off_invalidate(this.redis, id);
}
return false;
}
}
module.exports = ExpiresWatch;