/// <summary> /// Closes all outbound connections related to the current live agent client /// </summary> private void CloseClient(LiveSessionPublisher deadClient) { if (deadClient == null) { return; } //we are going to release every connection related to this client. They might not all be - some might be local. var deadClientOptions = deadClient.GetOptions(); if (deadClientOptions != null) { lock (m_DeadClients) { //move everything to the dead collection and then we unregister that guy (this is what the iterators expect) lock (m_ActiveClients) { foreach (var activeClient in m_ActiveClients) { if ((activeClient.GetOptions().Equals(deadClientOptions)) && (m_DeadClients.Contains(activeClient) == false)) { m_DeadClients.Add(activeClient); } } } lock (m_PendingClients) { foreach (var pendingClient in m_PendingClients) { if ((pendingClient.GetOptions().Equals(deadClientOptions)) && (m_DeadClients.Contains(pendingClient) == false)) { m_DeadClients.Add(pendingClient); } } m_PendingClients.Clear(); } } //now we can kill them all DropDeadConnections(); } deadClient.Dispose(); }
private void DiscoveryFileMonitorOnFileChanged(object sender, LocalServerDiscoveryFileEventArgs e) { LiveSessionPublisher target; //this event *should* mean that we have a new proxy to connect to... lock (m_LocalProxyConnections) { if (!m_LocalProxyConnections.TryGetValue(e.File.FileNamePath, out target)) { if (e.File.IsAlive) { target = new LiveSessionPublisher(this, e.File); target.Start(); m_LocalProxyConnections.Add(e.File.FileNamePath, target); } } } }
/// <summary> /// Send the latest summary to the specified publisher /// </summary> private void SendSummary(LiveSessionPublisher publisher) { try { if (publisher.IsConnected) { publisher.SendSummary(); } } catch (Exception ex) { if (!Log.SilentMode) { Log.Write(LogMessageSeverity.Error, LogWriteMode.Queued, ex, LogCategory, "Failed to send summary to the server or other network proxy", "An exception was thrown that prevents us from sending the latest session summary to the server:\r\n" + "Server or Proxy: {0}\r\n{1} Exception:\r\n{2}", publisher, ex.GetType(), ex.Message); } } }
/// <summary> /// Send the latest summary to the server /// </summary> public void SendSummary() { //We don't want to use locks so we need to do a point-in-time copy and then iterate through that copy. var client = m_Client; if (client != null) { SendSummary(client); } LiveSessionPublisher[] registeredClients; lock (m_LocalProxyConnections) { registeredClients = new LiveSessionPublisher[m_LocalProxyConnections.Count]; m_LocalProxyConnections.Values.CopyTo(registeredClients, 0); } foreach (var liveSessionPublisher in registeredClients) { SendSummary(liveSessionPublisher); } }
/// <summary> /// Make sure we have an outbound proxy connection. /// </summary> /// <remarks>Intended for asynchronous execution from the thread pool.</remarks> private async Task AsyncEnsureRemoteConnection() { try { try { DateTimeOffset hubConfigurationExpiration; lock (m_HubConnection) { hubConfigurationExpiration = m_HubConfigurationExpiration; } NetworkConnectionOptions newLiveStreamOptions = null; if ((m_HubConnection.IsConnected == false) || (hubConfigurationExpiration < DateTimeOffset.Now)) { await m_HubConnection.Reconnect().ConfigureAwait(false); DateTimeOffset connectionAttemptTime = DateTimeOffset.Now; var status = await m_HubConnection.GetStatus().ConfigureAwait(false); if (status.Status == HubStatus.Expired) { //if it's expired we're not going to check again for a while. if (status.Repository?.ExpirationDt == null) { //should never happen, but treat as our long term case. hubConfigurationExpiration = connectionAttemptTime.AddDays(1); } else { TimeSpan expiredTimeframe = connectionAttemptTime - status.Repository.ExpirationDt.Value; if (expiredTimeframe.TotalHours < 24) { hubConfigurationExpiration = connectionAttemptTime.AddMinutes(15); //we'll check pretty fast for that first day. } else if (expiredTimeframe.TotalDays < 4) { hubConfigurationExpiration = connectionAttemptTime.AddHours(6); } else { hubConfigurationExpiration = connectionAttemptTime.AddDays(1); } } if (!Log.SilentMode) { Log.Write(LogMessageSeverity.Verbose, LogCategory, "Loupe server status is expired so no remote live view possible.", "Will check the server configuration again at {0}", hubConfigurationExpiration); } } else { //we always want to periodically recheck the configuration in case it has changed anyway. //we want to coordinate to an exact hour point to provide some consistency to worried users wondering when things will reconnect. hubConfigurationExpiration = connectionAttemptTime.AddMinutes(60 - connectionAttemptTime.Minute); //so we go to a flush hour. newLiveStreamOptions = status.Repository?.AgentLiveStreamOptions; if ((newLiveStreamOptions == null) && (!Log.SilentMode)) { Log.Write(LogMessageSeverity.Information, LogCategory, "Remote live view not available due to server configuration", "The server is configured to have live view disabled so even though we have it enabled there is no live view."); } } lock (m_HubConnection) { m_HubConfigurationExpiration = hubConfigurationExpiration; } //if we got different options then we're going to have to drop & recreate the client. if (newLiveStreamOptions != m_ConnectionOptions) { if (!Log.SilentMode) { Log.Write(LogMessageSeverity.Verbose, LogCategory, "Loupe server live view options are different than our running configuration so we will close the client.", "New configuration:\r\n{0}", newLiveStreamOptions); } m_ConnectionOptions = newLiveStreamOptions; CloseClient(m_Client); m_Client = null; } } } catch (Exception ex) { if (!Log.SilentMode) { Log.Write(LogMessageSeverity.Warning, LogWriteMode.Queued, ex, LogCategory, "Remote viewer connection attempt failed", "While attempting to open our outbound connection to the proxy server an exception was thrown. We will retry again later.\r\nException: {0}", ex.Message); } } try { if ((m_Client == null) && (m_ConnectionOptions != null)) { LiveSessionPublisher newClient = new LiveSessionPublisher(this, m_ConnectionOptions); newClient.Start(); m_Client = newClient; SendSummary(m_Client); //since we just connected, we want to immediately tell it about us. } } catch (Exception ex) { if (!Log.SilentMode) { Log.Write(LogMessageSeverity.Warning, LogWriteMode.Queued, ex, LogCategory, "Remote viewer connection attempt failed", "While attempting to open our outbound connection to the proxy server an exception was thrown. We will retry again later.\r\nException: {0}", ex.Message); } } } finally { m_ActiveRemoteConnectionAttempt = false; } }
/// <summary> /// Inheritors should override this method to implement custom Close functionality /// </summary> /// <remarks>Code in this method is protected by a Queue Lock. /// This method is called with the Message Dispatch thread exclusively.</remarks> protected override void OnClose() { if (m_IsClosed) { return; } if (m_DiscoveryFileMonitor != null) { try { m_DiscoveryFileMonitor.FileChanged -= DiscoveryFileMonitorOnFileChanged; m_DiscoveryFileMonitor.FileDeleted -= DiscoveryFileMonitorOnFileDeleted; m_DiscoveryFileMonitor.Stop(); } catch { } } //move everything to the dead collection and then we unregister that guy (this is what the iterators expect) lock (m_DeadClients) { lock (m_ActiveClients) { foreach (NetworkWriter activeClient in m_ActiveClients) { if (m_DeadClients.Contains(activeClient) == false) { m_DeadClients.Add(activeClient); } } m_ActiveClients.Clear(); m_Buffer.Clear(); } lock (m_PendingClients) { foreach (NetworkWriter pendingClient in m_PendingClients) { if (m_DeadClients.Contains(pendingClient) == false) { m_DeadClients.Add(pendingClient); } } m_PendingClients.Clear(); } } //now we can kill them all DropDeadConnections(); //Now ditch our local proxies. We've already stopped the file monitor so new ones shouldn't be showing up. //Despite that we don't like holding locks if we don't have to. List <LiveSessionPublisher> registeredProxies = null; lock (m_LocalProxyConnections) { if (m_LocalProxyConnections.Count > 0) { registeredProxies = new List <LiveSessionPublisher>(m_LocalProxyConnections.Values); m_LocalProxyConnections.Clear(); } } if (registeredProxies != null) { foreach (var localProxyConnection in registeredProxies) { localProxyConnection.Close(); } } if (m_Client != null) { m_Client.Dispose(); m_Client = null; } if (m_HubConnection != null) { m_HubConnection.Dispose(); m_HubConnection = null; } m_IsClosed = true; }