Files

110 lines
7.8 KiB
Markdown
Raw Permalink Normal View History

2026-04-17 14:55:32 -04:00
---
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-16T11:44:27.973248+00:00"
model: "zai-org/GLM-5-FP8"
schema_version: 1
sha256: "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.