0

I created a windows service and installer that watches a collection of files for changes and copies any file that changes to a destination directory specified in the WatchlistConfig.xml file.

I have a couple issues with the service: 1. It has stopped running on one occasion. (unacceptable) 2. We sometimes have to attempt to start the service several times before it "takes".

I believe issue #1 is probably due to not handling fatal errors in the application. I found a bit of code that I tried to incorporate into the Main() method, but is written for a console app (Application is not a recognized class) and thus is commented out for now. Any idea which is the right class for implementing this in a service?

Issue #2 is most likely a timeout I'm guessing. The watchlist is currently comprised of 9 different files on different machines on the network. Connecting to these sources is not immediate (not all on a single domain). Is there a way to set a different timeout value for service startup?

Here's the relevant code. Additional classes on request.
Thanks in advance.

Edit: mistakenly posted the Main() from the test harness (console) which I use to debug. I've left it in place and add the Program class from the WinSvc Project

    //Console Test harness
        class Program
            {
                [STAThread]
                static void Main(string[] args)
                {
                    //AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
                    //Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(Application_ThreadException);
                    //Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
                    //Application.EnableVisualStyles();
                    //Application.SetCompatibleTextRenderingDefault(false);
                    //Application.Run(new Form1());

                    TimedWatchList twl = new TimedWatchList(new PSU_Config(Helpers.GetConfigFile()));

                    Console.WriteLine("Press \'q\' to quit the sample.");
                    while (Console.Read() != 'q') ;
                }

                static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
                {
                    HandleException((Exception)e.ExceptionObject);
                }

                static void HandleException(Exception e)
                {
                    //Handle/Log Exception Here
                }
                static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)
                {
                    Logger.Loggit(e.Exception.Message);
                }
            }

    //Actual Service
       static class Program
        {
            /// <summary>
            /// The main entry point for the application.
            /// </summary>
            static void Main()
            {
                ServiceBase[] ServicesToRun;
                ServicesToRun = new ServiceBase[] 
                { 
                    new Psu() 
                };
                ServiceBase.Run(ServicesToRun);
            }
        }

      public partial class Psu : ServiceBase
        {
            public Psu()
            {
                InitializeComponent();
                TimedWatchList twl = new TimedWatchList(new PSU_Config(Helpers.GetConfigFile()));
            }

            protected override void OnStart(string[] args)
            {
            }

            protected override void OnStop()
            {
            }
        }


        public class TimedWatchList
        {
            public static PSU_Config Config { get; set; }
            List<WatchFile> WatchList = new List<WatchFile>();

            public TimedWatchList(PSU_Config config)
            {
                Config = config;
                if (Config.PrintDebugMsgs) Logger.Loggit("Attempting to create TimedWatchList object");
                WatchList = WatchListFactory.GetWatchList(Helpers.GetWatchListFile());
                if (Config.PrintDebugMsgs) Logger.Loggit("TimedWatchList created");

                Timer _timer = new Timer();
                _timer.Interval += Config.Interval;
                _timer.Enabled = true;
                // register OnTimedEvent() to fire on each "tick" 
                _timer.Elapsed += OnTimedEvent;
            }

            private void OnTimedEvent(object source, ElapsedEventArgs e) 
            {
                foreach (WatchFile file in WatchList)
                {
                    file.PostOnUpdate();
                }
            }

        }//TimedWatchList class

 internal class WatchFile
    // represents a file that is being watched
    {
    #region Props
        public FileInfo SourceFile { get; set; }
        public DirectoryInfo TargetPath { get; set; }
    #endregion //Props

    #region CTOR
        public WatchFile() { }
        public WatchFile(string fileName, string sourcePath, string destPath)
        {
            SourceFile = new FileInfo(Path.Combine(sourcePath, fileName));
            TargetPath = new DirectoryInfo(destPath);
        }
        public WatchFile(FileInfo sourceFile, DirectoryInfo targetDirectory)
        {
            SourceFile = sourceFile;
            TargetPath = targetDirectory;
        }
    #endregion //CTOR

        public void PostOnUpdate()
        {
            //if (TimedWatchList.Config.PrintDebugMsgs) Logger.Loggit("WatchFile Post Event called for: " + SourceFile.Name);
            //if (TimedWatchList.Config.PrintDebugMsgs) Logger.Loggit("Stored LastModified datetime: " + LastModified);

            string targetPath = String.Format(@"{0}\{1}", TargetPath.FullName, SourceFile.Name);
            {
                try
                {
                    //ensure directory exists
                    if (!Directory.Exists(TargetPath.FullName)) Directory.CreateDirectory(TargetPath.FullName);

                    //ensure file version is current
                    if (!File.Exists(targetPath) || (File.GetLastWriteTime(targetPath) != File.GetLastWriteTime(SourceFile.FullName)))
                    {
                        Logger.Loggit(String.Empty);
                        Logger.Loggit("Attempting to copy: " + SourceFile + " (" + File.GetLastWriteTime(SourceFile.FullName) + ")");
                        SourceFile.CopyTo(targetPath, true);
                        Logger.Loggit("\tCopy posted.\tLastModified: " + File.GetLastWriteTime(targetPath));
                    }

                }
                catch (IOException ioex)
                {
                    Logger.Loggit("Error: " + ioex.Message);
                }
                catch (Exception ex)
                {
                    Logger.Loggit("Error: " + ex.Message);
                }
            }
        }
    }// WatchFile class
JM.
  • 678
  • 12
  • 23

2 Answers2

3

There's really no need to guess; as a service you should be logging your errors to the system event log. Set a top level handler (as you've done), but don't expect to be able to handle it.

If the error was unhandled you're not going to be able to do anything about it there. Log it and exit. Catch the errors you can handle as soon as possible, test and design your code to not break otherwise.

You can set your service to restart automatically after a crash, but that should be a last resort. Bust out your debugger and figure out exactly where the errors are occurring and why. I see a lot of "it's probably [something]" and "it may be [something else]" statements here. Again, there is no good reason to guess; you have tools at your disposal which will help you figure out exactly what is going on.

Ed S.
  • 122,712
  • 22
  • 185
  • 265
  • Whoops, I pasted in the Main() from my Console Test Harness. Not sure how I can debug a service so I've been debugging by referencing the assembly and running it in the console. Is there a way to run & debug the service natively? – JM. Nov 15 '11 at 18:31
  • @JM: At the very least you should be logging unhandled exceptions to the system event log. As for debugging, yeah, you can launch it as a console app. [Here is another SO Question](http://stackoverflow.com/questions/5156427/how-do-you-debug-a-windows-service) on that subject. – Ed S. Nov 15 '11 at 18:35
  • Thx Ed. Is it poor practice to create a custom error/event log? – JM. Nov 15 '11 at 18:42
  • @JM: It's not poor practice to do your own logging, but that should be *in addition to* logging to the event log. If there is a problem with a service or driver on my system I expect it to be logging *something* to the event log in order to help me diagnose the issue. – Ed S. Nov 15 '11 at 18:46
0

You might want to simply wrap your function in a try / catch block to see what you might find.

try
{
  MainAppFunctionality();
}
catch (Exception e)
{
   //Not sure what you are going to do here, it's probably too late
}

I suggest you log to the Windows Event Log at various points in your application as a start so you can start to narrow down the location of the error.

I'm also not sure why you are using Console.Read() from a Windows Service context. As of Vista, there isn't a way for the service to interact with the desktop.

Bryan Crosby
  • 6,486
  • 3
  • 36
  • 55
  • Hi Bryan. I have to admit I really haven't used Windows Event Logs. I thought it would be more user-friendly to write out messages to a custom error log. Is this bad practice? – JM. Nov 15 '11 at 18:41
  • @JM. : I'm not sure if it's helpful or not in your case. But that's what the Windows Event Log is designed for. You can always write to both if you want, but usually the Windows Event Log has all you need. Something is better than nothing for tracking this stuff down. – Bryan Crosby Nov 15 '11 at 18:49