/// <summary> /// Handles the failed state of the session /// </summary> /// <param name="id"></param> /// <param name="wrapper"></param> /// <param name="ct"></param> /// <returns></returns> private async Task HandleFailedAsync(ConnectionIdentifier id, SessionWrapper wrapper, CancellationToken ct) { try { // check if session requires cleanup if (!wrapper._subscriptions.Any()) { if (wrapper.IdleCount < wrapper.MaxKeepAlives) { wrapper.IdleCount++; } else { _logger.Information("Session '{id}' set to disconnect in {state}", id, wrapper.State); wrapper.State = SessionState.Disconnect; await HandleDisconnectAsync(id, wrapper).ConfigureAwait(false); return; } } else { wrapper.IdleCount = 0; } if (!ct.IsCancellationRequested) { await HandleInitAsync(id, wrapper, ct).ConfigureAwait(false); } } catch (Exception ex) { _logger.Error(ex, "Failed to reinitiate failed session"); } }
/// <inheritdoc/> public ISessionHandle GetSessionHandle(ConnectionModel connection) { if (connection?.Endpoint == null) { throw new ArgumentNullException(nameof(connection)); } var id = new ConnectionIdentifier(connection); _lock.Wait(); try { // Add a persistent session if (!_clients.TryGetValue(id, out var client)) { var tuple = ClientSession.CreateWithHandle(_appConfig, id.Connection, _logger, NotifyStateChangeAsync, _maxOpTimeout); _clients.Add(id, tuple.Item1); _logger.Debug("Opened session for endpoint {id} ({endpoint}).", id, connection.Endpoint.Url); return(tuple.Item2); } return(client.GetSafeHandle()); } finally { _lock.Release(); } }
/// <inheritdoc/> public void RegisterSubscription(ISubscription subscription) { var id = new ConnectionIdentifier(subscription.Connection); _lock.Wait(); try { if (!_sessions.TryGetValue(id, out var wrapper)) { wrapper = new SessionWrapper() { Id = id.ToString(), MissedKeepAlives = 0, MaxKeepAlives = (int)_clientConfig.MaxKeepAliveCount, State = SessionState.Init, Session = null, IdleCount = 0 }; _sessions.AddOrUpdate(id, wrapper); } wrapper._subscriptions.AddOrUpdate(subscription.Id, subscription); _logger.Information("Subscription '{subscriptionId}' registered/updated in session '{id}' in state {state}", subscription.Id, id, wrapper.State); if (wrapper.State == SessionState.Running) { wrapper.State = SessionState.Refresh; } TriggerKeepAlive(); } finally { _lock.Release(); } }
/// <summary> /// Handles the disconnect state of a session /// </summary> /// <param name="id"></param> /// <param name="wrapper"></param> /// <returns></returns> private async Task HandleDisconnectAsync(ConnectionIdentifier id, SessionWrapper wrapper) { _logger.Debug("Removing session '{id}'", id); await _lock.WaitAsync().ConfigureAwait(false); try { _sessions.Remove(id, out _); } finally { _lock.Release(); } try { if (wrapper != null && wrapper.Session != null) { wrapper.Session.Handle = null; // Remove subscriptions if (wrapper.Session.SubscriptionCount > 0) { foreach (var subscription in wrapper.Session.Subscriptions) { Try.Op(() => subscription.DeleteItems()); } Try.Op(() => wrapper.Session.RemoveSubscriptions(wrapper.Session.Subscriptions)); } // close the session Try.Op(wrapper.Session.Close); Try.Op(wrapper.Session.Dispose); wrapper.Session = null; } } catch (Exception ex) { _logger.Error(ex, "Failed to remove session '{id}'", id); } }
/// <inheritdoc/> public async Task RemoveSessionAsync(ConnectionModel connection, bool onlyIfEmpty = true) { var key = new ConnectionIdentifier(connection); await _lock.WaitAsync(); try { if (!_sessions.TryGetValue(key, out var wrapper) || wrapper?.Session == null) { return; } var session = wrapper.Session; if (onlyIfEmpty && session.SubscriptionCount > 0) { return; } // Remove subscriptions if (session.SubscriptionCount > 0) { if (onlyIfEmpty) { return; } Try.Op(() => session.RemoveSubscriptions(session.Subscriptions)); } _sessions.Remove(key); Try.Op(session.Close); Try.Op(session.Dispose); } finally { _lock.Release(); } }
/// <inheritdoc/> public Task <T> ExecuteServiceAsync <T>(ConnectionModel connection, CredentialModel elevation, int priority, Func <Session, Task <T> > service, TimeSpan?timeout, CancellationToken ct, Func <Exception, bool> handler) { if (connection.Endpoint == null) { throw new ArgumentNullException(nameof(connection)); } if (string.IsNullOrEmpty(connection.Endpoint?.Url)) { throw new ArgumentNullException(nameof(connection.Endpoint.Url)); } var key = new ConnectionIdentifier(connection); while (!_cts.IsCancellationRequested) { var client = GetOrCreateSession(key); if (!client.Inactive) { var scheduled = client.TryScheduleServiceCall(elevation, priority, service, handler, timeout, ct, out var result); if (scheduled) { // Session is owning the task to completion now. return(result); } } // Create new session next go around EvictIfInactive(key); } return(Task.FromCanceled <T>(_cts.Token)); }
/// <summary> /// Run export cycles /// </summary> /// <param name="id"></param> /// <param name="diagnostics"></param> /// <param name="ct"></param> /// <returns></returns> private async Task UploadModelAsync(ConnectionIdentifier id, DiagnosticsModel diagnostics, CancellationToken ct) { var fullPath = Path.Combine(Path.GetTempPath(), FileName); try { _outer._logger.Information("Start model upload to {fileName} for {url}.", FileName, id.Connection.Endpoint.Url); using (var file = new FileStream(fullPath, FileMode.Create)) using (var stream = new GZipStream(file, CompressionMode.Compress)) { // TODO: Try read nodeset from namespace metadata! // ... // Otherwise browse model await BrowseEncodeModelAsync(id.Connection.Endpoint, diagnostics, stream, ct); } // now upload file await _outer._upload.SendFileAsync(fullPath, Encoding); _outer._logger.Information("Model uploaded to {fileName} for {url}.", FileName, id.Connection.Endpoint.Url); } catch (OperationCanceledException) { _outer._logger.Information("Cancelled model upload of {fileName} for {url}", FileName, id.Connection.Endpoint.Url); } catch (Exception ex) { _outer._logger.Error(ex, "Error during exportto {fileName} for {url}.", FileName, id.Connection.Endpoint.Url); } finally { File.Delete(fullPath); _outer._tasks.TryRemove(id, out var tmp); } }
/// <summary> /// Create model upload task /// </summary> /// <param name="outer"></param> /// <param name="id"></param> /// <param name="contentType"></param> /// <param name="diagnostics"></param> public ModelUploadTask(DataUploadServices outer, ConnectionIdentifier id, string contentType, DiagnosticsModel diagnostics) { Encoding = ValidateEncoding(contentType, out var extension); StartTime = DateTime.UtcNow; FileName = $"{id.GetHashCode()}_{StartTime.ToBinary()}{extension}"; _outer = outer; _cts = new CancellationTokenSource(); _job = _outer._scheduler.Run(() => UploadModelAsync(id, diagnostics, _cts.Token)); }
/// <inheritdoc/> public async Task <Session> GetOrCreateSessionAsync(ConnectionModel connection, bool createIfNotExists) { // Find session and if not exists create var id = new ConnectionIdentifier(connection); await _lock.WaitAsync(); try { if (!_sessions.TryGetValue(id, out var wrapper) && createIfNotExists) { var sessionName = id.ToString(); var applicationConfiguration = _clientConfig.ToApplicationConfiguration(true); var endpointConfiguration = _clientConfig.ToEndpointConfiguration(); var endpointDescription = new EndpointDescription(id.Connection.Endpoint.Url); var configuredEndpoint = new ConfiguredEndpoint( null, endpointDescription, endpointConfiguration); _logger.Information("Trying to create session {sessionName}...", sessionName); using (new PerfMarker(_logger, sessionName)) { var userIdentity = connection.User.ToStackModel() ?? new UserIdentity(new AnonymousIdentityToken()); var session = await Session.Create( applicationConfiguration, configuredEndpoint, true, sessionName, _clientConfig.DefaultSessionTimeout, userIdentity, null); _logger.Information($"Session '{sessionName}' created."); _logger.Information("Loading Complex Type System...."); var complexTypeSystem = new ComplexTypeSystem(session); await complexTypeSystem.Load(); _logger.Information("Complex Type system loaded."); if (_clientConfig.KeepAliveInterval > 0) { session.KeepAliveInterval = _clientConfig.KeepAliveInterval; session.KeepAlive += Session_KeepAlive; } wrapper = new SessionWrapper { MissedKeepAlives = 0, MaxKeepAlives = _clientConfig.MaxKeepAliveCount, Session = session }; _sessions.Add(id, wrapper); } } return(wrapper?.Session); } finally { _lock.Release(); } }
/// <inheritdoc/> public Session GetOrCreateSession(ConnectionModel connection, bool createIfNotExists) { // Find session and if not exists create var id = new ConnectionIdentifier(connection); _lock.Wait(); try { // try to get an existing session if (!_sessions.TryGetValue(id, out var wrapper)) { if (!createIfNotExists) { return(null); } wrapper = new SessionWrapper() { Id = id.ToString(), MissedKeepAlives = 0, // TODO this seem not to be the appropriate configuration argument MaxKeepAlives = (int)_clientConfig.MaxKeepAliveCount, State = SessionState.Init, Session = null, ReportedStatus = StatusCodes.Good, IdleCount = 0 }; _sessions.AddOrUpdate(id, wrapper); TriggerKeepAlive(); } switch (wrapper.State) { case SessionState.Running: case SessionState.Refresh: return(wrapper.Session); case SessionState.Retry: case SessionState.Init: case SessionState.Failed: case SessionState.Disconnect: break; default: throw new InvalidOperationException($"Illegal SessionState ({wrapper.State})"); } } catch (Exception ex) { _logger.Error(ex, "Failed to get/create session for Id '{id}'", id); } finally { _lock.Release(); } return(null); }
/// <inheritdoc/> public int GetNumberOfConnectionRetries(ConnectionModel connection) { var key = new ConnectionIdentifier(connection); _lock.Wait(); try { if (!_sessions.TryGetValue(key, out var wrapper)) { return(0); } return(wrapper.NumberOfConnectRetries); } finally { _lock.Release(); } }
/// <inheritdoc/> public bool IsConnectionOk(ConnectionModel connection) { var key = new ConnectionIdentifier(connection); _lock.Wait(); try { if (!_sessions.TryGetValue(key, out var wrapper)) { return(false); } return(wrapper.State == SessionState.Running); } finally { _lock.Release(); } }
/// <inheritdoc/> public async Task <Session> GetOrCreateSessionAsync(ConnectionModel connection, bool createIfNotExists) { // Find session and if not exists create var id = new ConnectionIdentifier(connection); await _lock.WaitAsync(); try { if (!_sessions.TryGetValue(id, out var wrapper) && createIfNotExists) { var endpointUrlCandidates = id.Connection.Endpoint.Url.YieldReturn(); if (id.Connection.Endpoint.AlternativeUrls != null) { endpointUrlCandidates = endpointUrlCandidates.Concat( id.Connection.Endpoint.AlternativeUrls); } var exceptions = new List <Exception>(); foreach (var endpointUrl in endpointUrlCandidates) { try { wrapper = await CreateSessionAsync(endpointUrl, id); if (wrapper?.Session != null) { _logger.Information("Connected on {endpointUrl}", endpointUrl); _sessions.Add(id, wrapper); return(wrapper?.Session); } } catch (Exception ex) { _logger.Debug("Failed to connect on {endpointUrl}: {message} - try again...", endpointUrl, ex.Message); exceptions.Add(ex); } } throw new AggregateException(exceptions); } return(wrapper?.Session); } catch (Exception ex) { _logger.Error(ex, "Failed to get or create session."); return(null); } finally { _lock.Release(); } }
/// <summary> /// Create handle /// </summary> /// <param name="outer"></param> /// <param name="connection"></param> /// <param name="callback"></param> public CallbackHandle(ClientServices outer, ConnectionModel connection, Func <EndpointConnectivityState, Task> callback) { Callback = callback; _outer = outer; _connection = new ConnectionIdentifier(connection); _outer._callbacks.AddOrUpdate(_connection, new HashSet <CallbackHandle> { this }, (id, list) => { lock (list) { list.Add(this); return(list); } }); }
public PerConnectionMonitor(string connection) { Connection = new ConnectionIdentifier(connection); _keepAliveHandler.OnPingSample += KeepAliveHandlerOnOnPingSample; _ipcHandler.OnPingSample += IpcHandlerOnOnPingSample; _keepAliveHandler.OnClientRecv += _pingOpCodeDetector.ClientRecv; _keepAliveHandler.OnClientSent += _pingOpCodeDetector.ClientSent; _ipcHandler.OnClientRecv += _pingOpCodeDetector.ClientRecv; _ipcHandler.OnClientSent += _pingOpCodeDetector.ClientSent; _pingOpCodeDetector.OnPingOpCodeDetected += opCode => { _ipcHandler.PingOpCode = opCode; OnPingOpCodeDetected?.Invoke(opCode); }; }
/// <summary> /// Handle inactive /// </summary> /// <param name="id"></param> /// <returns></returns> private void EvictIfInactive(ConnectionIdentifier id) { _lock.Wait(); try { if (_clients.TryGetValue(id, out var item)) { if (item.Inactive && _clients.Remove(id)) { item.Dispose(); _logger.Debug("Evicted inactive session from session cache."); } } } finally { _lock.Release(); } }
/// <summary> /// Notify about session/endpoint state changes /// </summary> /// <param name="connection"></param> /// <param name="state"></param> /// <returns></returns> private Task NotifyStateChangeAsync(ConnectionModel connection, EndpointConnectivityState state) { var id = new ConnectionIdentifier(connection); if (_callbacks.TryGetValue(id, out var list)) { lock (list) { if (list.Count > 0) { return(Task.WhenAll(list.Select(cb => cb.Callback.Invoke(state))) .ContinueWith(_ => Task.CompletedTask)); } } } return(Task.CompletedTask); }
/// <summary> /// Create session /// </summary> /// <param name="id"></param> /// <returns></returns> private IClientSession GetOrCreateSession(ConnectionIdentifier id) { _lock.Wait(); try { if (!_clients.TryGetValue(id, out var session)) { session = ClientSession.Create( _appConfig, id.Connection, _logger, NotifyStateChangeAsync, _maxOpTimeout); _clients.Add(id, session); _logger.Debug("Add new session to session cache."); } return(session); } finally { _lock.Release(); } }
/// <inheritdoc/> public async Task RemoveSessionAsync(ConnectionModel connection, bool onlyIfEmpty = true) { var key = new ConnectionIdentifier(connection); Session session = null; await _lock.WaitAsync(); try { if (!_sessions.TryGetValue(key, out var wrapper)) { return; } session = wrapper.Session; if (onlyIfEmpty && session != null && session.SubscriptionCount > 0) { return; } _sessions.Remove(key); } finally { _lock.Release(); } try { if (session != null) { // Remove subscriptions if (session.SubscriptionCount > 0) { foreach (var subscription in session.Subscriptions) { Try.Op(() => subscription.DeleteItems()); } Try.Op(() => session.RemoveSubscriptions(session.Subscriptions)); } Try.Op(session.Close); Try.Op(session.Dispose); } } catch (Exception ex) { _logger.Error(ex, "Session '{name}' removal failure.", connection); } }
/// <inheritdoc/> public async Task RemoveSessionAsync(ConnectionModel connection, bool onlyIfEmpty = true) { var key = new ConnectionIdentifier(connection); await _lock.WaitAsync().ConfigureAwait(false); try { if (!_sessions.TryGetValue(key, out var wrapper)) { return; } if (onlyIfEmpty && wrapper._subscriptions.Count == 0) { wrapper.State = SessionState.Disconnect; TriggerKeepAlive(); } } finally { _lock.Release(); } }
/// <inheritdoc/> public void UnregisterSubscription(ISubscription subscription) { var id = new ConnectionIdentifier(subscription.Connection); _lock.Wait(); try { if (!_sessions.TryGetValue(id, out var wrapper)) { return; } if (wrapper._subscriptions.TryRemove(subscription.Id, out _)) { _logger.Information("Subscription '{subscriptionId}' unregistered from session '{sessionId}' in state {state}", subscription.Id, id, wrapper.State); } } finally { _lock.Release(); } }
/// <summary> /// Handles the refresh state of a session /// </summary> /// <param name="id"></param> /// <param name="wrapper"></param> /// <param name="ct"></param> /// <returns></returns> private async Task HandleRefreshAsync(ConnectionIdentifier id, SessionWrapper wrapper, CancellationToken ct) { try { _logger.Debug("Refreshing session '{id}'", id); if (wrapper.Session != null) { if (StatusCode.IsGood(wrapper.ReportedStatus)) { if (wrapper.Session.Connected && !wrapper.Session.KeepAliveStopped) { // Fetch namespaces in case, there was some new Uris added to the server wrapper.Session.FetchNamespaceTables(); foreach (var subscription in wrapper._subscriptions.Values) { if (!ct.IsCancellationRequested) { await subscription.ActivateAsync(wrapper.Session).ConfigureAwait(false); } } _logger.Debug("Refreshing done for session '{id}'", id); wrapper.State = SessionState.Running; return; } wrapper.ReportedStatus = StatusCodes.BadNoCommunication; } wrapper.State = SessionState.Retry; await HandleRetryAsync(id, wrapper, ct).ConfigureAwait(false); } else { wrapper.State = SessionState.Failed; await HandleInitAsync(id, wrapper, ct).ConfigureAwait(false); } } catch (Exception e) { _logger.Error(e, "Failed to refresh session '{id}'", id); } }
/// <summary> /// Handle retry state of a session /// </summary> /// <param name="id"></param> /// <param name="wrapper"></param> /// <param name="ct"></param> /// <returns></returns> private async Task HandleRetryAsync(ConnectionIdentifier id, SessionWrapper wrapper, CancellationToken ct) { try { if (!wrapper._subscriptions.Any()) { if (wrapper.IdleCount < wrapper.MaxKeepAlives) { wrapper.IdleCount++; } else { _logger.Information("Session '{id}' set to disconnect in {state}", id, wrapper.State); wrapper.State = SessionState.Disconnect; await HandleDisconnectAsync(id, wrapper).ConfigureAwait(false); return; } } else { wrapper.IdleCount = 0; } wrapper.MissedKeepAlives++; _logger.Information("Session '{id}' missed {keepAlives} Keepalive(s) due to {status}, " + "waiting to reconnect...", id, wrapper.MissedKeepAlives, wrapper.ReportedStatus); if (!ct.IsCancellationRequested) { wrapper.Session.Reconnect(); wrapper.ReportedStatus = StatusCodes.Good; wrapper.State = SessionState.Running; wrapper.MissedKeepAlives = 0; // reactivate all subscriptions foreach (var subscription in wrapper._subscriptions.Values) { if (!ct.IsCancellationRequested) { await subscription.ActivateAsync(wrapper.Session).ConfigureAwait(false); } } } return; } catch (Exception e) { wrapper.NumberOfConnectRetries++; if (e is ServiceResultException sre) { switch (sre.StatusCode) { case StatusCodes.BadNotConnected: case StatusCodes.BadNoCommunication: case StatusCodes.BadSessionNotActivated: case StatusCodes.BadServerHalted: case StatusCodes.BadServerNotConnected: _logger.Warning("Failed to reconnect session '{id}', " + "will retry later", id); if (wrapper.MissedKeepAlives < wrapper.MaxKeepAlives) { // retry later return; } break; default: break; } } _logger.Warning("Failed to reconnect session '{id}' due to {exception}, " + " disposing and trying to create new", id, e.Message); } // cleanup the session if (wrapper.Session.SubscriptionCount > 0) { foreach (var subscription in wrapper.Session.Subscriptions) { Try.Op(() => subscription.DeleteItems()); Try.Op(() => subscription.Delete(true)); } Try.Op(() => wrapper.Session.RemoveSubscriptions(wrapper.Session.Subscriptions)); } Try.Op(wrapper.Session.Close); Try.Op(wrapper.Session.Dispose); wrapper.Session = null; wrapper.MissedKeepAlives = 0; wrapper.ReportedStatus = StatusCodes.Good; wrapper.State = SessionState.Failed; await HandleInitAsync(id, wrapper, ct).ConfigureAwait(false); }
/// <inheritdoc/> public async Task <Session> GetOrCreateSessionAsync(ConnectionModel connection, bool createIfNotExists) { // Find session and if not exists create var id = new ConnectionIdentifier(connection); await _lock.WaitAsync(); try { if (!_sessions.TryGetValue(id, out var wrapper) && createIfNotExists) { var sessionName = id.ToString(); var applicationConfiguration = _clientConfig.ToApplicationConfiguration(true); var endpointConfiguration = _clientConfig.ToEndpointConfiguration(); var endpointDescription = SelectEndpoint(id.Connection.Endpoint.Url, id.Connection.Endpoint.SecurityMode, id.Connection.Endpoint.SecurityPolicy, (int)(connection.Endpoint.OperationTimeout.HasValue ? connection.Endpoint.OperationTimeout.Value.TotalMilliseconds : defaultOperationTimeout)); if (endpointDescription == null) { throw new EndpointNotAvailableException(id.Connection.Endpoint.Url, id.Connection.Endpoint.SecurityMode, id.Connection.Endpoint.SecurityPolicy); } if (id.Connection.Endpoint.SecurityMode.HasValue && id.Connection.Endpoint.SecurityMode != SecurityMode.None && endpointDescription.SecurityMode == MessageSecurityMode.None) { _logger.Warning("Although the use of security was configured, there was no security-enabled endpoint available at url {endpointUrl}. An endpoint with no security will be used.", id.Connection.Endpoint.Url); } var configuredEndpoint = new ConfiguredEndpoint( null, endpointDescription, endpointConfiguration); _logger.Information("Trying to create session {sessionName}...", sessionName); using (new PerfMarker(_logger, sessionName)) { var userIdentity = connection.User.ToStackModel() ?? new UserIdentity(new AnonymousIdentityToken()); var session = await Session.Create( applicationConfiguration, configuredEndpoint, true, sessionName, _clientConfig.DefaultSessionTimeout, userIdentity, null); _logger.Information($"Session '{sessionName}' created."); _logger.Information("Loading Complex Type System...."); try { var complexTypeSystem = new ComplexTypeSystem(session); await complexTypeSystem.Load(); _logger.Information("Complex Type system loaded."); } catch (Exception ex) { _logger.Error(ex, "Failed to load Complex Type System"); } if (_clientConfig.KeepAliveInterval > 0) { session.KeepAliveInterval = _clientConfig.KeepAliveInterval; session.KeepAlive += Session_KeepAlive; } wrapper = new SessionWrapper { MissedKeepAlives = 0, MaxKeepAlives = _clientConfig.MaxKeepAliveCount, Session = session }; _sessions.Add(id, wrapper); } } return(wrapper?.Session); } finally { _lock.Release(); } }
public ConnectionContext(Win32PInvoke_iphlpapi.MIB_TCPROW_OWNER_PID connection) { Connection = connection; ConnectionID = new ConnectionIdentifier(Connection.LocalAddress + ":" + Connection.LocalPort + "=>" + Connection.RemoteAddress + ":" + Connection.RemotePort); }
/// <summary> /// Create session against endpoint /// </summary> /// <param name="endpointUrl"></param> /// <param name="id"></param> /// <returns></returns> private async Task <Session> CreateSessionAsync(string endpointUrl, ConnectionIdentifier id) { var sessionName = $"Azure IIoT Publisher - {id}"; // Validate certificates void OnValidate(CertificateValidator sender, CertificateValidationEventArgs e) { if (!e.Accept && e.Error.StatusCode == StatusCodes.BadCertificateUntrusted) { // Validate thumbprint provided if (e.Certificate.RawData != null && id.Connection.Endpoint.Certificate != null && e.Certificate.Thumbprint == id.Connection.Endpoint.Certificate) { // Validate e.Accept = true; } else if (_clientConfig.AutoAcceptUntrustedCertificates) { _logger.Warning("Publisher is configured to accept untrusted certs. " + "Accepting untrusted certificate on endpoint {endpointUrl}", endpointUrl); e.Accept = true; } } }; var applicationConfiguration = await _clientConfig. ToApplicationConfigurationAsync(_identity, true, OnValidate); var endpointConfiguration = _clientConfig.ToEndpointConfiguration(); var endpointDescription = SelectEndpoint(endpointUrl, id.Connection.Endpoint.SecurityMode, id.Connection.Endpoint.SecurityPolicy, (int)(id.Connection.OperationTimeout.HasValue ? id.Connection.OperationTimeout.Value.TotalMilliseconds : kDefaultOperationTimeout)); if (endpointDescription == null) { throw new EndpointNotAvailableException(endpointUrl, id.Connection.Endpoint.SecurityMode, id.Connection.Endpoint.SecurityPolicy); } if (id.Connection.Endpoint.SecurityMode.HasValue && id.Connection.Endpoint.SecurityMode != SecurityMode.None && endpointDescription.SecurityMode == MessageSecurityMode.None) { _logger.Warning("Although the use of security was configured, " + "there was no security-enabled endpoint available at url " + "{endpointUrl}. An endpoint with no security will be used.", endpointUrl); } var configuredEndpoint = new ConfiguredEndpoint( null, endpointDescription, endpointConfiguration); _logger.Information("Trying to create session {sessionName}...", sessionName); using (new PerfMarker(_logger, sessionName)) { var userIdentity = id.Connection.User.ToStackModel() ?? new UserIdentity(new AnonymousIdentityToken()); var session = await Session.Create( applicationConfiguration, configuredEndpoint, true, sessionName, _clientConfig.DefaultSessionTimeout, userIdentity, null); if (sessionName != session.SessionName) { _logger.Warning("Session '{sessionName}' created with a revised name '{name}'", sessionName, session.SessionName); } _logger.Information("Session '{sessionName}' created.", sessionName); _logger.Information("Loading Complex Type System...."); try { var complexTypeSystem = new ComplexTypeSystem(session); await complexTypeSystem.Load(); _logger.Information("Complex Type system loaded."); } catch (Exception ex) { _logger.Error(ex, "Failed to load Complex Type System"); } // TODO - what happens when KeepAliveInterval is 0??? if (_clientConfig.KeepAliveInterval > 0) { session.KeepAliveInterval = _clientConfig.KeepAliveInterval; session.KeepAlive += Session_KeepAlive; session.Notification += Session_Notification; } return(session); } }
/// <summary> /// Create session against endpoint /// </summary> /// <param name="endpointUrl"></param> /// <param name="id"></param> /// <param name="wrapper"></param> /// <returns></returns> private async Task <Session> CreateSessionAsync(string endpointUrl, ConnectionIdentifier id, SessionWrapper wrapper) { var sessionName = $"Azure IIoT {id}"; var endpointDescription = SelectEndpoint(endpointUrl, id.Connection.Endpoint.SecurityMode, id.Connection.Endpoint.SecurityPolicy, (int)(id.Connection.OperationTimeout.HasValue ? id.Connection.OperationTimeout.Value.TotalMilliseconds : kDefaultOperationTimeout)); if (endpointDescription == null) { throw new EndpointNotAvailableException(endpointUrl, id.Connection.Endpoint.SecurityMode, id.Connection.Endpoint.SecurityPolicy); } if (id.Connection.Endpoint.SecurityMode.HasValue && id.Connection.Endpoint.SecurityMode != SecurityMode.None && endpointDescription.SecurityMode == MessageSecurityMode.None) { _logger.Warning("Although the use of security was configured, " + "there was no security-enabled endpoint available at url " + "{endpointUrl}. An endpoint with no security will be used.", endpointUrl); } var configuredEndpoint = new ConfiguredEndpoint( null, endpointDescription, _endpointConfiguration); _logger.Information("Creating session '{id}' for endpoint '{endpointUrl}'...", id, endpointUrl); using (new PerfMarker(_logger, sessionName)) { var userIdentity = id.Connection.User.ToStackModel() ?? new UserIdentity(new AnonymousIdentityToken()); var session = await Session.Create( _applicationConfiguration, configuredEndpoint, true, sessionName, _clientConfig.DefaultSessionTimeout, userIdentity, null).ConfigureAwait(false); session.Handle = wrapper; wrapper.Session = session; session.KeepAliveInterval = _clientConfig.KeepAliveInterval > 0 ? _clientConfig.KeepAliveInterval : kDefaultOperationTimeout; session.KeepAlive += Session_KeepAlive; session.Notification += Session_Notification; // TODO - store the created session id (node id)? if (sessionName != session.SessionName) { _logger.Warning("Session '{id}' created with a revised name '{name}'", id, session.SessionName); } _logger.Information("Session '{id}' created, loading complex type system ... ", id); try { var complexTypeSystem = new ComplexTypeSystem(session); await complexTypeSystem.Load().ConfigureAwait(false); _logger.Information("Session '{id}' complex type system loaded", id); } catch (Exception ex) { _logger.Error(ex, "Failed to load complex type system for session '{id}'", id); } return(session); } }
/// <inheritdoc/> public async Task <Session> GetOrCreateSessionAsync(ConnectionModel connection, bool createIfNotExists, uint statusCode = StatusCodes.Good) { // Find session and if not exists create var id = new ConnectionIdentifier(connection); SessionWrapper wrapper = null; await _lock.WaitAsync(); try { // try to get an existing session try { if (!_sessions.TryGetValue(id, out wrapper)) { if (!createIfNotExists) { return(null); } wrapper = new SessionWrapper() { MissedKeepAlives = 0, MaxKeepAlives = _clientConfig.MaxKeepAliveCount, State = SessionState.Init, Session = null, IdleCount = 0 }; _sessions.Add(id, wrapper); } switch (wrapper.State) { case SessionState.Reconnecting: case SessionState.Connecting: // nothing to do the consumer will either retry or handle the issue return(null); case SessionState.Running: if (StatusCode.IsGood(statusCode)) { return(wrapper.Session); } wrapper.State = SessionState.Reconnecting; break; case SessionState.Retry: wrapper.State = SessionState.Reconnecting; break; case SessionState.Init: case SessionState.Failed: wrapper.State = SessionState.Connecting; break; default: throw new InvalidOperationException($"Illegal SessionState ({wrapper.State})"); } } catch (Exception ex) { _logger.Error(ex, "Failed to get/create as session for Id {id}.", id); throw; } finally { _lock.Release(); } while (true) { switch (wrapper.State) { case SessionState.Reconnecting: // attempt to reactivate try { wrapper.MissedKeepAlives++; _logger.Information("Session '{name}' missed {keepAlives} keep alive(s) due to {status}." + " Awaiting for reconnect...", wrapper.Session.SessionName, wrapper.MissedKeepAlives, new StatusCode(statusCode)); wrapper.Session.Reconnect(); wrapper.State = SessionState.Running; wrapper.MissedKeepAlives = 0; return(wrapper.Session); } catch (Exception e) { if (e is ServiceResultException sre) { switch (sre.StatusCode) { case StatusCodes.BadNotConnected: case StatusCodes.BadNoCommunication: case StatusCodes.BadSessionNotActivated: case StatusCodes.BadServerHalted: case StatusCodes.BadServerNotConnected: _logger.Warning("Failed to reconnect session {sessionName}." + " Retry reconnection later.", wrapper.Session.SessionName); wrapper.State = SessionState.Retry; if (wrapper.MissedKeepAlives < wrapper.MaxKeepAlives) { return(null); } break; default: break; } } // cleanup the session _logger.Warning("Failed to reconnect session {sessionName} due to {exception}." + " Disposing and trying create new.", wrapper.Session.SessionName, e.Message); if (wrapper.Session.SubscriptionCount > 0) { foreach (var subscription in wrapper.Session.Subscriptions) { Try.Op(() => subscription.DeleteItems()); Try.Op(() => subscription.Delete(true)); } Try.Op(() => wrapper.Session.RemoveSubscriptions(wrapper.Session.Subscriptions)); } Try.Op(wrapper.Session.Close); Try.Op(wrapper.Session.Dispose); wrapper.Session = null; wrapper.MissedKeepAlives = 0; wrapper.State = SessionState.Connecting; } break; case SessionState.Connecting: if (wrapper.Session != null) { _logger.Warning("Session {sessionName} still attached to wrapper in {state}", wrapper.Session.SessionName, wrapper.State); Try.Op(wrapper.Session.Dispose); wrapper.Session = null; } var endpointUrlCandidates = id.Connection.Endpoint.Url.YieldReturn(); if (id.Connection.Endpoint.AlternativeUrls != null) { endpointUrlCandidates = endpointUrlCandidates.Concat( id.Connection.Endpoint.AlternativeUrls); } var exceptions = new List <Exception>(); foreach (var endpointUrl in endpointUrlCandidates) { try { var session = await CreateSessionAsync(endpointUrl, id); if (session != null) { _logger.Information("Connected on {endpointUrl}", endpointUrl); wrapper.Session = session; wrapper.State = SessionState.Running; return(wrapper.Session); } } catch (Exception ex) { _logger.Debug("Failed to connect on {endpointUrl}: {message} - try again...", endpointUrl, ex.Message); exceptions.Add(ex); } } throw new AggregateException(exceptions); default: throw new InvalidOperationException($"Invalid SessionState ({wrapper.State}) not handled."); } } } catch (ServiceResultException sre) { _logger.Warning("Failed to get or create session {id} due to {exception}.", id, sre.StatusCode.ToString()); } catch (AggregateException aex) { _logger.Warning("Failed to get or create session {id} due to {exception}.", id, aex.Message); } catch (Exception ex) { _logger.Error(ex, "Failed to get or create session."); } wrapper.State = SessionState.Failed; return(null); }
/// <summary> /// Handles the initialization state of the session /// </summary> /// <param name="id"></param> /// <param name="wrapper"></param> /// <param name="ct"></param> /// <returns></returns> private async Task HandleInitAsync(ConnectionIdentifier id, SessionWrapper wrapper, CancellationToken ct) { try { if (wrapper.Session != null) { _logger.Warning("Session '{id}' still attached to wrapper in {state}", id, wrapper.State); Try.Op(wrapper.Session.Dispose); wrapper.Session = null; } _logger.Debug("Initializing session '{id}'...", id); var endpointUrlCandidates = id.Connection.Endpoint.Url.YieldReturn(); if (id.Connection.Endpoint.AlternativeUrls != null) { endpointUrlCandidates = endpointUrlCandidates.Concat( id.Connection.Endpoint.AlternativeUrls); } var exceptions = new List <Exception>(); foreach (var endpointUrl in endpointUrlCandidates) { try { if (!ct.IsCancellationRequested) { var session = await CreateSessionAsync(endpointUrl, id, wrapper).ConfigureAwait(false); if (session != null) { _logger.Information("Connected to '{endpointUrl}'", endpointUrl); session.Handle = wrapper; wrapper.Session = session; foreach (var subscription in wrapper._subscriptions.Values) { await subscription.EnableAsync(wrapper.Session).ConfigureAwait(false); } foreach (var subscription in wrapper._subscriptions.Values) { await subscription.ActivateAsync(wrapper.Session).ConfigureAwait(false); } wrapper.State = SessionState.Running; _logger.Debug("Session '{id}' successfully initialized", id); return; } } } catch (Exception ex) { _logger.Debug("Failed to connect to {endpointUrl}: {message} - try again...", endpointUrl, ex.Message); exceptions.Add(ex); } } throw new AggregateException(exceptions); } catch (ServiceResultException sre) { _logger.Warning("Failed to create session '{id}' due to {exception}", id, sre.StatusCode.ToString()); } catch (AggregateException aex) { _logger.Warning("Failed to create session '{id}' due to {exception}", id, aex.Message); } catch (Exception ex) { _logger.Error(ex, "Failed to create session '{id}'", id); } wrapper.NumberOfConnectRetries++; wrapper.State = SessionState.Failed; }