Reputation: 41
i have a strange problem with File.Move method. Generally I have a service, that monitors for new files appearing on remote share and once they do it changes its extension via "ren" cmd function and calls other service. This other service is then using File.Move to change the extension of the file once again. The problem is that from time to time the second service fails and returns error "Could not find file..." from System.IO.File.InternalMove, while the file is already there.
Initially I thought that it may be issue with network connection, but I have a feeling that it happens to often to be caused by network problems.
Next I started digging into .net source code and found something interesting:
if (!InternalExists(fullSourceFileName))
__Error.WinIOError(Win32Native.ERROR_FILE_NOT_FOUND, fullSourceFileName);
if (!Win32Native.MoveFile(fullSourceFileName, fullDestFileName))
{
__Error.WinIOError();
}
This is part of File.InternalMove method which, from my understanding, is returning an "Could not find file..." error. In short InternalExists method is checking if file exists by checking the Marshal.GetLastWin32Error() error and later verifing attributes on file.
Also what is strange is that it seems that this method is using "hack" of some sort (don't know if it is related to my problem, but it is worrying):
// For floppy drives, normally the OS will pop up a dialog saying
// there is no disk in drive A:, please insert one. We don't want that.
// SetErrorMode will let us disable this, but we should set the error
// mode back, since this may have wide-ranging effects.
bool success = false;
int oldMode = Win32Native.SetErrorMode(Win32Native.SEM_FAILCRITICALERRORS);
try {
success = Win32Native.GetFileAttributesEx(path, GetFileExInfoStandard, ref data);
}
finally {
Win32Native.SetErrorMode(oldMode);
}
Now the interestring part :) Class FileInfo also allows to rename files via MoveTo() method. In this method the "moving" part is following:
if (!Win32Native.MoveFile(FullPath, fullDestFileName))
__Error.WinIOError();
It is the same as in File class, but it lacks the "InternalExists" verification, so at a glance it looks like it should resolve my problem, but I am little worried about this missing check.
Does anybody knows why it is implemented like this and if I can use FileInfo.MoveTo method? Also if you have any ideas on the cause of the problem, then please share it. Currently I am quessing that when first service is calling "ren" cmd program, the new file isn't fully renamed yet, which causes File.Move method to fail, but I have to yet verify it.
Edit :) I have tried to change code from File.Move to FileInfo.MoveTo and error still occurred. Next I started to investigate more my suspicion with "ren" method and noticed something interesting - when I run it via c#, then in debugger the command was executed immediately, which seemed quite normal, as we only rename the file. Then when I run the "ren" in cmd it sometimes took up to several seconds.
I then wrote small program that in inifinite loop makes same checks as File.Move method (basically copied the source code of method and some internal classes/structs) and I reproduced the error.
It turned out that once the service launched the bat script with "ren" command, it simply launched it and didn't care about the result. Then if other service tried to read the file quickly enough, it got error, as rename wasn't yet fully done.
I have changed it to wait for bat to finish, but it introduced big performance issues (or rather revealed), so I ended in doing it via File.Move method and I didn't reproduce it anymore.
I will yet wait for few weeks/months to make sure that issue doesn't reoccur, as it wasn't common, and if it doesn't I will close this question.
Upvotes: 4
Views: 758
Reputation: 72298
I'm guessing the error is actually a permissions issue, or possibly a locked file, and that the error returned by InternalExists
isn't actually ERROR_FILE_NOT_FOUND
, as no check is made on it.
Therefore it's a bug that the correct error is not thrown.
Why the Exists check needs to be there is unclear anyway: if it's not there you will get an error anyway when calling MoveFile
.
I suggest you file this as a bug on the GitHub repo. I think it's unlikely to get fixed, but at least it will be documented. If it gets closed as Won't fix
you may want to file a pull request on the documentation noting this.
Note that the current code is actually the following, which implements the same thing in a slightly different way.
if (!FileSystem.FileExists(fullSourceFileName))
{
throw new FileNotFoundException(SR.Format(SR.IO_FileNotFound_FileName, fullSourceFileName), fullSourceFileName);
}
FileSystem.MoveFile(fullSourceFileName, fullDestFileName, overwrite);
Upvotes: 1