From c284b1d4d529811ea9fb0e9f333e3f3ddf08d345 Mon Sep 17 00:00:00 2001 From: Derrick Mehaffy Date: Mon, 22 Jul 2024 10:09:28 -0700 Subject: [PATCH] migrate redcron/redlock code from v4 plugin --- package.json | 6 +++- server/config/index.js | 9 ++++++ server/register.js | 72 +++++++++++++++++++++++++++++++++++------- yarn.lock | 12 +++++++ 4 files changed, 86 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index db86ddb..35dceb7 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "dependencies": { "chalk": "4.1.2", "debug": "4.3.5", - "ioredis": "5.4.1" + "ioredis": "5.4.1", + "redlock": "5.0.0-beta.2" }, "peerDependencies": { "@strapi/strapi": "^5.0.0-rc.4" @@ -33,6 +34,9 @@ "email": "derrickmehaffy@gmail.com", "url": "https://github.com/derrickmehaffy", "lead": true + }, + { + "name": "Excl Networks Inc." } ], "bugs": { diff --git a/server/config/index.js b/server/config/index.js index 071e218..c76932c 100644 --- a/server/config/index.js +++ b/server/config/index.js @@ -5,6 +5,15 @@ module.exports = { settings: { debug: false, debugIORedis: false, + redlockConfig: { + driftFactor: 0.01, + retryCount: 10, + retryDelay: 200, + retryJitter: 200, + }, + enableRedlock: false, + lockDelay: null, + lockTTL: 5000, }, connections: { default: { diff --git a/server/register.js b/server/register.js index ed3b633..34c0a03 100644 --- a/server/register.js +++ b/server/register.js @@ -1,6 +1,7 @@ 'use strict'; const debug = require('debug'); +const { default: Redlock } = require('redlock'); module.exports = async ({ strapi }) => { // Load plugin Config @@ -25,16 +26,63 @@ module.exports = async ({ strapi }) => { // Build Redis database connections await strapi.plugin('redis').service('connection').buildAll(coreConfig); - // Construct Admin Permissions - // Commenting this since it's not needed for now, there is no admin panel for this plugin - // const actions = [ - // { - // section: 'settings', - // category: 'redis', - // displayName: 'Access the Redis Overview page', - // uid: 'settings.read', - // pluginName: 'redis', - // }, - // ]; - // await strapi.admin.services.permission.actionProvider.registerMany(actions); + // Configure Redlock + if (coreConfig.settings.enableRedlock === true) { + const originalAdd = strapi.cron.add; + const redlockConfig = coreConfig.settings.redlockConfig; + + strapi.cron.add = (tasks) => { + const generateRedlockFunction = (originalFunction, name) => { + return async (...args) => { + const connections = Object.keys(strapi.redis.connections).map((key) => { + return strapi.redis.connections[key].client; + }); + const redlock = new Redlock(connections, redlockConfig); + + let lock; + try { + lock = await redlock.acquire([name], coreConfig.settings.lockTTL); + debug(`Job ${name} acquired lock`); + await originalFunction(...args); + } catch (e) { + debug(`Job ${name} failed to acquire lock`); + } finally { + // wait some time so other processes will lose the lock + let lockDelay = coreConfig.settings.lockDelay + ? coreConfig.settings.lockDelay + : coreConfig.settings.redlockConfig.retryCount * + (coreConfig.settings.redlockConfig.retryDelay + + coreConfig.settings.redlockConfig.retryJitter); + debug(`Job ${name} waiting ${lockDelay}ms before releasing lock`); + await new Promise((resolve) => setTimeout(resolve, lockDelay)); + if (lock) { + debug(`Job ${name} releasing lock`); + try { + await lock.release(); + } catch (e) { + debug(`Job ${name} failed to release lock ${e}`); + } + } + } + }; + }; + Object.keys(tasks).forEach((key) => { + const taskValue = tasks[key]; + if (typeof taskValue === 'function') { + strapi.log.info('redlock requires tasks to use the object format'); + return; + } else if ( + typeof taskValue === 'object' && + taskValue && + typeof taskValue.task === 'function' && + taskValue.bypassRedlock !== true + ) { + // fallback to key if no name is provided + const taskName = taskValue.name || key; + taskValue.task = generateRedlockFunction(taskValue.task, 'redlock:' + taskName); + } + }); + originalAdd(tasks); + }; + } }; diff --git a/yarn.lock b/yarn.lock index c9bda95..a994484 100644 --- a/yarn.lock +++ b/yarn.lock @@ -93,6 +93,11 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +node-abort-controller@^3.0.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-3.1.1.tgz#a94377e964a9a37ac3976d848cb5c765833b8548" + integrity sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ== + redis-errors@^1.0.0, redis-errors@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad" @@ -105,6 +110,13 @@ redis-parser@^3.0.0: dependencies: redis-errors "^1.0.0" +redlock@5.0.0-beta.2: + version "5.0.0-beta.2" + resolved "https://registry.yarnpkg.com/redlock/-/redlock-5.0.0-beta.2.tgz#a629c07e07d001c0fdd9f2efa614144c4416fe44" + integrity sha512-2RDWXg5jgRptDrB1w9O/JgSZC0j7y4SlaXnor93H/UJm/QyDiFgBKNtrh0TI6oCXqYSaSoXxFh6Sd3VtYfhRXw== + dependencies: + node-abort-controller "^3.0.1" + standard-as-callback@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45"