multithreadingfilesystemwatchershared-directorywindows-share

FileSystemWatcher reports file available on network share but file cannot be found


BACKGROUND

I have a server that has a shared folder \\Server\Share with 4 subfolders:

All folders reside on the same physical disk and partition, no junction points used.

I also have several WinForms clients (up to 10) that write and read files to this share, each client is working on multiple threads (up to 5). Files are witten by clients (up to 50 threads altogether) into the \\Server\Share\OutboundStaging folder. Each file has the name of a GUID, so there's no overwriting. Once a file is completely written, it is moved by the client to the \\Server\Share\OutboundFinal folder. A Windows service running on the same server will pick it up, delete it, process it, then writes the file with the same name into the \\Server\Share\InboundStaging folder. Once the file is completely written, it is moved to the \\Server\Share\InboundFinal folder by the service.

This \\Server\Share\InboundFinal folder is monitored by each thread of each WinForms client using a FileSystemWatcher.WaitForChanged(WatcherChangeTypes.Changed | WatcherChangeTypes.Created, timeOut); The FileSystemWatcher.Filter is set to the GUID filename of the file a certain thread expects to see in the \Server\Share\InboundFinal folder, so the FileSystemWatcher waits until a specific file is shown in the folder.

I have read several SO questions about FileSystemWatcher behaving erratically and not reporting changes on UNC shares. This is however not the case for me.

The code I use looks like this:

    FileSystemWatcher fileWatcher = new FileSystemWatcher();
    fileWatcher.Path = InboundFinalFolder;
    fileWatcher.Filter = GUIDFileName; // contains full UNC path AND the file name
    fileWatcher.EnableRaisingEvents = true;
    fileWatcher.IncludeSubdirectories = false;
    var res = fileWatcher.WaitForChanged(WatcherChangeTypes.Changed | WatcherChangeTypes.Created, timeOut);
    if (!fileWatcher.TimedOut)
    {
        using (FileStream stream = fi.Open(FileMode.Open, FileAccess.Read, FileShare.Read)) {
        byte[] res = new byte[stream.Length];
        stream.Read(res, 0, stream.Length);
        return res;
    }

It's the using line that throws the exception.

THE PROBLEM

I would assume that the fileWatcher.WaitForChanged would go on only if the file with the proper GUID name is in the \\Server\Share\InboundFinal folder. This is exactly how FileSystemWatcher works on local folders, but not with file shares accessed over the network (local files, even accessed via a share, also tend to work). FileSystemWatcher reports that the file the thread is waiting for is in the FileSystemWatcher \\Server\Share\InboundFinal folder. However, when I try to read the file, I get a FileNotFoundException. The reading thread has to wait 3-15 seconds before the file can be read. I try to open the file with a FileStream with Read sharing.

What could cause this behavior? How do I work around it? Ideally the FileSystemWatcher.WaitForChanged(WatcherChangeTypes.Changed | WatcherChangeTypes.Created, timeOut); should only continue execution if the file can be read or timeout happens.


Solution

  • The FileSystemWatcher has a bad reputation, but actually, it is not that bad...

    1.)

    Your code sample does not compile. I tried this:

     FileSystemWatcher fileWatcher = new FileSystemWatcher();
     fileWatcher.Path = "X:\\temp";
     fileWatcher.Filter = "test.txt";
     fileWatcher.EnableRaisingEvents = true;
     fileWatcher.IncludeSubdirectories = false;
    
     var res = fileWatcher.WaitForChanged(WatcherChangeTypes.Changed |
                                     WatcherChangeTypes.Created, 20000);
     if (!res.TimedOut)
     {
         FileInfo fi = new FileInfo(Path.Combine(fileWatcher.Path, res.Name));
    
         using (FileStream stream = fi.Open(FileMode.Open, FileAccess.Read, FileShare.Read))
         {
             byte[] buf = new byte[stream.Length];
    
             stream.Read(buf, 0, (int)stream.Length);
         }
    
         Console.WriteLine("read ok");
     }
     else
     {
         Console.WriteLine("time out");
     }
    

    I tested this where X: is a SMB share. It worked without problems (for me, see below).

    But:

    You should open / read the file with retries (sleeping for 100 ms after every unsuccessfully open). This is because you may run into a situation where the FileSystemWatcher detects a file, but the move (or another write operation) has not yet ended, so you have to wait until the file create / mover is really ready.

    Or you do do not wait for the "real" file but for a flag file which the file move task creates after closing the "real" file.

    2.)

    Could it be that the move task did not close the file correctly?

    3.)

    Some years ago I had some tools (written in perl) where one script created a flag file and another script waited for it.

    I had some nasty problems on a SMB 2 share. I found out that this was due to SMB caching.

    See

    https://bogner.sh/2014/10/how-to-disable-smb-client-side-caching/

    File open fails initially when trying to open a file located on a win2k8 share but eventually can succeeed

    https://technet.microsoft.com/en-us/library/ff686200.aspx

    Try this (on the client):

    Windows Registry Editor Version 5.00
    
    [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\LanmanWorkstation\Parameters]
    
    "DirectoryCacheLifetime"=dword:00000000
    "FileNotFoundCacheLifetime"=dword:00000000
    

    Save this as disablecache.reg and run regedit disablecache.reg

    Then reboot.