Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to tell if a caught IOException is caused by the file being used by another process, without resorting to parsing the exception's Message property

When I open a file, I want to know if it is being used by another process so I can perform special handling; any other IOException I will bubble up. An IOException's Message property contains "The process cannot access the file 'foo' because it is being used by another process.", but this is unsuitable for programmatic detection. What is the safest, most robust way to detect a file being used by another process?

like image 350
Paul Killick Avatar asked Apr 02 '10 19:04

Paul Killick


People also ask

How do you handle system IO IOException?

Handling IOException This means that it can be thrown by any I/O operation. Because IOException is the base class of the other exception types in the System.IO namespace, you should handle in a catch block after you've handled the other I/O-related exceptions.

Why do we get IOException?

IOException is thrown when an error occurred during an input-output operation. That can be reading/writing to a file, a stream (of any type), a network connection, connection with a queue, a database etc, pretty much anything that has to do with data transfer from your software to an external medium.

What is a System IO IOException?

IOException is the base class for exceptions thrown while accessing information using streams, files and directories. The Base Class Library includes the following types, each of which is a derived class of IOException : DirectoryNotFoundException. EndOfStreamException. FileNotFoundException.


2 Answers

This particular version of IOException is thrown when the error code returned from the Win32 native function is ERROR_SHARING_VIOLATION (Documentation). It has the numeric value of 0x20 but is actually stored as 0x80070020 on the HRESULT property of the exception (it's the result of calling MakeHRFromErrorCode).

So the programatic way of checking for a sharing violation is checking the HResult property on the IOException for the value 0x80070020.

public static bool IsSharingViolation(this IOException ex) {
  return 0x80070020 == Marshal.GetHRForException(ex);
}

However I do question what exactly you want to do in the scenario that it was thrown as the result of a sharing violation. The moment the exception is thrown the other process could exit and hence remove the violation.

like image 140
JaredPar Avatar answered Sep 30 '22 23:09

JaredPar


I don't have enough "rep" to comment so hopefully this "answer" is OK...

The accepted answer is exactly what I was looking for and works perfectly, but folks here and on similar questions have questioned the utility of checking if a file is locked. It's correct that a utility function to test if a file is locked isn't much use because on the very next statement the status could have changed.

But the pattern of attempting a lock operation and then responding differently to lock errors vs. general errors is valid and useful. The most obvious thing to do is to wait for a little bit and retry the operation. This can be generalized into a helper function like this:

protected static void RetryLock(Action work) {
    // Retry LOCK_MAX_RETRIES times if file is locked by another process
    for (int i = 1; i <= LOCK_MAX_RETRIES; i++) {
        try {
            work();
            return;
        } catch (IOException ex) {
            if (i == LOCK_MAX_RETRIES || (uint) ex.HResult != 0x80070020) {
                throw;
            } else {
                // Min should be long enough to generally allow other process to finish
                // while max should be short enough such that RETRIES * MAX isn't intolerable
                Misc.SleepRandom(LOCK_MIN_SLEEP_MS, LOCK_MAX_SLEEP_MS);
            }
        }
    }
} // RetryLock

...which can then be used like this:

public string DoSomething() {
    string strReturn = null;
    string strPath = @"C:\Some\File\Path.txt";
    // Do some initial work...

    //----------------------------------------------------------------------------------------------------
    // NESTED FUNCTION to do main logic, RetryLock will retry up to N times on lock failures
    Action doWork = delegate {
        using (FileStream objFile = File.Open(strPath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None)) {
            // Does work here if lock succeeded, else File.Open will throw ex
            strReturn = new StreamReader(objFile).ReadLine();
        }
    }; // delegate doWork
    //----------------------------------------------------------------------------------------------------

    RetryLock(doWork); // Throws original ex if non-locking related or tried max times
    return strReturn;
}

...anyway, just posting in case someone with similar needs finds the pattern useful.

like image 44
modal_dialog Avatar answered Oct 01 '22 00:10

modal_dialog