I have a problem with a Windows Service application.
The application has a timer that every x seconds performs a function.
By testing the application on the local developer PC, the service works correctly.
By testing the service on the Windows Server 2008 compiling also in Release mode, when I start the service I get the following error
Error 1053: The service did not responde to the start or control request in a timely fashion
If I go to check from the server event viewer I get this information
Below I leave my little snippet of code
private const int TICK_TIMER = 120000; //Start timer every 2 minutes
private Timer readTimer = null;
public BarcodeReader()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
readTimer = new Timer();
readTimer.Interval = TICK_TIMER;
readTimer.Elapsed += readTimer_Tick;
readTimer.Enabled = true;
WriteLog("Servizio Barcode started");
}
private void readTimer_Tick(object sender, ElapsedEventArgs e)
{
//Start function
try
{
MyFunction();
}
catch (Exception ex)
{
WriteLog("ERROR: " + ex.Message);
}
}
private void WriteLog(string mex)
{
try
{
//sw = new StreamWriter(AppDomain.CurrentDomain.BaseDirectory + "\\LogFile.txt", true);
using (var sw = new StreamWriter(Globals.LogPath + "LogBarcodeService.txt", true))
{
sw.WriteLine(DateTime.Now.ToString(CultureInfo.CurrentCulture) + ": " + mex);
sw.Flush();
sw.Close();
}
}
catch (Exception)
{
throw;
}
}
protected override void OnStop()
{
readTimer.Enabled = false;
WriteLog("Servizio Barcode terminated");
}
N.B. On the server NET Framework 4.5 is installed as on the Developer PC
UPDATE
This is the call to the InitializeComponent function
namespace BarcodeReaderService
{
partial class BarcodeReader
{
/// <summary>
/// Variabile di progettazione necessaria.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Pulire le risorse in uso.
/// </summary>
/// <param name="disposing">ha valore true se le risorse gestite devono essere eliminate, false in caso contrario.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Codice generato da Progettazione componenti
/// <summary>
/// Metodo necessario per il supporto della finestra di progettazione. Non modificare
/// il contenuto del metodo con l'editor di codice.
/// </summary>
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
this.ServiceName = "Service1";
}
#endregion
}
}
UPDATE 2
I tried to bring all the code into an application console and run them smoothly on the server.
The solution should be to include everything from your output directory (e.g. bin\Debug) to copy to a certain folder on your server. From there you run InstallUtil to register the service.
Another way would be to create an installer for the Windows Service.
One thing to check would be the .Net framework version between your development machine and the Server.
We faced a similar issue when our development machine had .Net 4.7 (the latest one as of now) and the Server had 4.5.
In our case, we also had the following logged in our event viewer
First exception
Framework Version: v4.0.30319
Description: The process was terminated due to an unhandled exception.
Exception Info: System.IO.FileLoadException
Second exception entry
Faulting module name: KERNELBASE.dll, version: 6.3.9600.18340, time stamp: 0x5736541b
Exception code: 0xe0434352
Fault offset: 0x00014878
You can use this guide to find the exact version:
https://learn.microsoft.com/en-us/dotnet/framework/migration-guide/how-to-determine-which-versions-are-installed
Please also note that the later versions are in-place upgrades ! So once you have the newest version, there is no way to go back to the old version !
https://learn.microsoft.com/en-us/dotnet/framework/install/guide-for-developers
The fact that later .NET Framework 4.x versions are in-place updates to earlier versions means that you cannot install an earlier version listed in the table if a later version is already installed
Related
I'm encountering an issue where a service is exiting on errors that should never propagate up.
I built a microservice manager (.NET as the local environment doesnt support .NET Core and some of its native microservice abilities)
Built in VS2019 targeting .NET 4.5.2 (I know, but this is the world we live in)
For the microservice manager, it is built and installed as a windows service. Entry looks like this (#if/#else was for testing locally, it is working as intended when registered as a windows service)
Program.cs (Entry point)
` static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
static void Main()
{
#if DEBUG
Scheduler myScheduler = new Scheduler();
myScheduler.OnDebug();
System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite);
#else
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
new Scheduler()
};
ServiceBase.Run(ServicesToRun);
#endif
}
}`
Scheduler.cs
//(confidential code hidden)
`private static readonly Configuration config = Newtonsoft.Json.JsonConvert.DeserializeObject<Configuration>(
File.ReadAllText(configFilePath)
);
public Scheduler()
{
//InitializeComponent(); //windows service, doesnt need UI components initialized
}
public void OnDebug()
{
OnStart(null); //triggers when developing locally
}
protected override async void OnStart(string[] args)
{
try
{
logger.Log($#"Service manager starting...");
logger.Log($#"Finding external services... {config.services.Count} services found.");
foreach (var service in config.services)
{
try
{
if (service.disabled)
{
logger.Log(
$#"Skipping {service.name}: disabled=true in Data Transport Service's appSettings.json file");
continue;
}
logger.Queue($#"Starting: {service.name}...");
string serviceLocation = service.useRelativePath
? Path.Combine(assemblyLocation, service.path)
: service.path;
var svc = Assembly.LoadFrom(serviceLocation);
var assemblyType = svc.GetType($#"{svc.GetName().Name}.Program");
var methodInfo = assemblyType.GetMethod("Main");
var instanceObject = Activator.CreateInstance(assemblyType, new object[0]);
methodInfo.Invoke(instanceObject, new object[0]);
logger.Queue(" Running").Send("");
}
catch (TargetInvocationException ex)
{
logger.Queue(" Failed").Send("");
logger.Log("an error occurred", LOG.LEVEL.CRITICAL, ex);
}
catch (Exception ex)
{
logger.Queue(" Failed").Send("");
logger.Log("an error occurred", LOG.LEVEL.CRITICAL, ex);
}
}
logger.Log("Finished loading services.");
}
catch (Exception ex)
{
logger.Log($#"Critical error encountered", LOG.LEVEL.CRITICAL, ex);
}
}
Microservice:
public [Confidential]()
{
if (currentProfile == null)
{
var errMsg =
$#"Service not loaded, Profile not found, check appSettings.currentProfile: '{config.currentProfile}'";
logger.Log(errMsg,severity: LOG.LEVEL.CRITICAL);
throw new SettingsPropertyNotFoundException(errMsg);
}
if (currentProfile.disabled)
{
var errMsg = $#"Service not loaded: {config.serviceName}, Service's appSettings.currentProfile.disabled=true";
logger.Log(errMsg,LOG.LEVEL.WARN);
throw new ArgumentException(errMsg);
}
logger.Log($#"Loading: '{config.serviceName}' with following configuration:{Environment.NewLine}{JsonConvert.SerializeObject(currentProfile,Formatting.Indented)}");
logger.Queue($#"Encrypting config file passwords...");
bool updateConfig = false;
foreach (var kafkaSource in config.dataTargets)
{
if (!kafkaSource.password.IsEncrypted())
{
updateConfig = true;
logger.Queue($#"%tabEncrypting: {kafkaSource.name}");
kafkaSource.password = kafkaSource.password.Encrypt();
}
else
{
logger.Queue($#"%tabAlready encrypted: {kafkaSource.name}");
}
}
logger.Send(Environment.NewLine);
if (updateConfig)
{
File.WriteAllText(
configFilePath,
Newtonsoft.Json.JsonConvert.SerializeObject(config));
}
var _source = config.dataSources.FirstOrDefault(x=>x.name==currentProfile.dataSource);
var _target = config.dataTargets.FirstOrDefault(x => x.name == currentProfile.dataTarget);
source = new Connectors.Sql(logger,
_source?.name,
_source?.connectionString,
_source.pollingInterval,
_source.maxRowsPerSelect,
_source.maxRowsPerUpdate);
target = new Connectors.KafkaProducer(logger)
{
bootstrapServers = _target?.bootstrapServers,
name = _target?.name,
password = _target?.password.Decrypt(),
sslCaLocation = Path.Combine(assemblyLocation,_target?.sslCaLocation),
topic = _target?.topic,
username = _target?.username
};
Start();
}
public void Start()
{
Timer timer = new Timer();
try
{
logger.Log($#"SQL polling interval: {source.pollingInterval} seconds");
timer.Interval = source.pollingInterval * 1000;
timer.Elapsed += new ElapsedEventHandler(this.OnTimer);
timer.Start();
if (currentProfile.executeOnStartup)
Run();
}
catch (Exception ex)
{
var sb = new StringBuilder();
sb.AppendLine($#"Critical error encountered loading external service: {config.serviceName}.");
if (!timer.Enabled)
sb.AppendLine($#"service unloaded - Schedule not started!");
else
sb.AppendLine($#"service appears to be loaded and running on schedule.");
logger.Log(sb.ToString(), LOG.LEVEL.CRITICAL, ex);
}
}
public void OnTimer(object sender, ElapsedEventArgs e)
{
try
{
Run();
}
catch (Exception ex)
{
logger.Log($#"Critical error during scheduled run on service: {config.serviceName}.", LOG.LEVEL.CRITICAL, ex);
}
}
public async void Run()
{
//Get new alarm events from SQL source
logger.Queue("Looking for new alarms...");
var rows = await GetNewEvents();`
The exception occurred during the GetNewEvents method, which attempted to open a SqlConnection to a SQL server that was unavailable due to network issues, that method intentionally throws an exception, which should throw up to OnTimer, where it gets caught, logged, and the timer keeps running. During development/testing, I used invalid credentials, bad connection string, etc and simulated this type of error and it worked as expected, logged the error, kept running. For some reason recently, that error is not caught in OnTimer, it propagates up, where it should be caught by Start (but isn't), after that it should be caught by the parent service manager which is entirely wrapped in a try/catch with no throw's, and above that (because their could be multiple microservices managed by that service) the entry point to the service manager is wrapped in try/catch with no throws, all for isolation from microservice errors. For some reason though, now, the error from a VERY downstream application is propagating all the way up.
Typically, this code runs 24/7 no issues, the microservice it is loading from the config file launches and runs fine. The entry into that specific microservice starts with a try {...} catch (Exception ex) {...} block.
The concept is to have a microservice manager than can launch a number of microservices without having to install all of them as windows services, and have some level of configuration driven by a config file that dictates how the main service runs.
The microservice represented here opens a SQL connection, reads data, performs business logic, publishes results to Kafka, it does this on a polling interval dictated by the config file contained in the microservice. As stated above, its ran for months without issue.
Recently, I noticed the main microservice manager service was not running on the windows server, I investigated the Server Application Logs and found a "Runtime Error" that essentially stated the microservice, while attempting to connect to sql, failed (network issue) and caused the entire microservice manager to exit. To my understanding, they way I'm launching the microservice should isolate it from the main service manager app. Additionally, the main service manager app is wrapped in a very generic try catch block. The entry point to the micro service itself is wrapped in a try catch, and almost every component in the microservice is wrapped in try / catch per business need. The scenario that faulted (cant connect to sql) intentionally throws an error for logging purposes, but should be caught by the immediate parent try/catch, which does not propagate or re-throw, only logs the error to a txt file and the windows server app log.
How is it that this exception is bubbling up through isolation points and causing the main service to fault and exit? I tested this extensively during development and prior to release, this exact scenario being unable to connect to sql, and it generated the correct log entry, and tried again on the next polling cycle as expected.
I haven't tried any other approaches as yet, as I feel they would be band-aid fixes as best as I dont understand why the original design is suddenly failing. The server hasn't changed, no patching/security updates/etc.
From the server Application Log:
Application: DataTransportService.exe
Framework Version: v4.0.30319
Description: The process was terminated due to an unhandled exception.
Exception Info: System.Exception
at Connectors.SqlHelper.DbHelper+d__13`1[[System.__Canon, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]].MoveNext()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(System.Threading.Tasks.Task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(System.Threading.Tasks.Task)
at IntelligentAlarms.IntelligentAlarm+d__14.MoveNext()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(System.Threading.Tasks.Task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(System.Threading.Tasks.Task)
at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(System.Threading.Tasks.Task)
at IntelligentAlarms.IntelligentAlarm+d__12.MoveNext()
at System.Runtime.CompilerServices.AsyncMethodBuilderCore+<>c.b__6_1(System.Object)
at System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(System.Object)
at System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
at System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
at System.Threading.ThreadPoolWorkQueue.Dispatch()
at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()
I have the below code which I use as part of a program which reads some event logs.
After stepping through the code this morning to test something out, I keep getting error messages emailed to me:
EventLogHelper error occurred in QueryEvents
Exception: The RPC server is unavailable
User:
Client: D7-089
The process is not running on my machine, and I only stepped through the method in question once or twice. However The messages just keep coming. The space of time between each message seems to vary, and I have received at least 15 now.
How is this possible? I feel that if the program is sending me emails, it must be executing somewhere, but I cannot see it in the Processes tab in Task Manager, and I tried ending all Visual Studio related processes without any joy.
I restarted the PC on which VS was running, but I am still receiving the emails.
Main()
public static void Main()
{
var eh = new EventLogHelper();
var eventFired = eh.CheckEvents();
}
EventLogHelper.cs
public readonly string PcName;
private readonly int Timespan;
private readonly string Filter;
private static EventLogSession Session;
/// <summary>
/// ctor
/// </summary>
public EventLogHelper()
{
Timespan = 30000; // 30 seconds
PcName = "D7-089"; // This is usually "Environment.MachineName", but specified it here for testing
Filter = $"*[System[(EventID='5061' or EventID='5058') and TimeCreated[timediff(#SystemTime) <= {Timespan}]]]";
}
CheckEvents
/// <summary>
/// Checks the event logs for remote pc and returns true if any of the events we are interested in fired
/// </summary>
/// <returns></returns>
public bool CheckEvents()
{
var query = BuildQuery(PcName, Filter);
for (var i = 0; i < 60; i++)
{
var logs = QueryEvents(query);
var events = ReadLogs(logs);
if (events > 0)
{
return true;
}
System.Threading.Thread.Sleep(1000);
}
return false;
}
BuildQuery
/// <summary>
/// Builds an EventLogQuery for the given pcname and filter. This should be set up with a user who has admin rights
/// </summary>bh
private static EventLogQuery BuildQuery(string pcName, string filter)
{
try
{
using (var pw = GetPassword())
{
Session = new EventLogSession(
pcName,
"DOMAIN",
"USER",
pw,
SessionAuthentication.Default);
}
return new EventLogQuery("Security", PathType.LogName, filter)
{ Session = Session };
}
catch (Exception ex)
{
Email.Send($"EventLogHelper error occurred in BuildQuery \n\n Exception: {ex.Message} \n\n User: {Program.UserName} \n\n Client: {pcName}");
Environment.Exit(Environment.ExitCode);
return null;
}
}
QueryEvents
This is where the error is occurring. I stepped through this method 2 times at most and as I type this question I am still getting error emails through.
/// <summary>
/// Execute the given EventLogQuery
/// </summary>
private EventLogReader QueryEvents(EventLogQuery query)
{
try
{
return new EventLogReader(query);
}
catch (Exception ex)
{
Email.Send($"EventLogHelper error occurred in QueryEvents \n\n Exception: {ex.Message} \n\n User: {Program.UserName} \n\n Client: {PcName}");
Environment.Exit(Environment.ExitCode);
return null;
}
}
ReadLogs
/// <summary>
/// Read the given EventLogReader and return the amount of events that match the IDs we are looking for
/// </summary>
/// <param name="logReader"></param>
/// <returns></returns>
private int ReadLogs(EventLogReader logReader)
{
var count5058 = 0;
var count5061 = 0;
EventRecord entry;
try
{
while ((entry = logReader.ReadEvent()) != null)
{
if (entry.Id == 5058)
{
count5058++;
}
else
{
count5061++;
}
}
}
catch (Exception ex)
{
Email.Send($"EventLogHelper error occurred in ReadLogs \n\n Exception: {ex.Message} \n\n User: {Program.UserName} \n\n Client: {PcName}");
Environment.Exit(Environment.ExitCode);
}
return count5058 + count5061;
}
It turns out that this was being caused by a stupid mistake on my part.
This program has a Post-Build Event which is called with:
if $(ConfigurationName) == Release ("$(ProjectDir)PostBuildRelease.bat" "$(TargetDir)" #(VersionNumber) "$(TargetFileName)" "$(TargetName)")
So it only runs when VS build configuration is set to Release.
PostBuildRelease.bat simply copies the resuling assembly to the live location, ready for users to have copied to their desktops at logon.
Whilst testing my app, I foolishly edited the source code to query a specific PC, and then stepped through the code.
However, the build configuration was set to Release, So once the assembly was built ready to be debugged, it was automatically copied into the live executable location and therefore also copied to user's desktops at logon.
If the code is run with a hard-coded PcName where that PC is not the current machine, the event query appears to fail with the above error message.
So all of the emails I receiving were being sent out because the program was actually being executed on user PCs. However because PcName was hard-coded in, it always looked like it was coming from my instance of the program!
The lesson here is to always be aware of which build configuration is currently selected, especially if a Post-Build event is specified.
I'm trying to do a call to the Google API Services (specifically Google.Apis.Webfonts.v1.WebfontsService) from a C# console application. And each time i'm getting the very same exception:
An unhandled exception of type 'System.AccessViolationException' occurred in mscorlib.dll
Additional information: Attempted to read or write protected memory.
This is often an indication that other memory is corrupt.
My complete test application code is below and the google api is added as NuGet packages. I'm using the latest version of the API NuGet packages and i'm targeting .Net 4.5 on windows 8.1 and VS2013 sp4:
using Google.Apis.Services;
using Google.Apis.Webfonts.v1;
using Google.Apis.Webfonts.v1.Data;
using System;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
// create the service
var service = new WebfontsService(new BaseClientService.Initializer
{
ApplicationName = "Webfonts Sample",
ApiKey = "my private api key",
});
// run the request
var result = service.Webfonts.List().Execute();
// display the results
if (result.Items != null)
{
foreach (Webfont font in result.Items)
{
Console.WriteLine(font.Family);
}
}
}
}
}
The same problem exist for any API call to Google.Apis. So this made me think it's not directly related to the Webfonts, but to the BaseClientService.
I tried to solve it by running it in 32bit mode, but this didn't help me. Next, instead of nuget packages i used the source version of the api and tried to debug it. So i got to the point where the exception is thrown, but still can't see how it's been generated.
Did anybody encounter this problem before and able to resole it. And how?
UPDATE: the same code runs fine on other boxes with the same setup.
I just tested it and it worked fine for me.
You need the following NuGet Packages.
Install-Package Google.Apis.Webfonts.v1
Install-Package Google.Apis.Auth
Usings
using Google.Apis.Webfonts.v1;
using Google.Apis.Webfonts.v1.Data;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Services;
Auth
/// <summary>
/// Used for accessing public api data.
/// </summary>
/// <param name="apiKey">Public API key from Google Developers console</param>
/// <returns>a valid WebfontsService</returns>
public static WebfontsService AuthenticatePublic(string apiKey)
{
if (string.IsNullOrEmpty(apiKey))
throw new Exception("apiKey is required.");
try
{
// Create the service.
var service = new WebfontsService(new BaseClientService.Initializer()
{
ApiKey = apiKey,
ApplicationName = "Webfonts Authentication Sample",
});
return service;
}
catch (Exception ex)
{
Console.WriteLine(ex.InnerException);
throw ex;
}
}
Request Method
/// <summary>
/// Retrieves the list of fonts currently served by the Google Fonts Developer API
/// Documentation: https://developers.google.com/fonts/docs/developer_api/v1/webfonts/list
/// </summary>
/// <param name="service">Valid authentcated WebfontsService</param>
/// <returns></returns>
public static WebfontList list(WebfontsService service)
{
try
{
var request = service.Webfonts.List();
return request.Execute();
}
catch (Exception ex)
{
Console.WriteLine("Request Failed " + ex.Message);
throw ex;
}
}
Putting it all together
var service = StandardGoogleAuth.AuthenticatePublic("Your Public API key");
var result = WebfontsSample.list(service);
if (result.Items != null)
{
foreach (var font in result.Items)
{
Console.WriteLine(font.Family);
}
}
I want to add logging of exceptions to my Windows Store App. Based on an idea from here, I've started off with this code in App.xaml.cs:
sealed partial class App : Application
{
private LoggingChannel channel;
private LoggingSession session;
/// <summary>
/// Initializes the singleton application object. This is the first line of authored code
/// executed, and as such is the logical equivalent of main() or WinMain().
/// </summary>
public App()
{
this.InitializeComponent();
this.Suspending += OnSuspending;
channel = new LoggingChannel("PlatypiChannel");
session = new LoggingSession("PlatypiSession");
session.AddLoggingChannel(channel, LoggingLevel.Error);
UnhandledException += Application_UnhandledException;
}
async private void Application_UnhandledException(object sender, UnhandledExceptionEventArgs ex)
{
ex.Handled = true;
String exceptionCornucopia = String.Format("Message0 == {0}; Message1 == {1}; HResult == {2}; Inner Ex == {3}; StackTrace == {4}", ex.Message, ex.Exception.Message, ex.Exception.HResult, ex.Exception.InnerException, ex.Exception.StackTrace);
channel.LogMessage(exceptionCornucopia, LoggingLevel.Error);
// not seeing how this saves the channel's logged messages...???
StorageFile logFile = await session.SaveToFileAsync(ApplicationData.Current.LocalFolder, "CrashLog");
}
As the comment indicates, it seems to me the last line simply saves a file named "CrashLog" to the LocalFolder. But how do the LogMessages get into that file? There is obviously a key piece missing here.
I know that this question has been open for a long time, but I just want to provide an answer for anyone else finding this.
The secret here is that the LogMessages are all written into the LoggingChannel, which itself has previously been registered with the LoggingSession:
session.AddLoggingChannel(channel, LoggingLevel.Error);
When the session is then saved to a file, it obviously knows about the associated channels and where to search for pending log messages.
I have checked all posts here, but can't find a solution for me so far.
I did setup a small service that should only watch if my other services I want to monitor runs, and if not, start it again and place a message in the application eventlog.
The service itself works great, well nothing special :), but when I start the service it use around 1.6MB of RAM, and every 10 seconds it grow like 60-70k which is way to much to live with it.
I tried dispose and clear all resources. Tried work with the System.Timers instead of the actual solution, but nothing really works as I want it, memory still grows.
No difference in debug or release version and I am using it on .Net 2, don't know if it make a difference to you 3,3.5 or 4.
Any hint?!
using System;
using System.IO;
using System.Diagnostics;
using System.ServiceProcess;
using System.Threading;
using System.Timers;
namespace Watchguard
{
class WindowsService : ServiceBase
{
Thread mWorker;
AutoResetEvent mStop = new AutoResetEvent(false);
/// <summary>
/// Public Constructor for WindowsService.
/// - Put all of your Initialization code here.
/// </summary>
public WindowsService()
{
this.ServiceName = "Informer Watchguard";
this.EventLog.Source = "Informer Watchguard";
this.EventLog.Log = "Application";
// These Flags set whether or not to handle that specific
// type of event. Set to true if you need it, false otherwise.
this.CanHandlePowerEvent = false;
this.CanHandleSessionChangeEvent = false;
this.CanPauseAndContinue = false;
this.CanShutdown = false;
this.CanStop = true;
if (!EventLog.SourceExists("Informer Watchguard"))
EventLog.CreateEventSource("Informer Watchguard", "Application");
}
/// <summary>
/// The Main Thread: This is where your Service is Run.
/// </summary>
static void Main()
{
ServiceBase.Run(new WindowsService());
}
/// <summary>
/// Dispose of objects that need it here.
/// </summary>
/// <param name="disposing">Whether or not disposing is going on.</param>
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
}
/// <summary>
/// OnStart: Put startup code here
/// - Start threads, get inital data, etc.
/// </summary>
/// <param name="args"></param>
protected override void OnStart(string[] args)
{
base.OnStart(args);
MyLogEvent("Init");
mWorker = new Thread(WatchServices);
mWorker.Start();
}
/// <summary>
/// OnStop: Put your stop code here
/// - Stop threads, set final data, etc.
/// </summary>
protected override void OnStop()
{
mStop.Set();
mWorker.Join();
base.OnStop();
}
/// <summary>
/// OnSessionChange(): To handle a change event from a Terminal Server session.
/// Useful if you need to determine when a user logs in remotely or logs off,
/// or when someone logs into the console.
/// </summary>
/// <param name="changeDescription"></param>
protected override void OnSessionChange(SessionChangeDescription changeDescription)
{
base.OnSessionChange(changeDescription);
}
private void WatchServices()
{
string scName = "";
ServiceController[] scServices;
scServices = ServiceController.GetServices();
for (; ; )
{
// Run this code once every 10 seconds or stop right away if the service is stopped
if (mStop.WaitOne(10000)) return;
// Do work...
foreach (ServiceController scTemp in scServices)
{
scName = scTemp.ServiceName.ToString().ToLower();
if (scName == "InformerWatchguard") scName = ""; // don't do it for yourself
if (scName.Length > 8) scName = scName.Substring(0, 8);
if (scName == "informer")
{
ServiceController sc = new ServiceController(scTemp.ServiceName.ToString());
if (sc.Status == ServiceControllerStatus.Stopped)
{
sc.Start();
MyLogEvent("Found service " + scTemp.ServiceName.ToString() + " which has status: " + sc.Status + "\nRestarting Service...");
}
sc.Dispose();
sc = null;
}
}
}
}
private static void MyLogEvent(String Message)
{
// Create an eEventLog instance and assign its source.
EventLog myLog = new EventLog();
myLog.Source = "Informer Watchguard";
// Write an informational entry to the event log.
myLog.WriteEntry(Message);
}
}
}
Your code may throw an exceptions inside loop, but these exception are not catched. So, change the code as follows to catch exceptions:
if (scName == "informer")
{
try {
using(ServiceController sc = new ServiceController(scTemp.ServiceName.ToString())) {
if (sc.Status == ServiceControllerStatus.Stopped)
{
sc.Start();
MyLogEvent("Found service " + scTemp.ServiceName.ToString() + " which has status: " + sc.Status + "\nRestarting Service...");
}
}
} catch {
// Write debug log here
}
}
You can remove outer try/catch after investigating, leaving using statement to make sure Dispose called even if exception thrown inside.
At a minimum, you need to do this in your logging code since EventLog needs to be Dispose()d. Seems like this resource could be reused rather than new-ed on every call. You could also consider using in your main loop for the ServiceController objects, to make your code more exception-safe.
private static void MyLogEvent(String Message)
{
// Create an eEventLog instance and assign its source.
using (EventLog myLog = new EventLog())
{
myLog.Source = "Informer Watchguard";
// Write an informational entry to the event log.
myLog.WriteEntry(Message);
}
}
This should be moved into the loop, since you don't want to keep a reference to old service handles for the life of your service:
ServiceController[] scServices = ServiceController.GetServices();
You also want to dispose of your reference to EventLog and to the ServiceController instances. As Artem points out, watch out for exceptions that are preventing you from doing this.
Since memory is going up every 10 seconds, it has to be something in your loop.
If memory goes up whether or not you write to the EventLog, then that is not the main problem.
Does memory used ever come down? Ie does the garbage collector kick in after awhile? You could test the GC's effect by doing a GC.Collect() before going back to sleep (though I'd be careful of using it in production).
I am not sure I understand the problem exactly. Is the service you are going to be monitoring always the same. It would appear from your code that the answer is yes, and if that is the case then you can simply create the ServiceController class instance passing the name of the service to the constructor.
In your thread routine you want to continue looping until a stop is issued, and the WaitOne method call returns a Boolean, so a while loop seems to be appropriate. Within the while loop you can call the Refresh method on the ServiceController class instance to get the current state of the service.
The event logging should simple require a call one of the static method EventLog.WriteEntry methods, at minimum passing your message and the source 'Informer Watchguard'
The ServiceController instance can be disposed when you exit from the loop in the thread routine
All this would mean you are creating fewer objects that need to be disposed, and therefore less likely that some resource leak will exist.
Thanks to all suggestions.
Finally the service is stable now with some modifications.
#Steve: I watch many services all beginning with the same name "Informer ..." but I don't know exactly full names, that's why I go this way.