using Opc.Ua; namespace ZB.MOM.WW.OtOpcUa.Client.Shared.Adapters; /// /// The one the shared client hands the SDK's non-obsolete (telemetry-taking) /// overloads. An empty builder keeps SDK internals off any logging provider; the client logs through Serilog. /// internal static class ClientTelemetry { private static readonly Lazy Instance = new(() => DefaultTelemetry.Create(_ => { })); /// Gets the shared telemetry context. public static ITelemetryContext Context => Instance.Value; } /// /// Bounded sync-over-async for the Dispose paths, which cannot await. Runs on the thread pool so a /// caller on a synchronization context (the Avalonia UI thread) cannot deadlock, and waits at most /// so a dead server cannot hang teardown. /// internal static class DisposeBridge { private static readonly TimeSpan Timeout = TimeSpan.FromSeconds(5); /// Runs to completion (or the timeout), swallowing every failure. /// The asynchronous work to run; it receives a token that is cancelled when the timeout elapses. public static void Run(Func work) { using var cts = new CancellationTokenSource(Timeout); try { var task = Task.Run(() => work(cts.Token), cts.Token); // Observe a late fault so it never surfaces as an unobserved-task exception. _ = task.ContinueWith(t => _ = t.Exception, TaskContinuationOptions.OnlyOnFaulted); // The CTS's own timer and this Wait race: when Wait loses, `using` would dispose the CTS before // the token ever fired, leaving the work running while the caller goes on to dispose the session. // Cancel explicitly so the in-flight Close/Delete is told to stop. if (!task.Wait(Timeout)) { cts.Cancel(); } } catch { // Teardown is best-effort, exactly as the synchronous Close/Delete it replaces. } } }