4

Resolution from comments:
Application crash was caused by another issue

I am reading/writting to a file from 2 different applications, when the file is being read or written to, it will always be locked by app A or B and they both make use of FileShare.None.

My issue is that even wrapping the reader around try/catch it still crashes the application with IOException at the using line (does not happen with the writter).

I have also made the catch as catch (IOException ... which I believe makes no difference other then make it more readable.

What is the correct way to ignore when the file is locked and keep trying until the file is available ?

while (true)
{
    try
    {
        using (FileStream stream = new FileStream("test_file.dat", FileMode.Open, FileAccess.Read, FileShare.None))
        {
            using (TextReader reader = new StreamReader(stream))
            {
                // bla bla bla does not matter
            }
        }
    }
    catch
    {
        // bla bla bla does not matter again
    }
    Thread.Sleep(500);
}

Write

private bool WriteData(string data)
{
    try
    {
        using (FileStream stream = new FileStream("test_file.dat", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None))
        {
            stream.SetLength(0);
            using (TextWriter writer = new StreamWriter(stream))
            {
                writer.Write(data);
            }
        }
        return true;
    }
    catch
    {
        return false;
    }
}

Please note that I am not giving share rights (both writer and reader use FileShare.None) to anyone when the file is being used for whatever process be it reading or writting so basically I am handling the exception until the file is available which is not working.

Eugeniu Torica
  • 7,484
  • 12
  • 47
  • 62
Prix
  • 19,417
  • 15
  • 73
  • 132
  • When you say "crash", do you mean that it actually crashes when you run it "standalone", or just that it shows the exception being thrown inside the debugger? – Joachim Isaksson Oct 14 '12 at 10:15
  • Running standalone it crashes "Application stop running" and last error message I get prior the crash on the error log file is related to the file being locked: `System.IO.IOException: The process cannot access the file 'long_path_of_where_the_file_was' because it is being used by another process.` – Prix Oct 14 '12 at 10:18
  • How about using the ReaderWriterLock Class ? – tazyDevel Oct 14 '12 at 10:19
  • @tazyDevel vague, could you give me more info ? – Prix Oct 14 '12 at 10:20
  • 1
    There are only two ways I can see you getting that exception; either you're using `throw` inside your catch clause (not sure if you left the code inside the catch out or it's actually empty), or the file is being accessed in more than one place so this isn't the actual code throwing the exception. The existing catch should definitely catch `IOException`. – Joachim Isaksson Oct 14 '12 at 10:24
  • Can you confirm that you have multiple processes accessing the file and not multiple threads? – nick_w Oct 14 '12 at 10:28
  • inside the catch all I have it the logger function aka `_logger.Error("Houston we have a problem...");` – Prix Oct 14 '12 at 10:28
  • @NickW yes I can, more specific because I don't have multiple threads running on either of the applications. – Prix Oct 14 '12 at 10:29
  • 1
    Just a silly double check, it's not actually the log file that's locked? – Joachim Isaksson Oct 14 '12 at 10:29
  • Could you post the code from the other application that is accessing the file? – nick_w Oct 14 '12 at 10:30
  • See MSDN http://msdn.microsoft.com/en-us/library/system.threading.readerwriterlock%28v=vs.100%29.aspx. It allows to specify timeout on lock and catch exception and perform some action against the exception – tazyDevel Oct 14 '12 at 10:32
  • The code for read is the same, and I will update with the writting code, but they arent any different from app A to B or what I have mentioned. – Prix Oct 14 '12 at 10:33
  • @JoachimIsaksson no, the log file is unique and also allows multithread read/write but its not being used from multiple threads and each application have its own log file. – Prix Oct 14 '12 at 10:37
  • Do you access this file anywhere else? It does seem very odd that all accceses are wrapped in try/catch blocks yet the exceptions still occur. – nick_w Oct 14 '12 at 10:44
  • its still on my vs project so I havent used it anywhere else then locally for test and the files are uniques as in no related to any other processes. – Prix Oct 14 '12 at 10:46
  • What I meant was, is there any other place in the code for either of your programs that access the file? If so then that may be the culprit. – nick_w Oct 14 '12 at 10:50
  • Oh no, its a single thread of sequential functions so basically you press a start button on app A one at app B and a thread will start and all the work is done in there, there is no multiple threads nothing its rather simple functions in sequence and it does not spawn any other threads from inside or anything alike either. – Prix Oct 14 '12 at 10:53
  • To satisfy a curiosity, would you be able to post the exception stack trace you were getting? – nick_w Oct 14 '12 at 10:57
  • I've posted it on the comments but here it is again `System.IO.IOException: The process cannot access the file 'long_path_of_where_the_file_was' because it is being used by another process.` – Prix Oct 14 '12 at 11:11
  • I was really looking for the full stack trace that includes the exact file name and the method and line of code it all happened on. – nick_w Oct 14 '12 at 11:17
  • @NickW went out refreshed my mind, then looked at the code again, and started thinking to myself, if the catch is outputing and the app still crash it should be obvious it was not crashing at the try/catch then I've made the whole things to capture the error and output the stacktrace when running standalone to debug and figured out it was crashing a line after it :P im terrible sorry to wasting everyones time, should I mark some ones answer that is more adequate to this or vote this to close or delete ? – Prix Oct 14 '12 at 21:54
  • @Prix You should change the title of the question because it is really misleading. – Eugeniu Torica Feb 07 '14 at 09:55
  • @Jenea you have enough reputation to edit it yourself if you feel something could be better written. – Prix Feb 07 '14 at 16:05
  • @Prix Oh - thank you. I didn't notice I have this right. – Eugeniu Torica Feb 09 '14 at 19:11

6 Answers6

5

Here is the code we use for this purpose.

/// <summary>
/// Executes the specified action. If the action results in a file sharing violation exception, the action will be
/// repeatedly retried after a short delay (which increases after every failed attempt).
/// </summary>
/// <param name="action">The action to be attempted and possibly retried.</param>
/// <param name="maximum">Maximum amount of time to keep retrying for. When expired, any sharing violation
/// exception will propagate to the caller of this method. Use null to retry indefinitely.</param>
/// <param name="onSharingVio">Action to execute when a sharing violation does occur (is called before the waiting).</param>
public static void WaitSharingVio(Action action, TimeSpan? maximum = null, Action onSharingVio = null)
{
    WaitSharingVio<bool>(() => { action(); return true; }, maximum, onSharingVio);
}

/// <summary>
/// Executes the specified function. If the function results in a file sharing violation exception, the function will be
/// repeatedly retried after a short delay (which increases after every failed attempt).
/// </summary>
/// <param name="func">The function to be attempted and possibly retried.</param>
/// <param name="maximum">Maximum amount of time to keep retrying for. When expired, any sharing violation
/// exception will propagate to the caller of this method. Use null to retry indefinitely.</param>
/// <param name="onSharingVio">Action to execute when a sharing violation does occur (is called before the waiting).</param>
public static T WaitSharingVio<T>(Func<T> func, TimeSpan? maximum = null, Action onSharingVio = null)
{
    var started = DateTime.UtcNow;
    int sleep = 279;
    while (true)
    {
        try
        {
            return func();
        }
        catch (IOException ex)
        {
            int hResult = 0;
            try { hResult = (int) ex.GetType().GetProperty("HResult", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(ex, null); }
            catch { }
            if (hResult != -2147024864) // 0x80070020 ERROR_SHARING_VIOLATION
                throw;
            if (onSharingVio != null)
                onSharingVio();
        }

        if (maximum != null)
        {
            int leftMs = (int) (maximum.Value - (DateTime.UtcNow - started)).TotalMilliseconds;
            if (sleep > leftMs)
            {
                Thread.Sleep(leftMs);
                return func(); // or throw the sharing vio exception
            }
        }

        Thread.Sleep(sleep);
        sleep = Math.Min((sleep * 3) >> 1, 10000);
    }
}

Example of use:

Utilities.WaitSharingVio(
    action: () =>
    {
        using (var f = File.Open(file, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None))
        {
            // ... blah, process the file
        }
    },
    onSharingVio: () =>
    {
        Console.WriteLine("Sharing violation. Trying again soon...");
    }
);
Timwi
  • 65,159
  • 33
  • 165
  • 230
  • 1
    It is a shame that the IOException needs to be interpreted using private reflection. There just is no other way to find out programmatically what happened. Awful. – usr Oct 14 '12 at 11:32
  • +1 While this is not exactly what I needed it does show me some interesting ideas, very appreciated. – Prix Oct 14 '12 at 21:57
  • Why are using reflection to get the value of `HResult` when it's a normal property of the `IOException`? – t3chb0t Oct 22 '15 at 18:56
  • @t3chb0t: It isn’t, [it’s protected](https://msdn.microsoft.com/en-us/library/system.exception.hresult%28v=vs.100%29.aspx). – Timwi Oct 24 '15 at 14:01
  • 1
    @Timwi oh, then they must have changed it because I was looking [here](https://msdn.microsoft.com/en-us/library/system.exception.hresult(v=vs.110).aspx) and in .NET 4.5 it's public ;-) I didn't know it wasn't always like that. – t3chb0t Oct 24 '15 at 14:12
2

I did this once using info from Is there a global named reader/writer lock?.

I suppose the outcome is somewhat like a ReaderWriterLockSlim that works in the case where multiple processes, not threads, are accessing a resource.

Community
  • 1
  • 1
nick_w
  • 14,758
  • 3
  • 51
  • 71
1

You can use a mutex object to protect a shared resource from simultaneous access by multiple threads or processes.

Daniil Grankin
  • 3,841
  • 2
  • 29
  • 39
  • Well I am not looking for a workaround I would like to know why the method is not working as it should. – Prix Oct 14 '12 at 10:22
  • I answered for you question - "What is the correct way to ignore when the file is locked and keep trying until the file is available ?" – Daniil Grankin Oct 14 '12 at 10:25
  • Thats because you have ignored the meaning of why I posted the code, ofc the reason I posted the code was to find out if the problem was within it and what would be the proper way to make it in regards my sample ;) – Prix Oct 14 '12 at 21:56
1

You can check for your file lock by writing the function as below:

protected bool IsFileLocked(FileInfo file)
{
    FileStream stream = null;

    try
    {
        stream = file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None);
    }
    catch (IOException)
    {
        //the file is unavailable because it is:
        //still being written to
        //or being processed by another thread
        //or does not exist (has already been processed)
        return true;
    }
    finally
    {
        if (stream != null)
            stream.Close();
    }

    //file is not locked
    return false;
}
  • 1
    Problem is that the function isn't very helpful, it can return false, but in between `Close()`and the time the caller checks the boolean, the other process may have opened it for writing. – Joachim Isaksson Oct 14 '12 at 10:27
  • I don't really see a difference from the using to this, I mean the using would only open the file if it is available right ? and if it was not it would throw as it is hence I am avoiding the close/finally need. – Prix Oct 14 '12 at 10:31
  • What about `FileNotFoundException`? It's inherited from `IOException` and means something entirely different. – gimbar Jul 23 '15 at 12:52
0

The answer from Timwi helped us alot (albeit in another context) but I found that the flag "BindingFlags.Public" also needs to be added if you want to fetch HResult from all IOExceptions:

public static int GetHresult(this IOException ex)
{
   return (int)ex.GetType().GetProperty("HResult", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).GetValue(ex, null);
}
-1

use reader & writer lock
correct syntax of catch to

catch(Exception e)


while (true)
{
    try
    {
        using (FileStream stream = new FileStream("test_file.dat", FileMode.Open, FileAccess.Read, FileShare.None))
        {
            using (TextReader reader = new StreamReader(stream))
            {
                // bla bla bla does not matter
            }
        }
    }
    catch(Exception e)
    {
        // bla bla bla does not matter again
    }
    Thread.Sleep(500);
}
Ravindra Bagale
  • 17,226
  • 9
  • 43
  • 70
  • @Prix, I read elsewhere that you need to actually specify a value e like this answer suggests. Not clear to me why (it's not the case in python or c++). Did you try it? – Johan Lundberg Oct 14 '12 at 10:19
  • 1
    `catch` and `catch(Exception e)` are equivalent, the only difference being the `Exception` can be referenced in the latter example. See [related answer](http://stackoverflow.com/a/10806013/921321). – Lukazoid Oct 14 '12 at 10:20