diff --git a/lib/cache-provider.js b/lib/cache-provider.js new file mode 100644 index 000000000..675b736f5 --- /dev/null +++ b/lib/cache-provider.js @@ -0,0 +1,12 @@ +const NodeCache = require('node-cache') +let cache = null + +exports.start = function (done) { + if (cache) return done() + + cache = new NodeCache() +} + +exports.instance = function () { + return cache +} diff --git a/lib/create-app.js b/lib/create-app.js index a9b9b7b83..d5cb86894 100644 --- a/lib/create-app.js +++ b/lib/create-app.js @@ -26,6 +26,7 @@ const { routeResolvedFile } = require('./utils') const ResourceMapper = require('./resource-mapper') const aclCheck = require('@solid/acl-check') const { version } = require('../package.json') +const cacheProvider = require('./cache-provider') // alain const corsSettings = cors({ methods: [ @@ -60,6 +61,10 @@ function createApp (argv = {}) { const app = express() + cacheProvider.start(function (err) { // alain initCacheProvider + if (err) console.error(err) + }) + initAppLocals(app, argv, ldp) initHeaders(app) initViews(app, configPath) diff --git a/lib/lock.js b/lib/lock.js index 135229aa7..566fe7e14 100644 --- a/lib/lock.js +++ b/lib/lock.js @@ -1,33 +1,72 @@ /* eslint-disable no-async-promise-executor */ -const { lock } = require('proper-lockfile') +const cache = require('./cache-provider') +const { EventEmitter } = require('events') -const staleSeconds = 30 - -// Obtains a lock on the path, and maintains it until the callback finishes +/** + * Obtains a lock on the path, and maintains it until the callback finishes + * @param {*} path + * @param {*} options + * @param {*} callback + */ function withLock (path, options = {}, callback = options) { return new Promise(async (resolve, reject) => { - let releaseLock, result + let result try { - // Obtain the lock - releaseLock = await lock(path, { - retries: 10, - update: 1000, - stale: staleSeconds * 1000, - realpath: !!options.mustExist, - onCompromised: () => - reject(new Error(`The file at ${path} was not updated within ${staleSeconds}s.`)) - }) // Hold on to the lock until the callback's returned promise resolves + lock().acquire(path) // boolean // kept unlimited time result = await callback() } catch (error) { reject(error) // Ensure the lock is always released } finally { - await releaseLock() + // allways releaseLock() + lock().release(path) } resolve(result) }) } +/** + * https://medium.com/trabe/synchronize-cache-updates-in-node-js-with-a-mutex-d5b395457138 + * There are some npm libraries that implement some kind of mutex support (lock, mutex, async-lock) but none of them feel as simple and elegant as Valeri’s code. + * His solution (pasted below with original comments): + * + * Uses a boolean to keep the locked/unlocked state of the mutex. + * Uses promises to wait for the release of the lock. + * Relies on an event emitter to notify the code that is waiting when the lock is released. + */ +const lock = () => { + const ee = new EventEmitter() + ee.setMaxListeners(0) + + return { + acquire: key => + new Promise(resolve => { + if (!cache.instance().get(key)) { + cache.instance().set(key, true) + return resolve() + } + + const tryAcquire = value => { + if (!cache.instance().get(key)) { + cache.instance().set(key, true) + ee.removeListener(key, tryAcquire) + return resolve(value) + } + } + + ee.on(key, tryAcquire) + }), + + // If we pass a value, on release this value + // will be propagated to all the code that's waiting for + // the lock to release + release: (key, value) => { + cache.instance().del(key) + setImmediate(() => ee.emit(key, value)) + } + } +} + module.exports = withLock diff --git a/package-lock.json b/package-lock.json index 6dcc7a919..56db9d97b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3384,6 +3384,11 @@ } } }, + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=" + }, "clone-response": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", @@ -7931,6 +7936,14 @@ "propagate": "^2.0.0" } }, + "node-cache": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz", + "integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==", + "requires": { + "clone": "2.x" + } + }, "node-fetch": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", diff --git a/package.json b/package.json index 307991098..9edbaa60b 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,7 @@ "mime-types": "^2.1.27", "mkdir-recursive": "^0.4.0", "negotiator": "^0.6.2", + "node-cache": "^5.1.2", "node-fetch": "^2.6.0", "node-forge": "^0.9.1", "nodemailer": "^6.4.11",