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.
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/
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.