feat(abcip): per-runtime operation lock on DeviceState (AbLegacy pattern)
This commit is contained in:
@@ -1243,6 +1243,24 @@ public sealed class AbCipDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
public SemaphoreSlim GetRmwLock(string parentTagName) =>
|
||||
_rmwLocks.GetOrAdd(parentTagName, _ => new SemaphoreSlim(1, 1));
|
||||
|
||||
private readonly System.Collections.Concurrent.ConcurrentDictionary<string, SemaphoreSlim> _runtimeLocks =
|
||||
new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Per-runtime operation lock (05/STAB-4). A libplctag <c>Tag</c> handle is not safe for
|
||||
/// concurrent Read/GetStatus/DecodeValue (or Encode/Write/GetStatus) — the server read
|
||||
/// path, every poll-group loop, and the alarm-projection loop all call
|
||||
/// <see cref="AbCipDriver.ReadAsync"/> / <see cref="AbCipDriver.WriteAsync"/> against the
|
||||
/// same cached runtime, and <c>GetStatus</c> returns the <em>last</em> operation's status
|
||||
/// — so an unserialised overlap decodes a torn / cross-attributed value with Good status.
|
||||
/// Callers hold this lock around the whole op sequence. Keyed by tag name, which is also
|
||||
/// the <see cref="Runtimes"/> dictionary key. Mirrors the field-proven AbLegacy pattern.
|
||||
/// </summary>
|
||||
/// <param name="tagName">The tag name (the <see cref="Runtimes"/> key).</param>
|
||||
/// <returns>The per-runtime operation semaphore.</returns>
|
||||
public SemaphoreSlim GetRuntimeLock(string tagName) =>
|
||||
_runtimeLocks.GetOrAdd(tagName, _ => new SemaphoreSlim(1, 1));
|
||||
|
||||
/// <summary>
|
||||
/// Compute the effective <see cref="AbCipTagCreateParams"/> for a
|
||||
/// tag on this device. Combines the per-device options
|
||||
@@ -1277,6 +1295,12 @@ public sealed class AbCipDriver : IDriver, IReadable, IWritable, ITagDiscovery,
|
||||
Runtimes.Clear();
|
||||
foreach (var r in ParentRuntimes.Values) r.Dispose();
|
||||
ParentRuntimes.Clear();
|
||||
// Dispose + clear the per-runtime/RMW gates so ReinitializeAsync cycles don't orphan
|
||||
// their SemaphoreSlim instances (each leaks a wait handle once contended).
|
||||
foreach (var sem in _runtimeLocks.Values) sem.Dispose();
|
||||
_runtimeLocks.Clear();
|
||||
foreach (var sem in _rmwLocks.Values) sem.Dispose();
|
||||
_rmwLocks.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user