2

I have a console application that uses a fileSystemWatcher to monitor a mapped network path for new files. When the files appear, they get renamed based upon the content of the files. I want this to run 24/7. It works just fine for a time but I find that after a while the console "pauses" and seems to be waiting for input. If I hit return in the console, the application seems to catch up with all the events that seem to have been in "pending" awaiting some type of input. I can never seem to reproduce this issue to catch it in code where it is pausing or why.

I assumed this had something to do with the file system watcher losing its ability to monitor the network path due to outside factors (server restarting, network issues, etc.) so I recreate the file system watcher ever 30 minutes. I am pretty sure that actually has nothing to do with the situation now.

Essentially what I'm seeing is that the console is happily printing out the updates on the new files found, etc. but then it'll just stop (the program operation, not just the output) and will never continue, until I put focus onto the console app and hit some key and then all the pending events (like the timer elapses and the file system watcher re-creations) all happen.

The question is: Why is this happening, and how do I prevent it?

The nuts and bolts of the program looks like this:

static void Main(string[] args)
{

    RefreshTimer = new Timer(1800000); //30 minutes
    RefreshTimer.Elapsed += RefreshTimer_Elapsed;
    RefreshTimer.Enabled = true;
    writeHelp();
    reCreateWatcher();

    getInput();

}


private static void writeHelp()
{
    Console.WriteLine("HELP:");

    Console.WriteLine(@"");
    Console.WriteLine("RENAMES FILES THAT ARE PLACED INTO THE WORKING FOLDER.");

    Console.WriteLine("*************_Commands:_************");
    Console.WriteLine("'runAll' = will run the program on all files curently existing in the folder.  Note that if a file has already been renamed the program SHOULD attempt the rename, find the file has already been renamed, and move to the next file.");
    Console.WriteLine("'quit' = exits program");
    Console.WriteLine("'help' = shows this spiffy information.");
    Console.WriteLine("[end]");
    Console.WriteLine("");


}

private static void getInput()
{
    Console.Write("Cmd ->");
    string k = Console.ReadLine();
    switch (k)
    {
        case "help":
            writeHelp();
            break;
        case "runAll":
            string[] allFiles = System.IO.Directory.GetFiles(PATH_TO_FILES);
            foreach (string curr in allFiles)
            {
                parseTheFile(curr);
            }
            break;
        case "quit":
            return;
        default:
            Console.WriteLine("Huh?  Unknown command!");
            break;
    }

    getInput();
}

private static void reCreateWatcher()
{

    Console.WriteLine("re-starting watcher...");
    Console.WriteLine("Starting Monitor of: " + PATH_TO_FILES);
    if (TheWatcher != null)
    {
        TheWatcher.Dispose();
    }
    deleteOldFiles();
    try
    {
        TheWatcher = new FileSystemWatcher(PATH_TO_FILES, "*.pdf");
    }
    catch (Exception ex)
    {

        Console.WriteLine("AN ERROR OCCURED WHEN TRYING TO SET UP THE FILE SYSTEM WATCHER ON, '" + PATH_TO_FILES + "'.  THE ERROR WAS: " + ex.Message);
        Console.WriteLine("Hit any key to exit.");
        Console.ReadKey();
        Environment.Exit(0);

    }

    TheWatcher.NotifyFilter = NotifyFilters.LastWrite;
    TheWatcher.Changed += theWatcher_Changed;
    TheWatcher.EnableRaisingEvents = true;
}
static void theWatcher_Changed(object sender, FileSystemEventArgs e)
{
    Console.WriteLine("New file found: " + e.Name);
    parseTheFile(e.FullPath);

}
Mong Zhu
  • 23,309
  • 10
  • 44
  • 76
rune711
  • 351
  • 1
  • 11
  • 2
    Is it possible you've selected text using the mouse in the console? – recursive Jun 16 '16 at 20:24
  • 3
    You have a infinitely recursive program with `getInput()`. If you don't get lucky and the compiler descided to not do tail recursion you are eventually going to get a StackOverflow. Just use a `while(true)` loop instead. – Scott Chamberlain Jun 16 '16 at 20:26
  • No. This will run, sometimes for a day or so, before this behavior happens and there's no way anyone would be able to do that. – rune711 Jun 16 '16 at 20:27
  • Scott- good point, but if there were an overflow would that explain what I'm seeing? – rune711 Jun 16 '16 at 20:28
  • 1
    Note: Before you do `TheWatcher.Dispose();`, unbind the event `TheWatcher.Changed -= theWatcher_Changed;` first, or else the object will be alive because of the event. – Jeroen van Langen Jun 16 '16 at 20:30
  • Jeroen... another good point! But still doesn't explain (I don't think) why this is happening. – rune711 Jun 16 '16 at 20:32
  • @ScottChamberlain or a while until `quit` – Jeroen van Langen Jun 16 '16 at 20:32
  • @rune711 no, A overflow would explain a crash, but not the delayed loading you are seeing. – Scott Chamberlain Jun 16 '16 at 20:33
  • @JeroenvanLangen It would not keep it alive, you have it backwards. when you create a event the thing that is kept aive is the right hand side of the `+=` ie: the insteance of the class that contains `this.theWatcher_Changed`. However, because `theWatcher_Changed` is static there is no instance to be kept alive. The watcher itself will not be kept alive by the event. – Scott Chamberlain Jun 16 '16 at 20:52
  • @ScottChamberlain, you're right, it is only applicable when the event is static. – Jeroen van Langen Jun 16 '16 at 20:58
  • Rewrite your console application as Windows service. It is much more stable. – Alex Kudryashev Jun 16 '16 at 21:04
  • @Alex - I actually tried that as well, but there are separate issues for me with the network permissions on a service, which could be another question... – rune711 Jun 16 '16 at 21:28
  • I would avoid writing windows services. Those are more difficult to debug. I would create a windows forms application (without a form) but only a `NotifyIcon` – Jeroen van Langen Jun 17 '16 at 14:02

2 Answers2

0

To get rid of the Stackoverflow exception (which may cause by calling the GetInput() in the GetInput()) (like Scott said) use a while loop instead of recursive method calling.

It should not be needed to recreate the FileSystemWatcher. You probably get an exception while trying to read the file, because it isn't done writing yet.

An example to use the while loop instead of recursive method calling:

class Program
{
    private static readonly string PATH_TO_FILES = @"d:\temp";

    static void Main(string[] args)
    {
        using (var watcher = new FileSystemWatcher(PATH_TO_FILES, "*.pdf"))
        {
            watcher.Changed += Watcher_Changed;

            WriteHelp();

            while (true)
            {
                Console.Write("Cmd ->");
                string k = Console.ReadLine();
                switch (k)
                {
                    case "help":
                        WriteHelp();
                        break;

                    case "runAll":
                        string[] allFiles = System.IO.Directory.GetFiles(PATH_TO_FILES);
                        foreach (string curr in allFiles)
                        {
                            parseTheFile(curr);
                        }
                        break;

                    case "quit":
                        return;

                    default:
                        Console.WriteLine("Huh?  Unknown command!");
                        break;
                }
            }


        }
    }

    private static void WriteHelp()
    {
        Console.WriteLine("HELP:");

        Console.WriteLine(@"");
        Console.WriteLine("RENAMES FILES THAT ARE PLACED INTO THE WORKING FOLDER.");

        Console.WriteLine("*************_Commands:_************");
        Console.WriteLine("'runAll' = will run the program on all files curently existing in the folder.  Note that if a file has already been renamed the program SHOULD attempt the rename, find the file has already been renamed, and move to the next file.");
        Console.WriteLine("'quit' = exits program");
        Console.WriteLine("'help' = shows this spiffy information.");
        Console.WriteLine("[end]");
        Console.WriteLine("");


    }

    private static void Watcher_Changed(object sender, FileSystemEventArgs e)
    {
        try
        {
            Console.WriteLine("New file found: " + e.Name);
            parseTheFile(e.FullPath);
        }
        catch(Exception exception)
        {
            Console.WriteLine("Exception: " + exception.Message);
        }
    }

    private static void parseTheFile(string fullPath)
    {
        throw new NotImplementedException();
    }
}

The problem is, when in the FileSystemWatcher event an exception ocurs, you never retry it. You might add the filename to a List<string> and create a timer that tries to parse the file, if success, you can remove it from the list. This can be done with the command runAll. instead of directly parsing the files, all files could be added to the list.

Jeroen van Langen
  • 21,446
  • 3
  • 42
  • 57
  • Sorry, but I don't think this is an answer to the question, although @Scott is certainly correct in his advice on the while(true). Due to the way the FileSystemWatcher event fires, even if the file is not completed writing, I incorporated logic into parseTheFile() that does some file lock checks and error handling. However, even if an error were thrown at the event, I don't think it would explain the behavior I'm seeing. – rune711 Jun 17 '16 at 13:28
0

Ah! I think I see what occured, and I'm sorry that the whole program was not posted here because surely others would have seen this right off. As @Scott Chamberlain noted, there was recursion in the getInput() method. However I also noted that within the parseTheFile() also included calls to getInput() depending on some cases like:

static private void parseTheFile(string thePath)
{
     Console.WriteLine("Attempting to parse " + System.IO.Path.GetFileName(thePath));
     string fileText;
     try
     {
        fileText = GetTextFromPdf(thePath);
     }
     catch (Exception ex)
     {
         Console.WriteLine("An error was encountered when trying to parse the file. " + ex.Message);
        getInput(); // <---- WHOOPS!
         return;
      }...

I obviously have some things to learn about events, static methods, and console apps. Essentially what I see happening is that the program starts, goes to getInput() where it is waiting at string k = Console.ReadLine();. However the event then fires for a new file, which then might make another call to getInput() from within the event. That event can not complete not until I hit return, and, additionally, I neglected to unsubscribe that event when recreating the fileSystemWatcher as @Jeroen noted. This explains why when I hit return there is this cascading effect of all the other events? Somehow the console is "pausing" waiting for all these "ReadLines" within those events? I fixed this and ran it last night and all is still well this morning so I will post again if I see the problem again, but I am pretty sure this was it.

rune711
  • 351
  • 1
  • 11