diff --git a/common/api/core-backend.api.md b/common/api/core-backend.api.md index 12a2a68cf69e..31d5e430ec7d 100644 --- a/common/api/core-backend.api.md +++ b/common/api/core-backend.api.md @@ -138,6 +138,7 @@ import { LineStyleProps } from '@itwin/core-common'; import { LocalBriefcaseProps } from '@itwin/core-common'; import { LocalDirName } from '@itwin/core-common'; import { LocalFileName } from '@itwin/core-common'; +import { LockState as LockState_2 } from '@itwin/core-common'; import { LogLevel } from '@itwin/core-bentley'; import { LowAndHighXYZ } from '@itwin/core-geometry'; import { MarkRequired } from '@itwin/core-bentley'; @@ -4049,15 +4050,15 @@ export interface LockControl { } // @internal (undocumented) -export type LockMap = Map; +export type LockMap = Map; // @beta export interface LockProps { readonly id: Id64String; - readonly state: LockState; + readonly state: LockState_2; } -// @public +// @public @deprecated export enum LockState { Exclusive = 2, None = 0, @@ -4071,7 +4072,7 @@ export interface LockStatusExclusive { // (undocumented) lastCsIndex?: ChangesetIndex; // (undocumented) - state: LockState.Exclusive; + state: LockState_2.Exclusive; } // @internal @@ -4081,7 +4082,7 @@ export interface LockStatusShared { // (undocumented) sharedBy: Set; // (undocumented) - state: LockState.Shared; + state: LockState_2.Shared; } // @internal diff --git a/common/api/core-common.api.md b/common/api/core-common.api.md index e225dcd610cb..8409f5fe3a77 100644 --- a/common/api/core-common.api.md +++ b/common/api/core-common.api.md @@ -1640,6 +1640,20 @@ export namespace ConcreteEntityTypes { export function toBisCoreRootClassFullName(type: ConcreteEntityTypes): string; } +// @public +export interface ConflictingLock { + briefcaseIds: number[]; + objectId: string; + state: LockState; +} + +// @public +export class ConflictingLocksError extends IModelError { + constructor(message: string, getMetaData?: LoggingMetaData, conflictingLocks?: ConflictingLock[]); + // (undocumented) + conflictingLocks?: ConflictingLock[]; +} + // @alpha export enum ContentFlags { // (undocumented) @@ -5533,6 +5547,13 @@ export interface Localization { unregisterNamespace(namespace: string): void; } +// @public +export enum LockState { + Exclusive = 2, + None = 0, + Shared = 1 +} + export { LogFunction } export { LoggingMetaData } diff --git a/common/api/summary/core-backend.exports.csv b/common/api/summary/core-backend.exports.csv index 7e395cbb7aae..aabe2e2d1a44 100644 --- a/common/api/summary/core-backend.exports.csv +++ b/common/api/summary/core-backend.exports.csv @@ -266,6 +266,7 @@ beta;LockControl internal;LockMap = Map beta;LockProps public;LockState +deprecated;LockState internal;LockStatusExclusive internal;LockStatusShared internal;MetaDataRegistry diff --git a/common/api/summary/core-common.exports.csv b/common/api/summary/core-common.exports.csv index f8d1b57d9496..8bfea9a2ddef 100644 --- a/common/api/summary/core-common.exports.csv +++ b/common/api/summary/core-common.exports.csv @@ -138,6 +138,8 @@ internal;computeTileChordTolerance(tile: TileMetadata, is3d: boolean, tileScreen alpha;ConcreteEntityTypes alpha;ConcreteEntityTypes internal;toBisCoreRootClassFullName(type: ConcreteEntityTypes): string +public;ConflictingLock +public;ConflictingLocksError alpha;ContentFlags internal;class ContentIdProvider public;ContextRealityModel @@ -460,6 +462,7 @@ public;LocalBriefcaseProps public;LocalDirName = string public;LocalFileName = string public;Localization +public;LockState public;MapImageryProps public;MapImagerySettings public;MapLayerKey diff --git a/common/changes/@itwin/core-backend/nick-conflictinglockserror_2024-06-17-17-47.json b/common/changes/@itwin/core-backend/nick-conflictinglockserror_2024-06-17-17-47.json new file mode 100644 index 000000000000..99b35bb89b62 --- /dev/null +++ b/common/changes/@itwin/core-backend/nick-conflictinglockserror_2024-06-17-17-47.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@itwin/core-backend", + "comment": "", + "type": "none" + } + ], + "packageName": "@itwin/core-backend" +} \ No newline at end of file diff --git a/common/changes/@itwin/core-common/nick-conflictinglockserror_2024-06-17-17-47.json b/common/changes/@itwin/core-common/nick-conflictinglockserror_2024-06-17-17-47.json new file mode 100644 index 000000000000..7c91fa5527dc --- /dev/null +++ b/common/changes/@itwin/core-common/nick-conflictinglockserror_2024-06-17-17-47.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@itwin/core-common", + "comment": "add ConflictingLocksError", + "type": "none" + } + ], + "packageName": "@itwin/core-common" +} \ No newline at end of file diff --git a/core/backend/src/BackendHubAccess.ts b/core/backend/src/BackendHubAccess.ts index 2d9527f27dda..1f1933cca413 100644 --- a/core/backend/src/BackendHubAccess.ts +++ b/core/backend/src/BackendHubAccess.ts @@ -9,25 +9,12 @@ import { AccessToken, GuidString, Id64String, IModelHubStatus } from "@itwin/core-bentley"; import { BriefcaseId, ChangesetFileProps, ChangesetIdWithIndex, ChangesetIndex, ChangesetIndexAndId, ChangesetIndexOrId, ChangesetProps, ChangesetRange, - IModelError, IModelVersion, LocalDirName, LocalFileName, + LockState as CommonLockState, IModelError, IModelVersion, + LocalDirName, LocalFileName, } from "@itwin/core-common"; import { CheckpointProps, DownloadRequest, ProgressFunction } from "./CheckpointManager"; import type { TokenArg } from "./IModelDb"; -/** The state of a lock. - * @public - */ -export enum LockState { - /** The element is not locked */ - None = 0, - /** Holding a shared lock on an element blocks other users from acquiring the Exclusive lock it. More than one user may acquire the shared lock. */ - Shared = 1, - /** A Lock that permits modifications to an element and blocks other users from making modifications to it. - * Holding an exclusive lock on an "owner" (a model or a parent element), implicitly exclusively locks all its members. - */ - Exclusive = 2, -} - /** Exception thrown if lock cannot be acquired. * @beta */ @@ -43,6 +30,21 @@ export class LockConflict extends IModelError { } } +/** The state of a lock. See [Acquiring locks on elements.]($docs/learning/backend/ConcurrencyControl.md#acquiring-locks-on-elements). + * @deprecated in 4.7 Use [LockState]($common) + * @public + */ +export enum LockState { + /** The element is not locked */ + None = 0, + /** Holding a shared lock on an element blocks other users from acquiring the Exclusive lock it. More than one user may acquire the shared lock. */ + Shared = 1, + /** A Lock that permits modifications to an element and blocks other users from making modifications to it. + * Holding an exclusive lock on an "owner" (a model or a parent element), implicitly exclusively locks all its members. + */ + Exclusive = 2, +} + /** * The properties to access a V2 checkpoint through a daemon. * @internal @@ -61,7 +63,7 @@ export interface V2CheckpointAccessProps { } /** @internal */ -export type LockMap = Map; +export type LockMap = Map; /** * The properties of a lock that may be obtained from a lock server. @@ -71,7 +73,7 @@ export interface LockProps { /** The elementId for the lock */ readonly id: Id64String; /** the lock state */ - readonly state: LockState; + readonly state: CommonLockState; } /** @@ -232,6 +234,7 @@ export interface BackendHubAccess { /** * acquire one or more locks. Throws if unsuccessful. If *any* lock cannot be obtained, no locks are acquired * @internal + * @throws ConflictingLocksError if one or more requested locks are held by other briefcases. */ acquireLocks: (arg: BriefcaseDbArg, locks: LockMap) => Promise; diff --git a/core/backend/src/IModelDb.ts b/core/backend/src/IModelDb.ts index 28aec807655e..750f78fcb9f9 100644 --- a/core/backend/src/IModelDb.ts +++ b/core/backend/src/IModelDb.ts @@ -167,6 +167,7 @@ export interface LockControl { * If any required lock is not available, this method throws an exception and *none* of the requested locks are acquired. * > Note: acquiring the exclusive lock on an element requires also obtaining a shared lock on all its owner elements. This method will * attempt to acquire all necessary locks for both sets of input ids. + * @throws ConflictingLocksError if one or more requested locks are held by other briefcases. */ acquireLocks(arg: { /** if present, one or more elements to obtain shared lock */ diff --git a/core/backend/src/LocalHub.ts b/core/backend/src/LocalHub.ts index 11344981299b..d3a6e57d4a51 100644 --- a/core/backend/src/LocalHub.ts +++ b/core/backend/src/LocalHub.ts @@ -7,9 +7,9 @@ import { join } from "path"; import { DbResult, GuidString, Id64String, IModelHubStatus, IModelStatus, OpenMode } from "@itwin/core-bentley"; import { BriefcaseId, BriefcaseIdValue, ChangesetFileProps, ChangesetId, ChangesetIdWithIndex, ChangesetIndex, ChangesetIndexOrId, ChangesetProps, - ChangesetRange, IModelError, LocalDirName, LocalFileName, + ChangesetRange, IModelError, LocalDirName, LocalFileName, LockState, } from "@itwin/core-common"; -import { LockConflict, LockMap, LockProps, LockState } from "./BackendHubAccess"; +import { LockConflict, LockMap, LockProps } from "./BackendHubAccess"; import { BriefcaseManager } from "./BriefcaseManager"; import { BriefcaseLocalValue, IModelDb, SnapshotDb } from "./IModelDb"; import { IModelJsFs } from "./IModelJsFs"; diff --git a/core/backend/src/ServerBasedLocks.ts b/core/backend/src/ServerBasedLocks.ts index 9de7a7437de2..0d4c79bafb05 100644 --- a/core/backend/src/ServerBasedLocks.ts +++ b/core/backend/src/ServerBasedLocks.ts @@ -8,8 +8,8 @@ */ import { DbResult, Id64, Id64Arg, Id64String, IModelStatus, OpenMode } from "@itwin/core-bentley"; -import { IModel, IModelError } from "@itwin/core-common"; -import { LockMap, LockState } from "./BackendHubAccess"; +import { IModel, IModelError, LockState } from "@itwin/core-common"; +import { LockMap } from "./BackendHubAccess"; import { BriefcaseDb, LockControl } from "./IModelDb"; import { IModelHost } from "./IModelHost"; import { SQLiteDb } from "./SQLiteDb"; diff --git a/core/backend/src/test/standalone/HubMock.test.ts b/core/backend/src/test/standalone/HubMock.test.ts index 1b6e37c7f2bc..37680dad5d85 100644 --- a/core/backend/src/test/standalone/HubMock.test.ts +++ b/core/backend/src/test/standalone/HubMock.test.ts @@ -6,8 +6,8 @@ import { assert, expect } from "chai"; import { join } from "path"; import { AccessToken, Guid, Mutable } from "@itwin/core-bentley"; -import { ChangesetFileProps, ChangesetType } from "@itwin/core-common"; -import { LockProps, LockState } from "../../BackendHubAccess"; +import { ChangesetFileProps, ChangesetType, LockState } from "@itwin/core-common"; +import { LockProps } from "../../BackendHubAccess"; import { BriefcaseManager } from "../../BriefcaseManager"; import { IModelHost } from "../../IModelHost"; import { IModelJsFs } from "../../IModelJsFs"; diff --git a/core/backend/src/test/standalone/IModelWrite.test.ts b/core/backend/src/test/standalone/IModelWrite.test.ts index 0edd0ce8a2d7..e65d7842b79a 100644 --- a/core/backend/src/test/standalone/IModelWrite.test.ts +++ b/core/backend/src/test/standalone/IModelWrite.test.ts @@ -6,7 +6,7 @@ import { AccessToken, DbResult, GuidString, Id64, Id64String } from "@itwin/core-bentley"; import { Code, ColorDef, - GeometricElement2dProps, GeometryStreamProps, IModel, QueryRowFormat, RequestNewBriefcaseProps, SchemaState, SubCategoryAppearance, + GeometricElement2dProps, GeometryStreamProps, IModel, LockState, QueryRowFormat, RequestNewBriefcaseProps, SchemaState, SubCategoryAppearance, } from "@itwin/core-common"; import { Arc3d, IModelJson, Point2d, Point3d } from "@itwin/core-geometry"; import * as chai from "chai"; @@ -24,7 +24,7 @@ import { BriefcaseManager, ChannelControl, CodeService, - DefinitionModel, DictionaryModel, DocumentListModel, Drawing, DrawingGraphic, LockState, OpenBriefcaseArgs, SpatialCategory, Subject, + DefinitionModel, DictionaryModel, DocumentListModel, Drawing, DrawingGraphic, OpenBriefcaseArgs, SpatialCategory, Subject, } from "../../core-backend"; import { IModelTestUtils, TestUserType } from "../IModelTestUtils"; import { ServerBasedLocks } from "../../ServerBasedLocks"; diff --git a/core/backend/src/test/standalone/ServerBasedLocks.test.ts b/core/backend/src/test/standalone/ServerBasedLocks.test.ts index b077fe058334..ed9ea4abc818 100644 --- a/core/backend/src/test/standalone/ServerBasedLocks.test.ts +++ b/core/backend/src/test/standalone/ServerBasedLocks.test.ts @@ -6,8 +6,7 @@ import { assert, expect } from "chai"; import { restore as sinonRestore, spy as sinonSpy } from "sinon"; import { AccessToken, GuidString, Id64, Id64Arg } from "@itwin/core-bentley"; -import { Code, IModel, IModelError, LocalBriefcaseProps, PhysicalElementProps, RequestNewBriefcaseProps } from "@itwin/core-common"; -import { LockState } from "../../BackendHubAccess"; +import { Code, IModel, IModelError, LocalBriefcaseProps, LockState, PhysicalElementProps, RequestNewBriefcaseProps } from "@itwin/core-common"; import { BriefcaseManager } from "../../BriefcaseManager"; import { PhysicalObject } from "../../domains/GenericElements"; import { PhysicalElement } from "../../Element"; diff --git a/core/common/src/IModelError.ts b/core/common/src/IModelError.ts index 3d745e2e7bac..5dc30e6e3d0e 100644 --- a/core/common/src/IModelError.ts +++ b/core/common/src/IModelError.ts @@ -7,7 +7,7 @@ */ import { - BentleyError, BentleyStatus, BriefcaseStatus, ChangeSetStatus, DbResult, IModelStatus, LoggingMetaData, RepositoryStatus, + BentleyError, BentleyStatus, BriefcaseStatus, ChangeSetStatus, DbResult, IModelHubStatus, IModelStatus, LoggingMetaData, RepositoryStatus, } from "@itwin/core-bentley"; export { @@ -32,6 +32,50 @@ export class IModelError extends BentleyError { } } +/** The state of a lock. See [Acquiring locks on elements.]($docs/learning/backend/ConcurrencyControl.md#acquiring-locks-on-elements). + * @public + */ +export enum LockState { + /** The element is not locked */ + None = 0, + /** Holding a shared lock on an element blocks other users from acquiring the Exclusive lock it. More than one user may acquire the shared lock. */ + Shared = 1, + /** A Lock that permits modifications to an element and blocks other users from making modifications to it. + * Holding an exclusive lock on an "owner" (a model or a parent element), implicitly exclusively locks all its members. + */ + Exclusive = 2, +} + +/** Detailed information about a particular object Lock that is causing the Lock update conflict. + * An example of a lock update conflict would be attempting to use [LockControl.acquireLocks]($backend) on an object that is already locked by another Briefcase. + * @public +*/ +export interface ConflictingLock { + /** Id of the object that is causing conflict. */ + objectId: string; + /** + * The level of conflicting lock. Possible values are {@link LockState.Shared}, {@link LockState.Exclusive}. + * See {@link LockState}. + */ + state: LockState; + /** An array of Briefcase ids that hold this lock. */ + briefcaseIds: number[]; +} + +/** + * An error raised when there is a lock conflict detected. + * Typically this error would be thrown by [LockControl.acquireLocks]($backend) when you are requesting a lock on an element that is already held by another briefcase. + * @public + */ +export class ConflictingLocksError extends IModelError { + public conflictingLocks?: ConflictingLock[]; + constructor(message: string, getMetaData?: LoggingMetaData, conflictingLocks?: ConflictingLock[]) { + super(IModelHubStatus.LockOwnedByAnotherBriefcase, message, getMetaData); + this.conflictingLocks = conflictingLocks; + } + +} + /** @public */ export class ServerError extends IModelError { public constructor(errorNumber: number, message: string) {