@solid/community-server
Version:
Community Solid Server: an open and modular implementation of the Solid specifications
125 lines • 6.12 kB
JavaScript
"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