UNPKG

@solid/community-server

Version:

Community Solid Server: an open and modular implementation of the Solid specifications

125 lines 6.12 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.LockingResourceStore = void 0; const global_logger_factory_1 = require("global-logger-factory"); const BasicRepresentation_1 = require("../http/representation/BasicRepresentation"); const StreamUtil_1 = require("../util/StreamUtil"); /** * Store that for every call acquires a lock before executing it on the requested resource, * and releases it afterwards. * In case the request returns a Representation the lock will only be released when the data stream is finished. * * For auxiliary resources the lock will be applied to the subject resource. * The actual operation is still executed on the auxiliary resource. */ class LockingResourceStore { logger = (0, global_logger_factory_1.getLoggerFor)(this); source; locks; auxiliaryStrategy; constructor(source, locks, auxiliaryStrategy) { this.source = source; this.locks = locks; this.auxiliaryStrategy = auxiliaryStrategy; } async hasResource(identifier) { return this.locks.withReadLock(this.getLockIdentifier(identifier), async () => this.source.hasResource(identifier)); } async getRepresentation(identifier, preferences, conditions) { return this.lockedRepresentationRun(this.getLockIdentifier(identifier), async () => this.source.getRepresentation(identifier, preferences, conditions)); } async addResource(container, representation, conditions) { return this.locks.withWriteLock(this.getLockIdentifier(container), async () => this.source.addResource(container, representation, conditions)); } async setRepresentation(identifier, representation, conditions) { return this.locks.withWriteLock(this.getLockIdentifier(identifier), async () => this.source.setRepresentation(identifier, representation, conditions)); } async deleteResource(identifier, conditions) { return this.locks.withWriteLock(this.getLockIdentifier(identifier), async () => this.source.deleteResource(identifier, conditions)); } async modifyResource(identifier, patch, conditions) { return this.locks.withWriteLock(this.getLockIdentifier(identifier), async () => this.source.modifyResource(identifier, patch, conditions)); } /** * Acquires the correct identifier to lock this resource. * For auxiliary resources this means the subject identifier. */ getLockIdentifier(identifier) { return this.auxiliaryStrategy.isAuxiliaryIdentifier(identifier) ? this.auxiliaryStrategy.getSubjectIdentifier(identifier) : identifier; } /** * Acquires a lock that is only released when all data of the resulting representation data has been read, * an error occurs, or the timeout has been triggered. * The resulting data stream will be adapted to reset the timer every time data is read. * * In case the data of the resulting stream is not needed it should be closed to prevent a timeout error. * * @param identifier - Identifier that should be locked. * @param whileLocked - Function to be executed while the resource is locked. */ async lockedRepresentationRun(identifier, whileLocked) { // Create a new Promise that resolves to the resulting Representation // while only unlocking when the data has been read (or there's a timeout). // Note that we can't just return the result of `withReadLock` since that promise only // resolves when the stream is finished, while we want `lockedRepresentationRun` to resolve // once we have the Representation. // See https://github.com/CommunitySolidServer/CommunitySolidServer/pull/536#discussion_r562467957 return new Promise((resolve, reject) => { let representation; // Make the resource time out to ensure that the lock is always released eventually. this.locks.withReadLock(identifier, async (maintainLock) => { representation = await whileLocked(); resolve(this.createExpiringRepresentation(representation, maintainLock)); // Release the lock when an error occurs or the data finished streaming await this.waitForStreamToEnd(representation.data); }).catch((error) => { // Destroy the source stream in case the lock times out representation?.data.destroy(error); // Let this function return an error in case something went wrong getting the data // or in case the timeout happens before `func` returned reject(error); }); }); } /** * Wraps a representation to make it reset the timeout timer every time data is read. * * @param representation - The representation to wrap * @param maintainLock - Function to call to reset the timer. */ createExpiringRepresentation(representation, maintainLock) { const source = representation.data; // Spy on the source to maintain the lock upon reading. const data = Object.create(source, { read: { value(size) { maintainLock(); return source.read(size); }, }, }); return new BasicRepresentation_1.BasicRepresentation(data, representation.metadata, representation.binary); } /** * Returns a promise that resolve when the source stream is finished, * either by ending or emitting an error. * In the case of an error the stream will be destroyed if it hasn't been already. * * @param source - The input stream. */ async waitForStreamToEnd(source) { try { await (0, StreamUtil_1.endOfStream)(source); } catch { // Destroy the stream in case of errors if (!source.destroyed) { source.destroy(); } } } } exports.LockingResourceStore = LockingResourceStore; //# sourceMappingURL=LockingResourceStore.js.map