0

We recently went to a CSVFS clustered file server. I have a file watcher that's monitoring 4 directories for OnCreated and OnRenamed events but whenever the node changes, it causes a buffer overflow with the error

Too many changes at once in directory

The the watchers are automatically restarted and the process continues to work but begins writing errors when the OnCreated/OnRenamed events are fired.

Cannot access a disposed object.
Object name: 'FileSystemWatcher'.
at System.IO.FileSystemWatcher.StartRaisingEvents()
at System.IO.FileSystemWatcher.set_EnableRaisingEvents(Boolean value)

In the OnCreated method below, if I was to do this, should it work?

watchit = source as FileSystemWatcher;

I don't actually assign the newly created FileSystemWatcher to watchit anywhere else.

More details/code

The watchers are created via a foreach loop when the process initially starts. FileChange is simply a method that determines the type of change, does a bit of work, then triggers the correct action for the change type.

foreach (string subDir in subDirs)
{
    string localFolder = $"{subDir}";

    watchit = new FileSystemWatcher
                    {
                        Path = localFolder,
                        EnableRaisingEvents = true,
                        IncludeSubdirectories = false,
                        NotifyFilter = NotifyFilters.FileName | NotifyFilters.CreationTime,
                        Filter = watchFor,
                        InternalBufferSize = 65536,
                        SynchronizingObject = null //,
                    };

    watchit.Changed += FileChange;
    watchit.Created += FileChange;
    watchit.Deleted += FileChange;
    watchit.Renamed += OnRename;
    watchit.Error += OnError;
    watchit.EnableRaisingEvents = true;

    watchers.Add(watchit);

    Console.WriteLine($"watching {subDir} for {watchFor}");
}

watchit is a static FileSystemWatcher set globally.

private static async Task<int> OnCreated<T>(object source, FileSystemEventArgs e, string ext)
{
    int insertResult = 0;

    try
    {
        watchit.EnableRaisingEvents = false;
        EventLogWriter.WriteEntry("File: " + e.FullPath + " " + e.ChangeType);
        Console.WriteLine("File: " + e.FullPath + " " + e.ChangeType + " " + DateTime.Now);
        insertResult = await FileHandler.FileHandlers().ConfigureAwait(false);
        watchit.EnableRaisingEvents = true;
        // if (insertResult > 0) File.Delete(e.FullPath);
    }
    catch (Exception ex)
    {
        Logger.Trace($"{ex.Message} {ex.StackTrace} {ex.InnerException}");
        EventLogWriter.WriteEntry($"{ex.Message} {ex.StackTrace} {ex.InnerException}",
        EventLogEntryType.Error);
        watchit.EnableRaisingEvents = true;
    }
    finally
    {
        watchit.EnableRaisingEvents = true;
    }

    return insertResult;
}

These are my error handling methods.

private static void OnError(object source, ErrorEventArgs e)
{
    if (e.GetException().GetType() == typeof(InternalBufferOverflowException))
    {
        EventLogWriter.WriteEntry($"Error: File System Watcher internal buffer overflow at {DateTime.Now}", EventLogEntryType.Warning);
    }
    else
    {
        EventLogWriter.WriteEntry($"Error: Watched directory not accessible at {DateTime.Now}", EventLogEntryType.Warning);
    }

    MailSend.SendUploadEmail($"ASSIST NOTES: {e.GetException().Message}", "The notes service had a failure and should be restarted.", "admins", e.GetException(), MailPriority.High);
    NotAccessibleError(source as FileSystemWatcher, e);
}

    /// <summary>
    /// triggered on accessible error.
    /// </summary>
    /// <param name="source">The source.</param>
    /// <param name="e">The <see cref="ErrorEventArgs"/> instance containing the event data.</param>
    private static void NotAccessibleError(FileSystemWatcher source, ErrorEventArgs e)
        {
        EventLogWriter.WriteEntry($"Not Accessible issue. {e.GetException().Message}" + DateTime.Now.ToString("HH:mm:ss"));

        int iMaxAttempts = 120;
        int iTimeOut = 30000;
        int i = 0;
        string watchPath = source.Path;
        string watchFilter = source.Filter;
        int dirExists = 0;
        try
            {
            dirExists = Directory.GetFiles(watchPath).Length;
            }
        catch (Exception) { }
        try
            {
            while (dirExists == 0 && i < iMaxAttempts)
                {
                i += 1;
                try
                    {
                    source.EnableRaisingEvents = false;
                    if (!Directory.Exists(source.Path))
                        {
                        EventLogWriter.WriteEntry(
                            "Directory Inaccessible " + source.Path + " at " +
                            DateTime.Now.ToString("HH:mm:ss"));
                        Console.WriteLine(
                            "Directory Inaccessible " + source.Path + " at " +
                            DateTime.Now.ToString("HH:mm:ss"));
                        System.Threading.Thread.Sleep(iTimeOut);
                        }
                    else
                        {
                        // ReInitialize the Component
                        source.Dispose();
                        source = null;
                        source = new System.IO.FileSystemWatcher();
                        ((System.ComponentModel.ISupportInitialize)(source)).BeginInit();
                        source.EnableRaisingEvents = true;
                        source.Filter = watchFilter;
                        source.Path = watchPath;
                        source.NotifyFilter = NotifyFilters.FileName | NotifyFilters.CreationTime;
                        source.Created += FileChange;
                        source.Renamed += OnRename;
                        source.Error += new ErrorEventHandler(OnError);
                        ((System.ComponentModel.ISupportInitialize)(source)).EndInit();
                        EventLogWriter.WriteEntry(
                            $"Restarting watcher {watchPath} at " + DateTime.Now.ToString("HH:mm:ss"));
                        dirExists = 1;
                        }
                    }
                catch (Exception error)
                    {
                    EventLogWriter.WriteEntry($"Error trying Restart Service {watchPath} " + error.StackTrace +
                                               " at " + DateTime.Now.ToString("HH:mm:ss"));
                    source.EnableRaisingEvents = false;
                    System.Threading.Thread.Sleep(iTimeOut);
                    }
                }
            //Starts a new version of this console appp if retries exceeded
            //Exits current process
            var runTime = DateTime.UtcNow - Process.GetCurrentProcess().StartTime.ToUniversalTime();
            if (i >= 120 && runTime > TimeSpan.Parse("0:00:30"))
                {
                Process.Start(Assembly.GetExecutingAssembly().Location);
                Environment.Exit(666);
                }
            }
        catch (Exception erw) { }
        }
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Andrew
  • 123
  • 1
  • 9

1 Answers1

0

You are trying to do too much work in the FileSystemWatcher events. The watchers are backed by unmanaged buffers that need to be emptied as quickly as possible to keep up with changes.

Ideally, all the events should be doing is reading some very basic data, like the path that was changed, and the type of change, and throwing that in to queue to be processed on another thread. That other thread can do the heavy lifting, since it won't be blocking the unmanaged change buffer.

Bradley Uffner
  • 16,641
  • 3
  • 39
  • 76