4

I am currently experiencing a serious problem. I am scanning files in a directory, then I process them [read contents with File.ReadAllText(fi.FullName)], and then deleting the file. The thread sleeps for 200 ms, and starts again (scan, process, delete). The issue is that sometimes, I can see that a file that has been already deleted it appears in the next scan, this does not happen always, only occasionally.

List<FileInfo> files = GetFiles();
if (files != null)
{
    foreach (FileInfo fi in files)
    {
       if (ProcessFile(fi))
       {
           fi.Delete();
           log.Info("Report file: " + fi.FullName + " has been deleted");
       }
   }
}

And here is the GetFiles method

internal List<FileInfo> GetFiles()
{
    try
    {
        DirectoryInfo info = new DirectoryInfo(scanDir);
        List<FileInfo> files = info.GetFiles().OrderBy(p => p.CreationTime).Take(10).ToList(); //oldest file first
        return files;
    }
    catch (Exception ex)
    {
       log.Error("Getting files from directory: " + scanDir + ". Error: " + ex.ToString());
        return null;
    }
}

I have read in other posts that the FileInfo.Delete() takes some time, but Microsoft documentation does not say anything about this. So I am not sure as to what is happing. Can anybody spot anything wrong with the code? Is there any official documentation as to whether the fileInfo.Delete() is a blocking call? or does it simple marks a file for deletion?

EDIT and here is the only reference to the FileInfo in the ProcessFile

string message = File.ReadAllText(fi.FullName);

I believe that the File.ReadAllText closes the file, and that no handles should be left around please correct me if wrong!...also, this only happens occasionally, and not to all files (I am processing 10 files, and it happens to just 1)

MCS
  • 162
  • 1
  • 13
  • 2
    An alternative solution would be to simply collect the names of the files you want to delete in one pass, then delete all of them in another. This way the scan only happens once and cannot "catch" undeleted files that were supposedly deleted. – KDecker Apr 12 '16 at 16:17
  • 2
    Possible duplicate of [How to synchronously and consistently delete a folder on NTFS with C#](http://stackoverflow.com/questions/36384368/how-to-synchronously-and-consistently-delete-a-folder-on-ntfs-with-c-sharp) – Visual Vincent Apr 12 '16 at 16:18
  • 3
    Deletes are delayed by open handles so make sure all the execution paths in ProcessFile close it. Exempt your working directory from any A/V software. – Alex K. Apr 12 '16 at 16:18
  • 1
    What is ProcesseFile doing? FileInfo.Delete will not delete the file if it's being accessed by anything else. That's straight from MSDN https://msdn.microsoft.com/en-us/library/system.io.fileinfo.delete(v=vs.110).aspx – DotNetRussell Apr 12 '16 at 16:19
  • 2
    It happens, certainly if you do this at such a high rate. There is too much cr*pware around that wants a piece of such a file as well. Like anti-malware, search indexers, cloud storage utilities. They'll open the file with FileShare.Delete so they won't fail your File.Delete call. But the file will not actually disappear until it is left in peace. You'll get an exception when you open the file again, that's the way to tell that it is in limbo. Not otherwise different from the exception you'll get when the file is not accessible because a normal app is using it. – Hans Passant Apr 12 '16 at 16:23
  • processFile reads contents of file, string conten=File.ReadAllText(fi.FullName), it does not do anything else with the fi object. I believe the readalltext method cleans everything, thus there should not be any reference. – MCS Apr 12 '16 at 16:30
  • Hans Passant according to my log, the file went through the whole loop again, so the File.ReadAllText did not throw an exception – MCS Apr 12 '16 at 17:54

1 Answers1

5

From the Microsoft Reference Source:

   public override void Delete()
    {
#if FEATURE_CORECLR
        FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.Write, DisplayPath, FullPath);
        state.EnsureState();
#else
        // For security check, path should be resolved to an absolute path.
        new FileIOPermission(FileIOPermissionAccess.Write, new String[] { FullPath }, false, false).Demand();
#endif

        bool r = Win32Native.DeleteFile(FullPath);
        if (!r) {
            int hr = Marshal.GetLastWin32Error();
            if (hr==Win32Native.ERROR_FILE_NOT_FOUND)
                return;
            else
                __Error.WinIOError(hr, DisplayPath);
        }
    }

It is shown that the Delete method uses Win32Native.DeleteFile method in kernel32.dll. And further check on the kernel32.dll can be found in this over 500+ pages reference.

In page 79, you could find reference to DeleteFile:

1.49 DeleteFile

The DeleteFile function deletes an existing file.

DeleteFile: procedure
(
lpFileName: string
);
stdcall;
returns( "eax" );
external( "__imp__DeleteFileA@4" );

Parameters

lpFileName
[in] Pointer to a null-terminated string that specifies the file to be deleted.

Windows NT/2000: In the ANSI version of this function, the name is limited to MAX_PATH characters. To extend this limit to nearly 32,000 wide characters, call the Unicode version of the function and prepend "\\?\" to the path. For more information, see File Name Conventions.

Windows 95/98: This string must not exceed MAX_PATH characters.

Return Values

If the function succeeds, the return value is nonzero.
If the function fails, the return value is zero. To get extended error information, call GetLastError.
Remarks
If an application attempts to delete a file that does not exist, the DeleteFile function fails.
To delete or rename a file, you must have either delete permission on the file or delete child permission in the parent directory. 
If you set up a directory with all access except delete and delete child and the ACLs of new files are inherited, then you should be able to create a file without being able to delete it. However, you can then create a file, and you will get all the access you request on the handle returned to you at the time you create the file. 
If you requested delete permission at the time you created the file, you could delete or rename the file with that handle but not with any other.

Windows 95: The DeleteFile function deletes a file even if it is open for normal I/O or as a memory-mapped file. To prevent loss of data, close files before attempting to delete them.

Windows NT/2000: The DeleteFile function fails if an application attempts to delete a file that is open for normal I/O or as a memory-mapped file.

To close an open file, use the CloseHandle function.

MAPI: For more information, see Syntax and Limitations for Win32 Functions Useful in MAPI Development.

Since it is using kernel32.dll, It shares the same mechanism with what we do when we delete file from windows UI.

internal const String KERNEL32 = "kernel32.dll";
...
[DllImport(KERNEL32, SetLastError=true, CharSet=CharSet.Auto, BestFitMapping=false)]
[ResourceExposure(ResourceScope.Machine)]
internal static extern bool DeleteFile(String path);

Thus, this shows that it is not a "blocking" function as you might have suspected. Provided that the file is deletable (there is no access permission error or I/O error) It just takes time to delete.

One workaround for your case would be to try to collect every file which you want to delete first and then delete them together, say, using Async task or something like BackgroundWorker.

Ian
  • 30,182
  • 19
  • 69
  • 107
  • thanks a lot Ian, i have lots to look at!... and yes, I totally thought that it was a "blocking" (synchronous) call ! – MCS Apr 12 '16 at 16:49
  • Since it uses kernel function, *maybe* it is not blocking. But yes, we are also not really sure when a deletion is finished - just like what happen when we delete a file with our `Windows` UI. Sometimes, the deletion can also be delayed/blocked for different reasons. I think @HansPassant 's comment on this matter is helpful. – Ian Apr 12 '16 at 16:53
  • It is synchronous *unless* there is an open handle. The shell calls it via IFileOperation (or SHFileOperation given the age of that HLA doc). – Alex K. Apr 12 '16 at 16:55
  • Alex K, if the fileinfo.delete is synchronous, does it mean that my code is wrong and I still have an open handle somewhere? – MCS Apr 12 '16 at 17:51
  • As mentioned in the comment by Hans other external processes could be interfering. – Alex K. Apr 12 '16 at 23:42