156 lines
9.2 KiB
Markdown
156 lines
9.2 KiB
Markdown
|
|
---
|
||
|
|
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.
|