Files
DP44/enriched-partialglm/Common/DTS.Common.Import/DatabaseLocks.md
2026-04-17 14:55:32 -04:00

7.8 KiB

source_files, generated_at, model, schema_version, sha256
source_files generated_at model schema_version sha256
Common/DTS.Common.Import/DatabaseLocks/LockImportGroups.cs
Common/DTS.Common.Import/DatabaseLocks/LockImportTestSetups.cs
Common/DTS.Common.Import/DatabaseLocks/LockImportSensors.cs
2026-04-16T11:44:27.973248+00:00 zai-org/GLM-5-FP8 1 95ed39e21f5416a3

Documentation: DTS.Common.Import.DatabaseLocks

1. Purpose

This module provides a concurrency control mechanism for the import process, ensuring that database entities (Groups, TestSetups, and Sensors) are not modified by multiple users simultaneously. It implements the ILockImport interface to manage the lifecycle of database locks: acquiring them before an import operation, detecting contention (locks held by other users), handling "stranded" locks (expired locks), and releasing locks upon completion. It enforces permissions for lock stealing, restricting the ability to forcefully take locks to administrative users.

2. Public Interface

The module consists of three concrete classes, all implementing ILockImport.

Class: LockImportGroups

Manages locking for static group entities.

  • Constructor: LockImportGroups(User currentUser, double strandedLockTimeoutMinutes)
    • Initializes the locker with the user context and the threshold for considering a lock "stranded" (expired).
  • Property: bool Contended
    • Returns true if any group attempted to be locked is currently held by another user; otherwise false.
  • Method: void FreeLock(ref ImportObject importObject)
    • Releases all groups currently stored in the internal _lockedGroups list by calling LockManager.FreeLock. Note that the importObject parameter is ignored in the implementation.
  • Method: void SetLock(ref ImportObject importObject, ref StringBuilder message)
    • Iterates over importObject.StaticGroups(). Attempts to lock each group.
    • If a lock fails because the item is not found (LockError.ITEM_NOT_FOUND), it skips the item.
    • If a lock is held by the current user (matching UserName and MachineName) or is expired (LastUpdated + _strandedLockTimeoutMinutes < DateTime.Now), it forcefully claims the lock.
    • Populates _contendedGroups if locks are held by others. Appends contention details to the message StringBuilder.
  • Method: bool StealLock(bool proceed)
    • Attempts to forcefully take locks listed in _contendedGroups.
    • Returns false if locks are contended and the user is not an Admin (!_currentUser.IsAdmin).
    • Returns false if locks are contended and proceed is false.
    • Returns true if no contention exists or if the steal operation proceeds successfully.

Class: LockImportTestSetups

Manages locking for test setup entities.

  • Constructor: LockImportTestSetups(User currentUser, double strandedLockTimeoutMinutes)
  • Property: bool Contended
    • Returns true if _contendedTests contains any items.
  • Method: void FreeLock(ref ImportObject importObject)
    • Releases locks for items in _lockedTests.
  • Method: void SetLock(ref ImportObject importObject, ref StringBuilder message)
    • Iterates over importObject.TestSetups(). Logic mirrors LockImportGroups regarding expiration, ownership checks, and contention handling.
  • Method: bool StealLock(bool proceed)
    • Logic mirrors LockImportGroups regarding Admin checks and the proceed flag.

Class: LockImportSensors

Manages locking for sensor entities.

  • Constructor: LockImportSensors(User currentUser, double strandedLockTimeoutMinutes)
  • Property: bool Contended
    • Returns true if _contendedSensors contains any items.
  • Method: void FreeLock(ref ImportObject importObject)
    • Releases locks for items in _lockedSensors.
  • Method: void SetLock(ref ImportObject importObject, ref StringBuilder message)
    • Difference from other classes: It first retrieves all sensors via SensorsCollection.SensorsList.GetAllSensors(true) and creates a dictionary lookup.
    • Iterates importObject.Sensors(). Skips locking if the serial number is not found in the lookup.
    • Logic mirrors other classes for handling existing/expired locks.
  • Method: bool StealLock(bool proceed)
    • Logic mirrors LockImportGroups regarding Admin checks and the proceed flag.

3. Invariants

  1. Admin Requirement for Stealing: The StealLock method will always return false if _contendedGroups (or equivalent) is populated and _currentUser.IsAdmin is false.
  2. Lock Ownership Identity: A lock is considered "owned" by the current user in SetLock only if both existingLock.LockingUserName matches _currentUser.UserName AND existingLock.LockingMachineName matches Environment.MachineName.
  3. State Reset: Calling SetLock always clears the internal locked and contended lists (_lockedGroups, _contendedGroups, etc.) before processing new locks.
  4. Stranded Lock Calculation: A lock is considered stranded/eligible for reclamation if existingLock.LastUpdated.AddMinutes(_strandedLockTimeoutMinutes) < DateTime.Now.
  5. Exception Handling: In SetLock methods, if an exception occurs during the locking loop for a specific item, it is logged via APILogger.Log(ex) and the loop continues to the next item; the import process is not halted by a single locking failure.

4. Dependencies

Internal Dependencies (Referenced in Source)

  • DTS.Common.Classes.Locking: Provides LockManager, LockRecord, LockError.
  • DTS.Common.Import.Interfaces: Defines ILockImport.
  • DTS.Common.SharedResource.Strings: Provides StringResources for user messages.
  • DTS.Common.Storage: Likely defines ImportObject.
  • DTS.Common.Utilities.Logging: Provides APILogger.
  • DTS.Slice.Users: Provides the User class.
  • DTS.SensorDB: Provides SensorsCollection (used specifically in LockImportSensors).

External Dependencies (Inferred)

  • System, System.Collections.Generic, System.Linq, System.Text.

Dependents

  • Unknown from source alone. Presumably, an Import Manager or Controller class instantiates these lockers to orchestrate import operations.

5. Gotchas

  1. Incorrect Item Category in StealLock (Bug): In both LockImportGroups.StealLock and LockImportTestSetups.StealLock, the code calls LockManager.LockItem with LockManager.ItemCategories.Sensor.

    • Source Evidence: LockImportGroups.cs line 93 and LockImportTestSetups.cs line 90.
    • Impact: When stealing a lock for a Group or TestSetup, the new lock is incorrectly recorded in the database with the "Sensor" category. LockImportSensors uses the correct category.
  2. Incorrect Error Message in LockImportSensors: In LockImportSensors.SetLock, when appending contention details to the message, the code uses StringResources.ImportTestSetup_TestsLocked.

    • Source Evidence: LockImportSensors.cs line 76.
    • Impact: Users importing sensors will receive an error message intended for Test Setups.
  3. Unused Parameter in FreeLock: The FreeLock method signature includes ref ImportObject importObject, but the implementation in all three classes ignores this parameter entirely. It relies strictly on the internal list (_lockedGroups, etc.) populated during SetLock. Passing a different ImportObject to FreeLock than was passed to SetLock will have no effect; the locks released are determined solely by the previous SetLock call.

  4. Silent Failures: The SetLock methods catch all exceptions, log them, and continue. If LockManager throws an unexpected exception (e.g., database connection failure), the import process will proceed for remaining items, potentially resulting in a partial lock state without a clear indication to the caller other than the log file.