private void ProcessServerResponse(byte[] buffer, int length) { // Currently this work is done on the async socket completion thread, make sure work to be done is timely and if the response processing // is coming in via the command channel and needs to send a command back to the server, it should be done on a separate thread... if (buffer != null && length > 0) { try { Dictionary<Guid, DeviceStatisticsHelper<SubscribedDevice>> subscribedDevicesLookup; DeviceStatisticsHelper<SubscribedDevice> statisticsHelper; ServerResponse responseCode = (ServerResponse)buffer[0]; ServerCommand commandCode = (ServerCommand)buffer[1]; int responseLength = BigEndian.ToInt32(buffer, 2); int responseIndex = DataPublisher.ClientResponseHeaderSize; bool solicited = false; byte[][][] keyIVs; // See if this was a solicited response to a requested server command if (responseCode.IsSolicited()) { lock (m_requests) { int index = m_requests.BinarySearch(commandCode); if (index >= 0) { solicited = true; m_requests.RemoveAt(index); } } // Disconnect any established UDP data channel upon successful unsubscribe if (solicited && commandCode == ServerCommand.Unsubscribe && responseCode == ServerResponse.Succeeded) DataChannel = null; } OnReceivedServerResponse(responseCode, commandCode); switch (responseCode) { case ServerResponse.Succeeded: if (solicited) { switch (commandCode) { case ServerCommand.Authenticate: OnStatusMessage("Success code received in response to server command \"{0}\": {1}", commandCode, InterpretResponseMessage(buffer, responseIndex, responseLength)); m_authenticated = true; OnConnectionAuthenticated(); break; case ServerCommand.Subscribe: OnStatusMessage("Success code received in response to server command \"{0}\": {1}", commandCode, InterpretResponseMessage(buffer, responseIndex, responseLength)); m_subscribed = true; break; case ServerCommand.Unsubscribe: OnStatusMessage("Success code received in response to server command \"{0}\": {1}", commandCode, InterpretResponseMessage(buffer, responseIndex, responseLength)); m_subscribed = false; if ((object)m_dataStreamMonitor != null) m_dataStreamMonitor.Enabled = false; break; case ServerCommand.RotateCipherKeys: OnStatusMessage("Success code received in response to server command \"{0}\": {1}", commandCode, InterpretResponseMessage(buffer, responseIndex, responseLength)); break; case ServerCommand.MetaDataRefresh: OnStatusMessage("Success code received in response to server command \"{0}\": latest meta-data received.", commandCode); OnMetaDataReceived(DeserializeMetadata(buffer.BlockCopy(responseIndex, responseLength))); m_metadataRefreshPending = false; break; } } else { switch (commandCode) { case ServerCommand.MetaDataRefresh: // Meta-data refresh may be unsolicited OnStatusMessage("Received server confirmation for unsolicited request to \"{0}\" command: latest meta-data received.", commandCode); OnMetaDataReceived(DeserializeMetadata(buffer.BlockCopy(responseIndex, responseLength))); m_metadataRefreshPending = false; break; case ServerCommand.RotateCipherKeys: // Key rotation may be unsolicited OnStatusMessage("Received server confirmation for unsolicited request to \"{0}\" command: {1}", commandCode, InterpretResponseMessage(buffer, responseIndex, responseLength)); break; case ServerCommand.Subscribe: OnStatusMessage("Received unsolicited response to \"{0}\" command: {1}", commandCode, InterpretResponseMessage(buffer, responseIndex, responseLength)); break; default: OnProcessException(new InvalidOperationException("Publisher sent a success code for an unsolicited server command: " + commandCode)); break; } } break; case ServerResponse.Failed: if (solicited) OnStatusMessage("Failure code received in response to server command \"{0}\": {1}", commandCode, InterpretResponseMessage(buffer, responseIndex, responseLength)); else OnProcessException(new InvalidOperationException("Publisher sent a failed code for an unsolicited server command: " + commandCode)); if (commandCode == ServerCommand.MetaDataRefresh) m_metadataRefreshPending = false; break; case ServerResponse.DataPacket: long now = DateTime.UtcNow.Ticks; // Deserialize data packet List<IMeasurement> measurements = new List<IMeasurement>(); DataPacketFlags flags; Ticks timestamp = 0; int count; if (m_totalBytesReceived == 0) { // At the point when data is being received, data monitor should be enabled if ((object)m_dataStreamMonitor != null && !m_dataStreamMonitor.Enabled) m_dataStreamMonitor.Enabled = true; // Establish run-time log for subscriber if (m_autoConnect || m_dataGapRecoveryEnabled) { if ((object)m_runTimeLog == null) { m_runTimeLog = new RunTimeLog(); m_runTimeLog.FileName = GetLoggingPath(Name + "_RunTimeLog.txt"); m_runTimeLog.ProcessException += m_runTimeLog_ProcessException; m_runTimeLog.Initialize(); } else { // Mark the start of any data transmissions m_runTimeLog.StartTime = DateTime.UtcNow; m_runTimeLog.Enabled = true; } } // The duration between last disconnection and start of data transmissions // represents a gap in data - if data gap recovery is enabled, we log // this as a gap for recovery: if (m_dataGapRecoveryEnabled && (object)m_dataGapRecoverer != null) m_dataGapRecoverer.LogDataGap(m_runTimeLog.StopTime, DateTime.UtcNow); } // Track total data packet bytes received from any channel m_totalBytesReceived += m_lastBytesReceived; m_monitoredBytesReceived += m_lastBytesReceived; // Get data packet flags flags = (DataPacketFlags)buffer[responseIndex]; responseIndex++; bool synchronizedMeasurements = ((byte)(flags & DataPacketFlags.Synchronized) > 0); bool compactMeasurementFormat = ((byte)(flags & DataPacketFlags.Compact) > 0); bool compressedPayload = ((byte)(flags & DataPacketFlags.Compressed) > 0); int cipherIndex = (flags & DataPacketFlags.CipherIndex) > 0 ? 1 : 0; // Decrypt data packet payload if keys are available if ((object)m_keyIVs != null) { // Get a local copy of volatile keyIVs reference since this can change at any time keyIVs = m_keyIVs; // Decrypt payload portion of data packet buffer = Common.SymmetricAlgorithm.Decrypt(buffer, responseIndex, responseLength - 1, keyIVs[cipherIndex][0], keyIVs[cipherIndex][1]); responseIndex = 0; responseLength = buffer.Length; } // Synchronized packets contain a frame level timestamp if (synchronizedMeasurements) { timestamp = BigEndian.ToInt64(buffer, responseIndex); responseIndex += 8; } // Deserialize number of measurements that follow count = BigEndian.ToInt32(buffer, responseIndex); responseIndex += 4; if (compressedPayload) { if ((object)m_signalIndexCache == null && m_lastMissingCacheWarning + MissingCacheWarningInterval < now) { if (m_lastMissingCacheWarning != 0L) { // Warning message for missing signal index cache OnStatusMessage("WARNING: Signal index cache has not arrived. No compact measurements can be parsed."); } m_lastMissingCacheWarning = now; } else { try { if (CompressionModes.HasFlag(CompressionModes.TSSC)) { // Use TSSC compression to decompress measurements if ((object)m_decompressionBlock == null) m_decompressionBlock = new MeasurementDecompressionBlock(); MemoryStream bufferStream = new MemoryStream(buffer, responseIndex, responseLength - responseIndex + DataPublisher.ClientResponseHeaderSize); bool eos = false; while (!eos) { Measurement measurement; Tuple<Guid, string, uint> tuple; ushort id; long time; uint quality; float value; byte command; switch (m_decompressionBlock.GetMeasurement(out id, out time, out quality, out value, out command)) { case DecompressionExitCode.EndOfStreamOccured: if (bufferStream.Position != bufferStream.Length) m_decompressionBlock.Fill(bufferStream); else eos = true; break; case DecompressionExitCode.CommandRead: break; case DecompressionExitCode.MeasurementRead: // Attempt to restore signal identification if (m_signalIndexCache.Reference.TryGetValue(id, out tuple)) { measurement = new Measurement(); measurement.Key = MeasurementKey.LookUpOrCreate(tuple.Item1, tuple.Item2, tuple.Item3); measurement.Timestamp = time; measurement.StateFlags = (MeasurementStateFlags)quality; measurement.Value = value; measurements.Add(measurement); } break; } } } else { // Decompress compact measurements from payload measurements.AddRange(buffer.DecompressPayload(m_signalIndexCache, responseIndex, responseLength - responseIndex + DataPublisher.ClientResponseHeaderSize, count, m_includeTime, flags)); } } catch (Exception ex) { OnProcessException(new InvalidOperationException("WARNING: Decompression failure: " + ex.Message, ex)); } } } else { // Deserialize measurements for (int i = 0; i < count; i++) { if (!compactMeasurementFormat) { // Deserialize full measurement format SerializableMeasurement measurement = new SerializableMeasurement(m_encoding); responseIndex += measurement.ParseBinaryImage(buffer, responseIndex, responseLength - responseIndex); measurements.Add(measurement); } else if ((object)m_signalIndexCache != null) { // Deserialize compact measurement format CompactMeasurement measurement = new CompactMeasurement(m_signalIndexCache, m_includeTime, m_baseTimeOffsets, m_timeIndex, m_useMillisecondResolution); responseIndex += measurement.ParseBinaryImage(buffer, responseIndex, responseLength - responseIndex); // Apply timestamp from frame if not included in transmission if (!measurement.IncludeTime) measurement.Timestamp = timestamp; measurements.Add(measurement); } else if (m_lastMissingCacheWarning + MissingCacheWarningInterval < now) { if (m_lastMissingCacheWarning != 0L) { // Warning message for missing signal index cache OnStatusMessage("WARNING: Signal index cache has not arrived. No compact measurements can be parsed."); } m_lastMissingCacheWarning = now; } } } // Calculate statistics subscribedDevicesLookup = m_subscribedDevicesLookup; statisticsHelper = null; if ((object)subscribedDevicesLookup != null) { IEnumerable<IGrouping<DeviceStatisticsHelper<SubscribedDevice>, IMeasurement>> deviceGroups = measurements .Where(measurement => subscribedDevicesLookup.TryGetValue(measurement.ID, out statisticsHelper)) .Select(measurement => Tuple.Create(statisticsHelper, measurement)) .ToList() .GroupBy(tuple => tuple.Item1, tuple => tuple.Item2); foreach (IGrouping<DeviceStatisticsHelper<SubscribedDevice>, IMeasurement> deviceGroup in deviceGroups) { statisticsHelper = deviceGroup.Key; foreach (IGrouping<Ticks, IMeasurement> frame in deviceGroup.GroupBy(measurement => measurement.Timestamp)) { // Determine the number of measurements received with valid values int measurementsReceived = frame.Count(measurement => !double.IsNaN(measurement.Value)); IMeasurement statusFlags = null; IMeasurement frequency = null; IMeasurement deltaFrequency = null; // Attempt to update real-time if (!m_useLocalClockAsRealTime && frame.Key > m_realTime) m_realTime = frame.Key; // Search the frame for status flags, frequency, and delta frequency foreach (IMeasurement measurement in frame) { if (measurement.ID == statisticsHelper.Device.StatusFlagsID) statusFlags = measurement; else if (measurement.ID == statisticsHelper.Device.FrequencyID) frequency = measurement; else if (measurement.ID == statisticsHelper.Device.DeltaFrequencyID) deltaFrequency = measurement; } // If we are receiving status flags for this device, // count the data quality, time quality, and device errors if ((object)statusFlags != null) { uint commonStatusFlags = (uint)statusFlags.Value; if ((commonStatusFlags & (uint)Bits.Bit19) > 0) statisticsHelper.Device.DataQualityErrors++; if ((commonStatusFlags & (uint)Bits.Bit18) > 0) statisticsHelper.Device.TimeQualityErrors++; if ((commonStatusFlags & (uint)Bits.Bit16) > 0) statisticsHelper.Device.DeviceErrors++; measurementsReceived--; } // Zero is not a valid value for frequency. // If frequency is zero, invalidate both frequency and delta frequency if ((object)frequency != null && frequency.Value == 0.0D) { if ((object)deltaFrequency != null) measurementsReceived -= 2; else measurementsReceived--; } // Track the number of measurements received statisticsHelper.AddToMeasurementsReceived(measurementsReceived); } } } // Provide new measurements to local concentrator, if defined, otherwise directly expose them to the consumer if ((object)m_localConcentrator != null) m_localConcentrator.SortMeasurements(measurements); else OnNewMeasurements(measurements); // Gather statistics on received data DateTime timeReceived = RealTime; if (!m_useLocalClockAsRealTime && timeReceived.Ticks - m_lastStatisticsHelperUpdate > Ticks.PerSecond) { UpdateStatisticsHelpers(); m_lastStatisticsHelperUpdate = m_realTime; } m_lifetimeMeasurements += measurements.Count; UpdateMeasurementsPerSecond(timeReceived, measurements.Count); for (int x = 0; x < measurements.Count; x++) { long latency = timeReceived.Ticks - (long)measurements[x].Timestamp; // Throw out latencies that exceed one hour as invalid if (Math.Abs(latency) > Time.SecondsPerHour * Ticks.PerSecond) continue; if (m_lifetimeMinimumLatency > latency || m_lifetimeMinimumLatency == 0) m_lifetimeMinimumLatency = latency; if (m_lifetimeMaximumLatency < latency || m_lifetimeMaximumLatency == 0) m_lifetimeMaximumLatency = latency; m_lifetimeTotalLatency += latency; m_lifetimeLatencyMeasurements++; } break; case ServerResponse.BufferBlock: // Buffer block received - wrap as a buffer block measurement and expose back to consumer uint sequenceNumber = BigEndian.ToUInt32(buffer, responseIndex); int cacheIndex = (int)(sequenceNumber - m_expectedBufferBlockSequenceNumber); BufferBlockMeasurement bufferBlockMeasurement; Tuple<Guid, string, uint> measurementKey; ushort signalIndex; // Check if this buffer block has already been processed (e.g., mistaken retransmission due to timeout) if (cacheIndex >= 0 && (cacheIndex >= m_bufferBlockCache.Count || (object)m_bufferBlockCache[cacheIndex] == null)) { // Send confirmation that buffer block is received SendServerCommand(ServerCommand.ConfirmBufferBlock, buffer.BlockCopy(responseIndex, 4)); // Get measurement key from signal index cache signalIndex = BigEndian.ToUInt16(buffer, responseIndex + 4); if (!m_signalIndexCache.Reference.TryGetValue(signalIndex, out measurementKey)) throw new InvalidOperationException("Failed to find associated signal identification for runtime ID " + signalIndex); // Skip the sequence number and signal index when creating the buffer block measurement bufferBlockMeasurement = new BufferBlockMeasurement(buffer, responseIndex + 6, responseLength - 6) { Key = MeasurementKey.LookUpOrCreate(measurementKey.Item1, measurementKey.Item2, measurementKey.Item3) }; // Determine if this is the next buffer block in the sequence if (sequenceNumber == m_expectedBufferBlockSequenceNumber) { List<IMeasurement> bufferBlockMeasurements = new List<IMeasurement>(); int i; // Add the buffer block measurement to the list of measurements to be published bufferBlockMeasurements.Add(bufferBlockMeasurement); m_expectedBufferBlockSequenceNumber++; // Add cached buffer block measurements to the list of measurements to be published for (i = 1; i < m_bufferBlockCache.Count; i++) { if ((object)m_bufferBlockCache[i] == null) break; bufferBlockMeasurements.Add(m_bufferBlockCache[i]); m_expectedBufferBlockSequenceNumber++; } // Remove published measurements from the buffer block queue if (m_bufferBlockCache.Count > 0) m_bufferBlockCache.RemoveRange(0, i); // Publish measurements OnNewMeasurements(bufferBlockMeasurements); } else { // Ensure that the list has at least as many // elements as it needs to cache this measurement for (int i = m_bufferBlockCache.Count; i <= cacheIndex; i++) m_bufferBlockCache.Add(null); // Insert this buffer block into the proper location in the list m_bufferBlockCache[cacheIndex] = bufferBlockMeasurement; } } m_lifetimeMeasurements += 1; UpdateMeasurementsPerSecond(DateTime.UtcNow, 1); break; case ServerResponse.DataStartTime: // Raise data start time event OnDataStartTime(BigEndian.ToInt64(buffer, responseIndex)); break; case ServerResponse.ProcessingComplete: // Raise input processing completed event OnProcessingComplete(InterpretResponseMessage(buffer, responseIndex, responseLength)); break; case ServerResponse.UpdateSignalIndexCache: // Deserialize new signal index cache m_remoteSignalIndexCache = DeserializeSignalIndexCache(buffer.BlockCopy(responseIndex, responseLength)); m_signalIndexCache = new SignalIndexCache(DataSource, m_remoteSignalIndexCache); FixExpectedMeasurementCounts(); break; case ServerResponse.UpdateBaseTimes: // Get active time index m_timeIndex = BigEndian.ToInt32(buffer, responseIndex); responseIndex += 4; // Deserialize new base time offsets m_baseTimeOffsets = new[] { BigEndian.ToInt64(buffer, responseIndex), BigEndian.ToInt64(buffer, responseIndex + 8) }; break; case ServerResponse.UpdateCipherKeys: // Move past active cipher index (not currently used anywhere else) responseIndex++; // Extract remaining response byte[] bytes = buffer.BlockCopy(responseIndex, responseLength - 1); // Decrypt response payload if subscription is authenticated if (m_authenticated) bytes = bytes.Decrypt(m_sharedSecret, CipherStrength.Aes256); // Deserialize new cipher keys keyIVs = new byte[2][][]; keyIVs[EvenKey] = new byte[2][]; keyIVs[OddKey] = new byte[2][]; int index = 0; int bufferLen; // Read even key size bufferLen = BigEndian.ToInt32(bytes, index); index += 4; // Read even key keyIVs[EvenKey][KeyIndex] = new byte[bufferLen]; Buffer.BlockCopy(bytes, index, keyIVs[EvenKey][KeyIndex], 0, bufferLen); index += bufferLen; // Read even initialization vector size bufferLen = BigEndian.ToInt32(bytes, index); index += 4; // Read even initialization vector keyIVs[EvenKey][IVIndex] = new byte[bufferLen]; Buffer.BlockCopy(bytes, index, keyIVs[EvenKey][IVIndex], 0, bufferLen); index += bufferLen; // Read odd key size bufferLen = BigEndian.ToInt32(bytes, index); index += 4; // Read odd key keyIVs[OddKey][KeyIndex] = new byte[bufferLen]; Buffer.BlockCopy(bytes, index, keyIVs[OddKey][KeyIndex], 0, bufferLen); index += bufferLen; // Read odd initialization vector size bufferLen = BigEndian.ToInt32(bytes, index); index += 4; // Read odd initialization vector keyIVs[OddKey][IVIndex] = new byte[bufferLen]; Buffer.BlockCopy(bytes, index, keyIVs[OddKey][IVIndex], 0, bufferLen); //index += bufferLen; // Exchange keys m_keyIVs = keyIVs; OnStatusMessage("Successfully established new cipher keys for data packet transmissions."); break; case ServerResponse.Notify: // Skip the 4-byte hash string message = m_encoding.GetString(buffer, responseIndex + 4, responseLength - 4); // Display notification OnStatusMessage("NOTIFICATION: {0}", message); OnNotificationReceived(message); // Send confirmation of receipt of the notification SendServerCommand(ServerCommand.ConfirmNotification, buffer.BlockCopy(responseIndex, 4)); break; case ServerResponse.ConfigurationChanged: OnStatusMessage("Received notification from publisher that configuration has changed."); OnServerConfigurationChanged(); // Initiate meta-data refresh when publisher configuration has changed - we only do this // for automatic connections since API style connections have to manually initiate a // meta-data refresh. API style connection should attach to server configuration changed // event and request meta-data refresh to complete automated cycle. if (m_autoConnect && m_autoSynchronizeMetadata) SendServerCommand(ServerCommand.MetaDataRefresh, m_metadataFilters); break; } } catch (Exception ex) { OnProcessException(new InvalidOperationException("Failed to process publisher response packet due to exception: " + ex.Message, ex)); } } }
/// <summary> /// Event handler for service stopping operation. /// </summary> /// <param name="sender">Event source.</param> /// <param name="e">Event arguments.</param> /// <remarks> /// Time-series framework uses this handler to un-wire events and dispose of system objects. /// </remarks> protected virtual void ServiceStoppingHandler(object sender, EventArgs e) { // Dispose system health exporter if ((object)m_healthExporter != null) { m_healthExporter.Enabled = false; m_serviceHelper.ServiceComponents.Remove(m_healthExporter); m_healthExporter.Dispose(); m_healthExporter.StatusMessage -= m_iaonSession.StatusMessageHandler; m_healthExporter.ProcessException -= m_iaonSession.ProcessExceptionHandler; m_healthExporter = null; } // Dispose system status exporter if ((object)m_statusExporter != null) { m_statusExporter.Enabled = false; m_serviceHelper.ServiceComponents.Remove(m_statusExporter); m_statusExporter.Dispose(); m_statusExporter.StatusMessage -= m_iaonSession.StatusMessageHandler; m_statusExporter.ProcessException -= m_iaonSession.ProcessExceptionHandler; m_statusExporter = null; } // Dispose reload config queue if ((object)m_reloadConfigQueue != null) { m_reloadConfigQueue.ProcessException -= m_iaonSession.ProcessExceptionHandler; m_reloadConfigQueue.Dispose(); m_reloadConfigQueue = null; } // Dispose reporting processes if ((object)m_reportingProcesses != null) { m_serviceHelper.ServiceComponents.Remove(m_reportingProcesses); m_reportingProcesses = null; } // Dispose Iaon session if ((object)m_iaonSession != null) { m_serviceHelper.ServiceComponents.Remove(m_iaonSession.InputAdapters); m_serviceHelper.ServiceComponents.Remove(m_iaonSession.ActionAdapters); m_serviceHelper.ServiceComponents.Remove(m_iaonSession.OutputAdapters); m_iaonSession.Dispose(); m_iaonSession.StatusMessage -= StatusMessageHandler; m_iaonSession.ProcessException -= ProcessExceptionHandler; m_iaonSession.ConfigurationChanged -= ConfigurationChangedHandler; m_iaonSession = null; } // Dispose of run-time log if ((object)m_runTimeLog != null) { m_serviceHelper.ServiceComponents.Remove(m_runTimeLog); m_runTimeLog.ProcessException -= ProcessExceptionHandler; m_runTimeLog.Dispose(); m_runTimeLog = null; } m_serviceHelper.ServiceStarting -= ServiceStartingHandler; m_serviceHelper.ServiceStarted -= ServiceStartedHandler; m_serviceHelper.ServiceStopping -= ServiceStoppingHandler; m_serviceHelper.UpdatedStatus -= UpdatedStatusHandler; m_serviceHelper.LoggedException -= LoggedExceptionHandler; if ((object)m_serviceHelper.StatusLog != null) { m_serviceHelper.StatusLog.Flush(); m_serviceHelper.StatusLog.LogException -= LogExceptionHandler; } if ((object)m_serviceHelper.ErrorLogger != null && (object)m_serviceHelper.ErrorLogger.ErrorLog != null) { m_serviceHelper.ErrorLogger.ErrorLog.Flush(); m_serviceHelper.ErrorLogger.ErrorLog.LogException -= LogExceptionHandler; } // Detach from handler for unobserved task exceptions TaskScheduler.UnobservedTaskException -= TaskScheduler_UnobservedTaskException; ShutdownHandler.InitiateSafeShutdown(); }
/// <summary> /// Releases the unmanaged resources used by the <see cref="DataSubscriber"/> object and optionally releases the managed resources. /// </summary> /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param> protected override void Dispose(bool disposing) { if (!m_disposed) { try { if (disposing) { DataLossInterval = 0.0D; CommandChannel = null; DataChannel = null; DisposeLocalConcentrator(); if ((object)m_dataGapRecoverer != null) { m_dataGapRecoverer.RecoveredMeasurements -= m_dataGapRecoverer_RecoveredMeasurements; m_dataGapRecoverer.StatusMessage -= m_dataGapRecoverer_StatusMessage; m_dataGapRecoverer.ProcessException -= m_dataGapRecoverer_ProcessException; m_dataGapRecoverer.Dispose(); m_dataGapRecoverer = null; } if ((object)m_runTimeLog != null) { m_runTimeLog.ProcessException -= m_runTimeLog_ProcessException; m_runTimeLog.Dispose(); m_runTimeLog = null; } if ((object)m_subscribedDevicesTimer != null) { m_subscribedDevicesTimer.Elapsed -= SubscribedDevicesTimer_Elapsed; m_subscribedDevicesTimer.Dispose(); m_subscribedDevicesTimer = null; } } } finally { m_disposed = true; // Prevent duplicate dispose. base.Dispose(disposing); // Call base class Dispose(). } } }
/// <summary> /// Event handler for service starting operations. /// </summary> /// <param name="sender">Event source.</param> /// <param name="e">Event arguments containing command line arguments passed into service at startup.</param> /// <remarks> /// Time-series framework uses this handler to load settings from configuration file as service is starting. /// </remarks> protected virtual void ServiceStartingHandler(object sender, EventArgs<string[]> e) { ShutdownHandler.Initialize(); // Define a run-time log m_runTimeLog = new RunTimeLog(); m_runTimeLog.FileName = "RunTimeLog.txt"; m_runTimeLog.ProcessException += ProcessExceptionHandler; m_runTimeLog.Initialize(); // Initialize Iaon session m_iaonSession = new IaonSession(); m_iaonSession.StatusMessage += StatusMessageHandler; m_iaonSession.ProcessException += ProcessExceptionHandler; m_iaonSession.ConfigurationChanged += ConfigurationChangedHandler; // Create a handler for unobserved task exceptions TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException; // Make sure default service settings exist ConfigurationFile configFile = ConfigurationFile.Current; string servicePath = FilePath.GetAbsolutePath(""); string cachePath = string.Format("{0}{1}ConfigurationCache{1}", servicePath, Path.DirectorySeparatorChar); string defaultLogPath = string.Format("{0}{1}Logs{1}", servicePath, Path.DirectorySeparatorChar); // System settings CategorizedSettingsElementCollection systemSettings = configFile.Settings["systemSettings"]; systemSettings.Add("ConfigurationType", "Database", "Specifies type of configuration: Database, WebService, BinaryFile or XmlFile"); systemSettings.Add("ConnectionString", "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=IaonHost.mdb", "Configuration database connection string"); systemSettings.Add("DataProviderString", "AssemblyName={System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089};ConnectionType=System.Data.OleDb.OleDbConnection;AdapterType=System.Data.OleDb.OleDbDataAdapter", "Configuration database ADO.NET data provider assembly type creation string"); systemSettings.Add("ConfigurationCachePath", cachePath, "Defines the path used to cache serialized configurations"); systemSettings.Add("LogPath", defaultLogPath, "Defines the path used to archive log files"); systemSettings.Add("MaxLogFiles", DefaultMaxLogFiles, "Defines the maximum number of log files to keep"); systemSettings.Add("CachedConfigurationFile", "SystemConfiguration.xml", "File name for last known good system configuration (only cached for a Database or WebService connection)"); systemSettings.Add("UniqueAdaptersIDs", "True", "Set to true if all runtime adapter ID's will be unique to allow for easier adapter specification"); systemSettings.Add("ProcessPriority", "High", "Sets desired process priority: Normal, AboveNormal, High, RealTime"); systemSettings.Add("AllowRemoteRestart", "True", "Controls ability to remotely restart the host service."); systemSettings.Add("MinThreadPoolWorkerThreads", DefaultMinThreadPoolSize, "Defines the minimum number of allowed thread pool worker threads."); systemSettings.Add("MaxThreadPoolWorkerThreads", DefaultMaxThreadPoolSize, "Defines the maximum number of allowed thread pool worker threads."); systemSettings.Add("MinThreadPoolIOPortThreads", DefaultMinThreadPoolSize, "Defines the minimum number of allowed thread pool I/O completion port threads (used by socket layer)."); systemSettings.Add("MaxThreadPoolIOPortThreads", DefaultMaxThreadPoolSize, "Defines the maximum number of allowed thread pool I/O completion port threads (used by socket layer)."); systemSettings.Add("ConfigurationBackups", DefaultConfigurationBackups, "Defines the total number of older backup configurations to maintain."); systemSettings.Add("PreferCachedConfiguration", "False", "Set to true to try the cached configuration first, before loading database configuration - typically used when cache is updated by external process."); systemSettings.Add("LocalCertificate", $"{ServiceName}.cer", "Path to the local certificate used by this server for authentication."); systemSettings.Add("RemoteCertificatesPath", @"Certs\Remotes", "Path to the directory where remote certificates are stored."); systemSettings.Add("DefaultCulture", "en-US", "Default culture to use for language, country/region and calendar formats."); // Example connection settings CategorizedSettingsElementCollection exampleSettings = configFile.Settings["exampleConnectionSettings"]; exampleSettings.Add("SqlServer.ConnectionString", "Data Source=serverName; Initial Catalog=databaseName; User ID=userName; Password=password", "Example SQL Server database connection string"); exampleSettings.Add("SqlServer.DataProviderString", "AssemblyName={System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089}; ConnectionType=System.Data.SqlClient.SqlConnection; AdapterType=System.Data.SqlClient.SqlDataAdapter", "Example SQL Server database .NET provider string"); exampleSettings.Add("MySQL.ConnectionString", "Server=serverName;Database=databaseName; Uid=root; Pwd=password; allow user variables = true;", "Example MySQL database connection string"); exampleSettings.Add("MySQL.DataProviderString", "AssemblyName={MySql.Data, Version=6.3.6.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d}; ConnectionType=MySql.Data.MySqlClient.MySqlConnection; AdapterType=MySql.Data.MySqlClient.MySqlDataAdapter", "Example MySQL database .NET provider string"); exampleSettings.Add("Oracle.ConnectionString", "Data Source=tnsName; User ID=schemaUserName; Password=schemaPassword", "Example Oracle database connection string"); exampleSettings.Add("Oracle.DataProviderString", "AssemblyName={Oracle.DataAccess, Version=2.112.2.0, Culture=neutral, PublicKeyToken=89b483f429c47342}; ConnectionType=Oracle.DataAccess.Client.OracleConnection; AdapterType=Oracle.DataAccess.Client.OracleDataAdapter", "Example Oracle database .NET provider string"); exampleSettings.Add("SQLite.ConnectionString", "Data Source=databaseName.db; Version=3; Foreign Keys=True; FailIfMissing=True", "Example SQLite database connection string"); exampleSettings.Add("SQLite.DataProviderString", "AssemblyName={System.Data.SQLite, Version=1.0.99.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139}; ConnectionType=System.Data.SQLite.SQLiteConnection; AdapterType=System.Data.SQLite.SQLiteDataAdapter", "Example SQLite database .NET provider string"); exampleSettings.Add("OleDB.ConnectionString", "Provider=Microsoft.Jet.OLEDB.4.0; Data Source=databaseName.mdb", "Example Microsoft Access (via OleDb) database connection string"); exampleSettings.Add("OleDB.DataProviderString", "AssemblyName={System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089}; ConnectionType=System.Data.OleDb.OleDbConnection; AdapterType=System.Data.OleDb.OleDbDataAdapter", "Example OleDb database .NET provider string"); exampleSettings.Add("Odbc.ConnectionString", "Driver={SQL Server Native Client 10.0}; Server=serverName; Database=databaseName; Uid=userName; Pwd=password;", "Example ODBC database connection string"); exampleSettings.Add("Odbc.DataProviderString", "AssemblyName={System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089}; ConnectionType=System.Data.Odbc.OdbcConnection; AdapterType=System.Data.Odbc.OdbcDataAdapter", "Example ODBC database .NET provider string"); exampleSettings.Add("WebService.ConnectionString", "http://localhost/ConfigSource/SystemConfiguration.xml", "Example web service connection string"); exampleSettings.Add("XmlFile.ConnectionString", "SystemConfiguration.xml", "Example XML configuration file connection string"); // Attempt to set default culture try { string defaultCulture = systemSettings["DefaultCulture"].ValueAs("en-US"); CultureInfo.DefaultThreadCurrentCulture = CultureInfo.CreateSpecificCulture(defaultCulture); // Defaults for date formatting, etc. CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.CreateSpecificCulture(defaultCulture); // Culture for resource strings, etc. } catch (Exception ex) { DisplayStatusMessage("Failed to set default culture due to exception, defaulting to \"{1}\": {0}", UpdateType.Alarm, ex.Message, CultureInfo.CurrentCulture.Name.ToNonNullNorEmptyString("Undetermined")); LogException(ex); } // Retrieve application log path as defined in the config file string logPath = FilePath.GetAbsolutePath(systemSettings["LogPath"].Value); // Make sure log directory exists try { if (!Directory.Exists(logPath)) Directory.CreateDirectory(logPath); } catch (Exception ex) { // Attempt to default back to common log file path if (!Directory.Exists(defaultLogPath)) { try { Directory.CreateDirectory(defaultLogPath); } catch { defaultLogPath = servicePath; } } DisplayStatusMessage("Failed to create logging directory \"{0}\" due to exception, defaulting to \"{1}\": {2}", UpdateType.Alarm, logPath, defaultLogPath, ex.Message); LogException(ex); logPath = defaultLogPath; } int maxLogFiles = systemSettings["MaxLogFiles"].ValueAs(DefaultMaxLogFiles); try { Logger.FileWriter.SetPath(logPath); Logger.FileWriter.SetLoggingFileCount(maxLogFiles); } catch (Exception ex) { DisplayStatusMessage("Failed to set logging path \"{0}\" or max file count \"{1}\" due to exception: {2}", UpdateType.Alarm, logPath, maxLogFiles, ex.Message); LogException(ex); } // Retrieve configuration cache directory as defined in the config file cachePath = FilePath.GetAbsolutePath(systemSettings["ConfigurationCachePath"].Value); // Make sure configuration cache directory exists try { if (!Directory.Exists(cachePath)) Directory.CreateDirectory(cachePath); } catch (Exception ex) { DisplayStatusMessage("Failed to create configuration cache directory \"{0}\" due to exception: {1}", UpdateType.Alarm, cachePath, ex.Message); LogException(ex); } try { Directory.SetCurrentDirectory(servicePath); } catch (Exception ex) { DisplayStatusMessage("Failed to set current directory to execution path \"{0}\" due to exception: {1}", UpdateType.Alarm, servicePath, ex.Message); LogException(ex); } // Initialize system settings m_configurationType = systemSettings["ConfigurationType"].ValueAs<ConfigurationType>(); m_cachedXmlConfigurationFile = FilePath.AddPathSuffix(cachePath) + systemSettings["CachedConfigurationFile"].Value; m_cachedBinaryConfigurationFile = FilePath.AddPathSuffix(cachePath) + FilePath.GetFileNameWithoutExtension(m_cachedXmlConfigurationFile) + ".bin"; m_configurationBackups = systemSettings["ConfigurationBackups"].ValueAs(DefaultConfigurationBackups); m_uniqueAdapterIDs = systemSettings["UniqueAdaptersIDs"].ValueAsBoolean(true); m_allowRemoteRestart = systemSettings["AllowRemoteRestart"].ValueAsBoolean(true); m_preferCachedConfiguration = systemSettings["PreferCachedConfiguration"].ValueAsBoolean(false); m_reloadConfigQueue = ProcessQueue<Tuple<string, Action<bool>>>.CreateSynchronousQueue(ExecuteReloadConfig, 500.0D, Timeout.Infinite, false, false); m_reloadConfigQueue.ProcessException += m_iaonSession.ProcessExceptionHandler; m_configurationCacheOperation = new LongSynchronizedOperation(ExecuteConfigurationCache) { IsBackground = true }; // Setup default thread pool size try { ThreadPool.SetMinThreads(systemSettings["MinThreadPoolWorkerThreads"].ValueAs(DefaultMinThreadPoolSize), systemSettings["MinThreadPoolIOPortThreads"].ValueAs(DefaultMinThreadPoolSize)); ThreadPool.SetMaxThreads(systemSettings["MaxThreadPoolWorkerThreads"].ValueAs(DefaultMaxThreadPoolSize), systemSettings["MaxThreadPoolIOPortThreads"].ValueAs(DefaultMaxThreadPoolSize)); } catch (Exception ex) { DisplayStatusMessage("Failed to set desired thread pool size due to exception: {0}", UpdateType.Alarm, ex.Message); LogException(ex); } // Define guid with query string delimiters according to database needs if (string.IsNullOrWhiteSpace(m_nodeIDQueryString)) m_nodeIDQueryString = "'" + m_iaonSession.NodeID + "'"; // Set up the configuration loader switch (m_configurationType) { case ConfigurationType.Database: m_configurationLoader = new DatabaseConfigurationLoader { ConnectionString = systemSettings["ConnectionString"].Value, DataProviderString = systemSettings["DataProviderString"].Value, NodeIDQueryString = m_nodeIDQueryString }; break; case ConfigurationType.WebService: m_configurationLoader = new WebServiceConfigurationLoader { URI = systemSettings["ConnectionString"].Value }; break; case ConfigurationType.BinaryFile: m_configurationLoader = new BinaryFileConfigurationLoader { FilePath = systemSettings["ConnectionString"].Value }; break; case ConfigurationType.XmlFile: m_configurationLoader = new XMLConfigurationLoader { FilePath = systemSettings["ConnectionString"].Value }; break; } m_binaryCacheConfigurationLoader = new BinaryFileConfigurationLoader { FilePath = m_cachedBinaryConfigurationFile }; m_xmlCacheConfigurationLoader = new XMLConfigurationLoader { FilePath = m_cachedXmlConfigurationFile }; m_configurationLoader.StatusMessage += (o, args) => DisplayStatusMessage(args.Argument, UpdateType.Information); m_binaryCacheConfigurationLoader.StatusMessage += (o, args) => DisplayStatusMessage(args.Argument, UpdateType.Information); m_xmlCacheConfigurationLoader.StatusMessage += (o, args) => DisplayStatusMessage(args.Argument, UpdateType.Information); m_configurationLoader.ProcessException += ConfigurationLoader_ProcessException; m_binaryCacheConfigurationLoader.ProcessException += ConfigurationLoader_ProcessException; m_xmlCacheConfigurationLoader.ProcessException += ConfigurationLoader_ProcessException; m_reloadConfigQueue.Start(); #if !MONO try { // Attempt to assign desired process priority. Note that process will require SeIncreaseBasePriorityPrivilege or // Administrative privileges to make this change Process.GetCurrentProcess().PriorityClass = systemSettings["ProcessPriority"].ValueAs<ProcessPriorityClass>(); } catch (Exception ex) { LogException(ex); } #endif }