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

9.2 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-16T02:06:50.106613+00:00 Qwen/Qwen3-Coder-Next-FP8 1 95ed39e21f5416a3

Documentation: Database Locking for Import Operations

1. Purpose

This module provides database-level locking for import operations on three distinct entity types—Groups, Test Setups, and Sensors—to prevent concurrent modifications during data import workflows. Each class (LockImportGroups, LockImportTestSetups, LockImportSensors) implements the ILockImport interface and encapsulates logic to acquire, release, and (if authorized) steal locks on the respective entities. Locks are managed via the shared LockManager utility, with support for detecting and reclaiming stranded locks (i.e., locks held longer than a configurable timeout) and handling contention by requiring explicit admin consent. These classes exist to ensure data integrity and consistency during import processes that modify critical system resources.

2. Public Interface

All three classes implement the same interface ILockImport, with identical method signatures and semantics. Below are the concrete implementations per class.

LockImportGroups

  • Constructor:
    LockImportGroups(User currentUser, double strandedLockTimeoutMinutes)
    Initializes the instance with the current user and timeout (in minutes) after which a lock is considered stranded.

  • bool Contended { get; }
    Returns true if any group lock acquisition failed due to contention (i.e., another user holds the lock and it is not expired or owned by the current user).

  • void FreeLock(ref ImportObject importObject)
    Releases all locks currently held by this instance (stored in _lockedGroups). Iterates over _lockedGroups and calls LockManager.FreeLock(...) for each. No-op if _lockedGroups is empty.

  • void SetLock(ref ImportObject importObject, ref StringBuilder message)
    Attempts to acquire locks for all groups returned by importObject.StaticGroups().

    • Clears _lockedGroups and _contendedGroups first.
    • For each group:
      • Calls LockManager.LockItem(...) with category LockManager.ItemCategories.Group.
      • On failure:
        • Skips if lockError.ErrorCode == LockError.ITEM_NOT_FOUND.
        • Otherwise, checks if lock is expired (LastUpdated + timeout < Now) or owned by current user/machine. If so, frees and re-acquires the lock, adding to _lockedGroups.
        • Else, adds the existing lock record to _contendedGroups.
      • On success, adds lock record to _lockedGroups.
    • If any locks are contended, appends a user-facing message listing each contended group (key, owner, machine) using StringResources.ImportTestSetup_GroupsLocked.
  • bool StealLock(bool proceed)
    Attempts to steal contended locks (only if proceed == true and _currentUser.IsAdmin == true).

    • Returns true if no contention or if stealing succeeded.
    • Returns false if user is not admin or proceed is false.
    • For each contended group:
      • Calls LockManager.FreeLock(...) to release the existing lock.
      • Calls LockManager.LockItem(...) again (note: category incorrectly set to LockManager.ItemCategories.Sensor—see Gotchas).
      • Adds the new lock record to _lockedGroups.

LockImportTestSetups

  • Constructor:
    LockImportTestSetups(User currentUser, double strandedLockTimeoutMinutes)
    Same semantics as LockImportGroups.

  • bool Contended { get; }
    Same semantics.

  • void FreeLock(ref ImportObject importObject)
    Releases locks in _lockedTests via LockManager.FreeLock(...).

  • void SetLock(ref ImportObject importObject, ref StringBuilder message)
    Attempts to lock all test setups returned by importObject.TestSetups(), using category LockManager.ItemCategories.TestSetup.

    • On contention, appends message using StringResources.ImportTestSetup_TestsLocked.
  • bool StealLock(bool proceed)
    Same logic as LockImportGroups.StealLock(...), but operates on _contendedTests and _lockedTests.

LockImportSensors

  • Constructor:
    LockImportSensors(User currentUser, double strandedLockTimeoutMinutes)
    Same semantics.

  • bool Contended { get; }
    Same semantics.

  • void FreeLock(ref ImportObject importObject)
    Releases locks in _lockedSensors via LockManager.FreeLock(...).

  • void SetLock(ref ImportObject importObject, ref StringBuilder message)
    Attempts to lock all sensors referenced in importObject.Sensors().

    • Pre-builds a sensorLookup from SensorsCollection.SensorsList.GetAllSensors(true) keyed by SerialNumber.
    • Skips sensors not in the lookup.
    • Uses category LockManager.ItemCategories.Sensor.
    • On contention, appends message using StringResources.ImportTestSetup_TestsLocked (note: same string as test setups—see Gotchas).
  • bool StealLock(bool proceed)
    Same logic as others, but operates on _contendedSensors and _lockedSensors.

3. Invariants

  • Lock ownership and expiration:
    A lock is considered reclaimable if either:

    • existingLock.LastUpdated.AddMinutes(_strandedLockTimeoutMinutes) < DateTime.Now, or
    • existingLock.LockingUserName == _currentUser.UserName && existingLock.LockingMachineName == Environment.MachineName.
  • Lock state separation:
    After SetLock(...) completes, _lockedGroups/_lockedTests/_lockedSensors contains only locks successfully acquired (including re-claimed ones), and _contendedGroups/_contendedTests/_contendedSensors contains only locks that could not be acquired due to active contention.

  • Contended property:
    Always reflects whether _contended* list is non-empty after the last SetLock(...) call.

  • Admin requirement for stealing:
    StealLock(...) returns false unless _currentUser.IsAdmin == true and proceed == true.

  • No double-locking:
    SetLock(...) clears the internal lists before processing, ensuring no stale lock records persist across calls.

4. Dependencies

External Dependencies

  • DTS.Common.Classes.Locking.LockManager: Central lock manager used for LockItem, FreeLock. Defines LockError, LockRecord, and ItemCategories.
  • DTS.Slice.Users.User: Represents the current user; accessed for UserName, Id, and IsAdmin.
  • DTS.Common.Storage.LockManager: Used in LockImportSensors to access SensorsCollection.SensorsList.GetAllSensors(...).
  • DTS.Common.SharedResource.Strings.StringResources: Provides localized messages (e.g., ImportTestSetup_GroupsLocked, ImportTestSetup_TestsLocked).
  • DTS.Common.Utilities.Logging.APILogger: Used to log exceptions during lock operations.

Interface Dependencies

  • ILockImport: All three classes implement this interface (not shown in source, but implied by usage and naming). Signature must include SetLock, FreeLock, StealLock, and Contended.

Depended Upon

  • These classes are likely used by higher-level import orchestrators (e.g., ImportService, ImportWizard) that coordinate import workflows and handle user prompts for lock stealing.

5. Gotchas

  • Incorrect category in StealLock for Groups:
    In LockImportGroups.StealLock(...), the re-lock call uses LockManager.ItemCategories.Sensor instead of LockManager.ItemCategories.Group. This is inconsistent with the initial lock and may cause lock metadata corruption or misattribution.

  • Incorrect resource string for sensor contention messages:
    In LockImportSensors.SetLock(...), the message appended on contention uses StringResources.ImportTestSetup_TestsLocked (same as test setups), not a sensor-specific string. This may mislead users.

  • No validation of ImportObject contents:
    The classes assume importObject.StaticGroups(), TestSetups(), and Sensors() return valid collections. No null/empty checks are performed on the collections themselves.

  • Exception swallowing in SetLock:
    All exceptions during lock acquisition are caught and logged via APILogger.Log(ex), but the method continues silently. This may hide critical failures (e.g., database connectivity issues) and leave the system in an inconsistent state (e.g., partial locking).

  • Race condition in lock stealing:
    Between FreeLock and LockItem in StealLock(...), another user could acquire the lock. The code does not re-check lock ownership before re-locking.

  • Ambiguity in ImportObject interface:
    The source does not define ImportObject or its methods (StaticGroups, TestSetups, Sensors). Their behavior (e.g., ordering, nullability) is unknown.

  • No rollback on partial failure:
    If SetLock(...) fails partway through (e.g., due to an exception), already-locked items are not released. Only FreeLock(...) (called later) cleans up.

  • Machine name dependency:
    Lock ownership check uses Environment.MachineName, which may be unreliable in containerized/distributed environments or if machine names change.

None identified beyond the above.