--- source_files: - Common/DTS.Common.Import/DatabaseLocks/LockImportGroups.cs - Common/DTS.Common.Import/DatabaseLocks/LockImportTestSetups.cs - Common/DTS.Common.Import/DatabaseLocks/LockImportSensors.cs generated_at: "2026-04-16T02:06:50.106613+00:00" model: "Qwen/Qwen3-Coder-Next-FP8" schema_version: 1 sha256: "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.