@eclipse-emfcloud/model-manager
Version:
Command-based model editing with undo/redo.
97 lines • 4.13 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExclusiveExecutor = void 0;
const model_logger_1 = require("@eclipse-emfcloud/model-logger");
const logger = (0, model_logger_1.getLogger)('model-manager/promise-util');
/**
* Utility for execution of async functions that must be given mutually
* exclusive access to some resource. Execution is chained: each async
* call that is passed in for execution is deferred until the previous
* has completed, whether normally or with an error.
*/
class ExclusiveExecutor {
constructor() {
/**
* Awaitable results of the current pending exclusive operations.
* Initially there's nothing pending.
*/
this.locks = [];
}
/**
* Exclusively run the given asynchronous `operation`, returning its
* eventual result. Operations can be executed in parallel only if they
* affect different models and editing contexts. If modelIds is not
* specified, the operation is assumed to potentially affect any model,
* and will not run in parallel with any other operation.
*
* @param operation an asynchronous operation to run exclusively
* @param contexts the editing contexts affected by the operation
* @param modelIds the identifier of models modified by the operation
* @returns the eventual result of the `operation`
*/
run(operation, contexts, modelIds) {
const dependentLock = this.getDependentLock(contexts, modelIds);
const result = dependentLock.pendingExclusiveOperation.then(() => operation());
// Don't propagate any error to the next caller in the chain
const lockOperation = new Promise((resolve) => {
result
.catch((error) => {
if (error instanceof Error) {
logger.debug('Error occurred during command execution:', error.message);
}
else {
logger.debug('Error occurred during command execution.', error);
}
})
.finally(resolve);
});
const lock = {
modelIds,
contexts,
pendingExclusiveOperation: lockOperation,
};
this.locks.push(lock);
// Remove the lock after execution
lock.pendingExclusiveOperation.finally(() => {
const lockIndex = this.locks.indexOf(lock);
this.locks.splice(lockIndex, 1);
});
return result;
}
getDependentLock(contexts, modelIds) {
const waitForLocks = [];
if (modelIds === undefined) {
// If the model ids are missing, lock all models. This is typically
// the case for undo/redo operations, which we shouldn't be running
// in parallel.
waitForLocks.push(...this.locks.map((lock) => lock.pendingExclusiveOperation));
}
else {
for (const lock of this.locks) {
// If the editing contexts overlap, we need to wait for the previous operation.
if (lock.contexts.some((context) => contexts.includes(context))) {
waitForLocks.push(lock.pendingExclusiveOperation);
}
else {
if (lock.modelIds === undefined) {
// Wait for all operations that lock all models.
waitForLocks.push(lock.pendingExclusiveOperation);
}
else {
// If the model ids are present, only wait for overlapping locks.
if (lock.modelIds.some((modelId) => modelIds.includes(modelId))) {
waitForLocks.push(lock.pendingExclusiveOperation);
}
}
}
}
}
return {
modelIds,
contexts,
pendingExclusiveOperation: Promise.allSettled(waitForLocks),
};
}
}
exports.ExclusiveExecutor = ExclusiveExecutor;
//# sourceMappingURL=promise-util.js.map