Monitoring and Dynamically Configuring Windows Services

 

Carl Nolan
Microsoft Corporation

December 2003

Summary: Discusses how to monitor the configuration of a service and dynamically reconfigure the state of that service by changing the XML configuration file. (9 printed pages)

Download the MessageRoutingServiceApplication.exe sample file.

Introduction

Over the years I have written a series of articles on message routing based upon a Windows® Service solution. The premise was the automatic routing of messages from a central dispersion point and content-based processing. First, there was a solution presented utilizing ATL (Active Template Library), which was followed by a .NET Framework solution that utilized an XML configuration file.

This is the final installment in this series and it addresses needs that have arisen from deployments of the .NET Framework solution. These needs, applicable to most services, are monitoring the internals/configuration of the service, and dynamically reconfiguring the state of the service based on changes to the XML configuration file.

Before reading this article, you should review the previous .NET Framework solution at https://msdn.microsoft.com/library/en-us/dndotnet/html/csharpmsmq.asp.

Windows Service Application

The purpose of this article is to present a pattern for configuration management and internal monitoring of the state of a service. The assumption being that the service is based on an XML configuration file and has a series of distinct processes.

Configuration management of a service implies the ability to reconfigure the service through the deployment and file copy of a new XML configuration file. Monitoring of a service implies the ability to view the internal state. This involves the ability to view the status of each individual process within the service, along with the base configuration parameters.

click for larger image

Figure 1. Remoting static structure

The static diagram outlined in Figure 1 defines the base object model for the solution. The Service namespace contains classes internal to the service, and the Monitor namespace contains classes referenced by the client and the service for querying state.

In this implementation, the WorkerFormatter and WorkerStatus classes are specific to ones own implementation. In addition, ServiceControl derives from ServiceBase, the base class for a service, and ServiceMonitor derives from MarshalByRefObject.

Monitoring Service State

Any service instrumentation should provide for monitoring the state of the service, possibly through Performance Counter, Event Logging, WMI, and so on. However, there may be other specifics that one wishes to monitor, such as internal configuration parameters, processing characteristics, thread status, and so on. Such internal probing of a service can be achieved through the use of .NET remoting.

.NET Remoting Schematics

Monitoring the state of a service is achieved through .NET remoting by marshaling a single instance of a class from the service to a remote client. This is achieved through the use of RemotingServices. A static Marshal method is available that converts a given MarshalByRefObject-derived object into an instance of an ObjRef class, which can be serialized for transmission between application domains.

An ObjRef is a serializable representation of an object used to transfer an object reference through a remoting channel into another application domain, possibly in another process or on another computer. The ObjRef stores all relevant information required to generate a proxy in order to communicate with a remote object, including the Type description and class of the object being marshaled, a URI that uniquely identifies the specific object instance, and communication-related information on how to reach the remoting subdivision where the object is located.

It is through this premise that one is able to make available to clients a single instance of a class containing information pertinent to the service.

Remoting Implementation

The message routing service in question is configured from an XML file. The processing architecture is such that the service is defined by distinct processes, each with their own configuration parameters.

The premise is that there is a separate package, for client deployment, containing the marshalable object ServiceMonitor, which references an array of WorkerStatus objects. These WorkerStatus objects hold state information for each process within the service. The client-accessible classes contain read-only properties for the service state.

The service contains an internal implementation of the marshalable object, called ServiceMonitorState, which extends the base ServiceMonitor class with internal methods for property value maintenance. Using the RemotingServices, the service then marshals an initialized instance of this class after being cast to the base class for a client to consume:

string objectUri =
   ConfigurationSettings.AppSettings["ServiceMonitorUri"];
string remotingFile =
AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
serviceMonitor = new ServiceMonitorState();
RemotingConfiguration.Configure(remotingFile);
ObjRef objref = RemotingServices.Marshal(
   (ServiceMonitor)serviceMonitor, objectUri, typeof(ServiceMonitor));

In this scenario, the remoting configuration settings are held in the application configuration file:

<system.runtime.remoting>
   <application>
      <channels>
         <channel ref="tcp server"
            port="8081"
            name="messageroutingmonitor"
         />
      </channels>
   </application>
</system.runtime.remoting>

The only requirement is for a channel definition as the RemotingServices class provides the mechanism for marshaling the remotable object over the defined channel. The URI that uniquely identifies the specific object instance was in this case defined as messageroutingmonitor.rem.

In addition to RemotingServices, the remoting architecture provides a mechanism for remoting a server-activated singleton. However, defining such a definition will not allow one to control the initial state of the object to be marshaled. The configuration for such a service would be:

<service>
   <wellknown
      type="MSDN.MessageRoutingService.Monitor.ServiceMonitor,
         MessageMonitor"
      mode="Singleton"
      objectUri="messageroutingmonitor.rem" />
</service>

The remoting registration is executed during the service startup, and the initial state of the object defined by calling the ServiceMonitorState SetStates method. It is this method that creates the array of WorkerStatus objects. During the execution of the service, the UpdateStates method is used to reflect state changes. The UpdateSingleState method is executed upon a state change within a worker process, usually an exception condition.

To allow the marshalling of the ServiceMonitor class, it has to derive from MarshalByRefObject. The remoting lifetime of such an object is determined by a lifetime service object, a class derived from ILease, obtained from the virtual method InitializeLifetimeService. The lifetime service associates a lease with each remotely activated object. When the lease expires, the object is removed. In the service implementation, the marshaled object should not expire. This is achieved by retuning null from the InitializeLifetimeService method.

Client Application

To access the service state information, the client has to connect to the uniquely identified object instance over the specified channel. This can be achieved with a remoting configuration section in the client-application configuration file:

<system.runtime.remoting>
   <application>
      <client>
         <wellknown
type="MSDN.MessageRoutingService.Monitor.ServiceMonitor,
            MessageMonitor"
            url="tcp://localhost:8081/messageroutingmonitor.rem" />
      </client>
      <channels>
         <channel ref="tcp client" name="messageroutingmonitorclient" />
      </channels>
   </application>
</system.runtime.remoting>

Using the configuration file, the client only has to configure the remoting options like the following:

string configFile =
   AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
RemotingConfiguration.Configure(configFile);

Once the remoting options are configured, the client can create a class instance to view the service configuration. The actual class instance is accessed through a proxy that forwards calls made on it to the real object using the remoting infrastructure:

serviceMonitor = new ServiceMonitor();
this.resultText.Text = string.Empty;
foreach (WorkerStatus status in serviceMonitor.WorkStatusValues)
{
   this.resultText.AppendText(status.ToXml());
   this.resultText.AppendText("\r\n");
}

As the remoted class contains an indexer based on the keys handed out by the WorkerStatusKeys collection, the status of each worker process can be queried individually. Each query on a worker process gives the latest state for that process.

Windows Management Instrumentation

In addition to using remoting, one has the option to monitor a service using WMI (Windows Management Instrumentation). The sample code comes with a complete implementation that also utilizes WMI along with client query code.

Analogous to the ServiceMonitorState class, the implementation provides a WMIServiceState that exposes WMI instance and event data. The WMI instance data is analogous to the remoted status information. The WMI event data is exposed when the state of the service or single worker process changes.

Dynamic Service Reconfiguration

When the configuration of a service is dependant on an XML configuration file, the service should reconfigure itself when the configuration file is modified. This can be achieved through restarting the service. Hence, picking up the new configuration file. However, a much more manageable approach is to have the service respond to configuration changes by restarting itself.

The base service control functions implemented from the ServiceBase class are OnStart, OnStop, OnPause, and OnContinue. This pattern is extended with an OnConfigurationChange method, designed to response to configuration file changes in much the same fashion as the other methods response to service control commands.

Service Control Functions

Normal service control functions, and their corresponding implementation, are sequentially executed on a single processing thread. However, with the inclusion of the OnConfigurationChange, this is no longer the case. Using a file watcher on the configuration file, the corresponding event handler executes on a separate processing thread. Thus, one has to consider thread safety, which is easily achieved within the .NET Framework by the use of the lock keyword.

The OnConfigurationChange implementation is similar to the standard OnStart implementation. The main difference is that the original processes has to be stopped before the new configuration is loaded. The basic processes being performed are:

  • Stop all currently running processes
  • Load the new configuration file creating the appropriate new processes
  • Start the newly created processes
  • Set the process state values.
private void OnConfigurationChange()
{
   lock (this)
   {
      // set the initial state of the monitor class
      ServiceMonitorClear();
            
      // calls the stop method on each worker object
      foreach (WorkerInstance workerReference in workerReferences.Values) 
      {
         workerReference.StopService();
      }
      workerReferences.Clear();

      // process the XML file
      ProcessConfigurationFile();

      // set the initial state of the monitor class
      ServiceMonitorSetState();

      // call the start method on each worker object
      foreach (WorkerInstance workerReference in workerReferences.Values) 
      {
         try
         {
            workerReference.StartService();
         }
         catch (Exception) 
         {
            // a start failed but continue with other creations
            // one could decide to abort the start process at this point
         }
      }

      // update the status of the monitor class
      ServiceMonitorUpdateState();

      // enable monitoring of xml configuration changes
      xmlWatcher.EnableRaisingEvents = monitorConfiguration;
   }

   // indicate the service has been reconfigured
   LogInformation("Service Restarted with new configuration");

} //OnConfigurationChange

The OnStart method operates in the same fashion with the inclusion of initializing the instrumentation functions and loading the remoting configuration.

File Watcher Schematics

For monitoring the XML configuration file, the .NET Framework provides a FileSystemWatcher class. This class listens to file system change notifications and raises events for these notifications. The service class constructor only has to hook up the appropriate filters to watch for changes to the configuration file:

xmlWatcher.Path = Path.GetDirectoryName(configFile);
xmlWatcher.Filter = Path.GetFileName(configFile);
xmlWatcher.NotifyFilter =
   NotifyFilters.LastWrite | NotifyFilters.CreationTime;
xmlWatcher.Changed += new
   FileSystemEventHandler(ConfigurationChangeHandler);
xmlWatcher.Created += new
   FileSystemEventHandler(ConfigurationChangeHandler);

The ConfigurationChangeHandler is a FileSystemEventHandler delegate. The implemented event handler calls the new OnConfigurationChange service control function.

Pause and Continue Considerations

Another side effect of an OnConfigurationChange operation is that one now has to consider that a new configuration may be loaded when the service is in a paused state. In this case, the new configuration has to be loaded but the service must still reside in the paused state.

To achieve this configuration when the service is placed into a paused state , a flag is set to indicate this condition during the OnPause operation. The OnConfigurationChange operation includes this state flag when starting each worker process to indicate the initial required state of each worker process.

Conclusion

As one can see from the presented solution, the ability to monitor the internals/configuration of the service and dynamically reconfiguring the state of the service based on changes to the XML configuration file is not difficult.

If one has a service based on an XML configuration file and independent processes, the pattern should be reusable to achieve the same goals. The pattern only leaves two classes, WorkerFormatter and WorkerStatus, which one would have to modify for specific implementations.

Although this pattern solves the solution of peeking into a running service, one could also provide a solution to modify the internal state of a running service through the use of .NET remoting . In this case, the remoted object would have defined operations.

References

Carl Nolan works for Microsoft Services, US National Services. He focuses on the development of .NET Framework solutions using the Windows .NET platform and SQL Server. He can be reached at carlnol@microsoft.com.