UNPKG

@itwin/core-backend

Version:
180 lines • 9.48 kB
"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); exports.ServerBasedLocks = void 0; exports.createServerBasedLocks = createServerBasedLocks; /** @packageDocumentation * @module iModels */ const core_bentley_1 = require("@itwin/core-bentley"); const core_common_1 = require("@itwin/core-common"); const IModelHost_1 = require("../IModelHost"); const SQLiteDb_1 = require("../SQLiteDb"); const Symbols_1 = require("./Symbols"); class ServerBasedLocks { [Symbols_1._implementationProhibited] = undefined; get isServerBased() { return true; } lockDb = new SQLiteDb_1.SQLiteDb(); briefcase; constructor(iModel) { this.briefcase = iModel; const dbName = `${iModel[Symbols_1._nativeDb].getTempFileBaseName()}-locks`; try { this.lockDb.openDb(dbName, core_bentley_1.OpenMode.ReadWrite); } catch { this.lockDb.createDb(dbName); this.lockDb.executeSQL("CREATE TABLE locks(id INTEGER PRIMARY KEY NOT NULL,state INTEGER NOT NULL,origin INTEGER)"); this.lockDb.saveChanges(); } } [Symbols_1._close]() { if (this.lockDb.isOpen) this.lockDb.closeDb(); } getOwners(id) { return this.briefcase.withPreparedSqliteStatement("SELECT ModelId,ParentId FROM bis_Element WHERE id=?", (stmt) => { stmt.bindId(1, id); const rc = stmt.step(); if (core_bentley_1.DbResult.BE_SQLITE_ROW !== rc) throw new core_common_1.IModelError(rc, `element ${id} not found`); return { modelId: stmt.getValueId(0), parentId: stmt.getValueId(1) }; }); } getLockState(id) { return (id === undefined || !core_bentley_1.Id64.isValid(id)) ? undefined : this.lockDb.withPreparedSqliteStatement("SELECT state FROM locks WHERE id=?", (stmt) => { stmt.bindId(1, id); return (core_bentley_1.DbResult.BE_SQLITE_ROW === stmt.step()) ? stmt.getValueInteger(0) : undefined; }); } /** Clear the cache of locally held locks. * Note: does *not* release locks from server. */ clearAllLocks() { this.lockDb.executeSQL("DELETE FROM locks"); this.lockDb.saveChanges(); } /** only for tests */ getLockCount(state) { return this.lockDb.withSqliteStatement("SELECT count(*) FROM locks WHERE state=?", (stmt) => { stmt.bindInteger(1, state); stmt.step(); return stmt.getValueInteger(0); }); } async [Symbols_1._releaseAllLocks]() { await IModelHost_1.IModelHost[Symbols_1._hubAccess].releaseAllLocks(this.briefcase); // throws if unsuccessful this.clearAllLocks(); } async releaseAllLocks() { if (this.briefcase.txns.hasLocalChanges) { throw new Error("Locks cannot be released while the briefcase contains local changes"); } return this[Symbols_1._releaseAllLocks](); } insertLock(id, state, origin) { this.lockDb.withPreparedSqliteStatement("INSERT INTO locks(id,state,origin) VALUES (?,?,?) ON CONFLICT(id) DO UPDATE SET state=excluded.state,origin=excluded.origin", (stmt) => { stmt.bindId(1, id); stmt.bindInteger(2, state); stmt.bindInteger(3, origin); const rc = stmt.step(); if (core_bentley_1.DbResult.BE_SQLITE_DONE !== rc) throw new core_common_1.IModelError(rc, "can't insert lock into database"); }); return true; } ownerHoldsExclusiveLock(id) { if (id === undefined || id === core_common_1.IModel.rootSubjectId) return false; // has no owners const { modelId, parentId } = this.getOwners(id); if (this.getLockState(modelId) === core_common_1.LockState.Exclusive || this.getLockState(parentId) === core_common_1.LockState.Exclusive) return true; // see if this model is exclusively locked by one of its owners. If so, save that fact on modelId so future tests won't have to descend. if (this.ownerHoldsExclusiveLock(modelId)) return this.insertLock(modelId, core_common_1.LockState.Exclusive, 2 /* LockOrigin.Discovered */); // see if the parent is exclusively locked by one of its owners. If so, save that fact on parentId so future tests won't have to descend. return this.ownerHoldsExclusiveLock(parentId) ? this.insertLock(parentId, core_common_1.LockState.Exclusive, 2 /* LockOrigin.Discovered */) : false; // eslint-disable-line @typescript-eslint/no-non-null-assertion } /** Determine whether an the exclusive lock is already held by an element (or one of its owners) */ holdsExclusiveLock(id) { // see if we hold the exclusive lock. or if one of the element's owners is exclusively locked (recursively) return this.getLockState(id) === core_common_1.LockState.Exclusive || this.ownerHoldsExclusiveLock(id); } holdsSharedLock(id) { const state = this.getLockState(id); // see if we hold shared or exclusive lock, or if an owner has exclusive lock. If so we implicitly have shared lock, but owner holding shared lock doesn't help. return (state === core_common_1.LockState.Shared || state === core_common_1.LockState.Exclusive) || this.ownerHoldsExclusiveLock(id); } /** if the shared lock on the element supplied is not already held, add it to the set of shared locks required. Then, check owners. */ addSharedLock(id, locks) { // if the id is not valid, or of the lock is already in the set, or if we already hold a shared lock, we're done // Note: if we hold a shared lock, it is guaranteed that we also hold all required shared locks on owners. if (id === undefined || !core_bentley_1.Id64.isValid(id) || locks.has(id) || this.holdsSharedLock(id)) return; locks.add(id); // add to set of needed shared locks this.addOwnerSharedLocks(id, locks); // check parent models and groups } /** add owners (recursively) of an element to a list of required shared locks, if not already held. */ addOwnerSharedLocks(id, locks) { const el = this.getOwners(id); this.addSharedLock(el.parentId, locks); // if this element is in a group this.addSharedLock(el.modelId, locks); // check its model } /** attempt to acquire all necessary locks for a set of elements */ async acquireAllLocks(locks) { if (locks.size === 0) // no locks are required. return; const sharedLocks = new Set(); for (const lock of locks) this.addOwnerSharedLocks(lock[0], sharedLocks); for (const shared of sharedLocks) { if (!locks.has(shared)) // we may already be asking for exclusive lock locks.set(shared, core_common_1.LockState.Shared); } await IModelHost_1.IModelHost[Symbols_1._hubAccess].acquireLocks(this.briefcase, locks); // throws if unsuccessful for (const lock of locks) this.insertLock(lock[0], lock[1], 0 /* LockOrigin.Acquired */); this.lockDb.saveChanges(); } async acquireLocks(arg) { const locks = new Map(); if (arg.shared) { for (const id of core_bentley_1.Id64.iterable(arg.shared)) { if (!this.holdsSharedLock(id)) locks.set(id, core_common_1.LockState.Shared); } } if (arg.exclusive) { for (const id of core_bentley_1.Id64.iterable(arg.exclusive)) { if (!this.holdsExclusiveLock(id)) locks.set(id, core_common_1.LockState.Exclusive); } } return this.acquireAllLocks(locks); } /** When an element is newly created in a session, we hold the lock on it implicitly. Save that fact. */ [Symbols_1._elementWasCreated](id) { this.insertLock(id, core_common_1.LockState.Exclusive, 1 /* LockOrigin.NewElement */); this.lockDb.saveChanges(); } /** locks are not necessary during change propagation. */ get _locksAreRequired() { return !this.briefcase.txns.isIndirectChanges; } /** throw if locks are currently required and the exclusive lock is not held on the supplied element */ checkExclusiveLock(id, type, operation) { if (this._locksAreRequired && !this.holdsExclusiveLock(id)) throw new core_common_1.IModelError(core_bentley_1.IModelStatus.LockNotHeld, `exclusive lock not held on ${type} for ${operation} (id=${id})`); } /** throw if locks are currently required and a shared lock is not held on the supplied element */ checkSharedLock(id, type, operation) { if (this._locksAreRequired && !this.holdsSharedLock(id)) throw new core_common_1.IModelError(core_bentley_1.IModelStatus.LockNotHeld, `shared lock not held on ${type} for ${operation} (id=${id})`); } } exports.ServerBasedLocks = ServerBasedLocks; function createServerBasedLocks(iModel) { return new ServerBasedLocks(iModel); } //# sourceMappingURL=ServerBasedLocks.js.map