namespace ZB.MOM.WW.OtOpcUa.AdminUI.Browsing; /// /// Closes a browse session that finished opening after its host went away (R04 L4). Every picker and /// RawBrowseModal awaited and then stored the token; /// if the host was disposed (or the modal closed) meanwhile, its teardown had already run with no token, /// so the session — an OPC UA client session, an MQTT connection, a SQL connection against the plant — /// stayed open until the reaper's idle TTL (2 min + one 30 s tick). /// public static class BrowseOpenGuard { /// /// Call right after returns. When the host is gone it /// closes the new session (fire-and-forget, like the hosts' own teardown) and returns /// , telling the caller not to adopt the token. /// /// The open result. /// Whether the host was disposed (or closed) while the open was in flight. /// The browse service the session was opened on. /// when the caller must drop the result. public static bool CloseIfOrphaned(BrowseOpenResult result, bool hostGone, IBrowserSessionService service) { if (!hostGone) return false; if (result.Ok && result.Token != Guid.Empty) { // Never block (or throw into) a dead host's continuation on a slow remote close. _ = CloseQuietlyAsync(service, result.Token); } return true; } private static async Task CloseQuietlyAsync(IBrowserSessionService service, Guid token) { try { await service.CloseAsync(token).ConfigureAwait(false); } catch { // The reaper still bounds a session whose close failed. } } }