views:

160

answers:

6

My Win32 application performs numerous disk operations in a designated temporary folder while functioning, and seriously redesigning it is out of the question.

Some clients have antivirus software that scans the same temporary directory (it simply scans everything). We tried to talk them into disabling it - it doesn't work, so it's out of the question either.

Every once in a while (something like once for every one thousand file operations) my application tries to perform an operation on a file which is at that very time opened by the antivirus and is therefore locked by the operating system. A sharing violation occurs and causes an error in my application. This happens about once in three minutes on average.

The temporary folder can contain up to 100k files in most typical scenarios, so I don't like the idea of having them open at all times because this could cause running out of resources on some edge conditions.

Is there some reasonable strategy for my application to react to situations when a needed file is locked? Maybe something like this?

for( int i = 0; i < ReasonableNumber; i++ ) {
    try {
        performOperation(); // do useful stuff here
        break;
    } catch( ... ) {
        if( i == ReasonableNumber - 1 ) {
            throw; //not to hide errors if unlock never happens
        }
    }
    Sleep( ReasonableInterval );
 }

Is this a viable strategy? If so, how many times and how often should my application retry? What are better ideas if any?

+1  A: 

Could you change your application so you don't release the file handle? If you hold a lock on the file yourself the antivir application will not be able to scan it.

Otherwise a strategy such as yours will help, a bit, because it only reduces the probability but it doesn't solve the problem.

thijs
+1  A: 

Tough problem. Most ideas that I have go into a direction that you don't want (e.g. redesign).

I don't know how many files you have in your directory, but if it's not that much you may be able to work around your problem by keeping all files open and locked while your program runs.

That way the virus scanner will have no chance to interrupt your file-accesses anymore.

Nils Pipenbrinck
+1  A: 

If there is the possibility that some other process - be it the antivirus software, a backup utility or even the user themselves - can open the file, then you must code for that possibility.

Your solution, while perhaps not the most elegant, will certainly work as long as ReasonableNumber is sufficiently large - in the past I've used 10 as the reasonable number. I certainly wouldn't go any higher and you could get away with a lower value such as 5.

The value of sleep? 100ms or 200ms at most

Bear in mind that most of the time your application will get the file first time anyway.

ChrisF
+4  A: 

A virusscanner that locks files while it's scanning them is quite bad. Clients who have virusscanners this bad need to have their brains replaced... ;-)

Okay, enough ranting. If a file is locked by some other process then you can use a "try again" strategy like you suggest. OTOH, do you really need to close and then re-open those files? Can't you keep them open until your process is done? One tip: Add a delay (sleep) when you try to re-open the file again. About 100 ms should be enough. If the virusscanner keeps the file open that long then it's a real bad scanner. Clients with scanners that bad deserve the exception message that they'll see. Typically, try up to three times... -> Open, on failure try again, on second failure try again, on third failure just crash.

Remember to crash in a user-friendly way.

Workshop Alex
With that many files, I wonder if it is the OS which is locking the directory, rather than the VS locking individual files. The VS *should* be opening for shared-delete, which is the most permissive open possible.
James Hugard
+1  A: 

Depends on how big your files are, but for 10s to 100s of Kb I find that 5 trys with 100ms (0.1 seconds) to be sufficient. If you still hit the error once in a while, double the wait, but YMMV.

If you have a few places in the code which needs to do this, may I suggest taking a functional approach:

using System;

namespace Retry
{
    class Program
    {
        static void Main(string[] args)
        {
            int i = 0;
            Utils.Retry(() =>
            {
                i = i + 1;
                if (i < 3)
                    throw new ArgumentOutOfRangeException();
            });
            Console.WriteLine(i);
            Console.Write("Press any key...");
            Console.ReadKey();
        }
    }

    class Utils
    {
        public delegate void Retryable();
        static int RETRIES = 5;
        static int WAIT = 100; /*ms*/
        static public void Retry( Retryable retryable )
        {
            int retrys = RETRIES;
            int wait = WAIT;
            Exception err;
            do
            {
                try
                {
                    err = null;
                    retryable();
                }
                catch (Exception e)
                {
                    err = e;
                    if (retrys != 1)
                    {
                        System.Threading.Thread.Sleep(wait);
                        wait *= 2;
                    }
                }
            } while( --retrys > 0 && err != null );
            if (err != null)
                throw err;
        }
    }
}
James Hugard
100 ms != 0.01 sec
Josh Kelley
Uh, meant 0.1 not .01.
James Hugard
+1  A: 

I've had experience with antivirus software made by both Symantec and AVG which resulted in files being unavailable for open.

A common problem we experienced back in the 2002 time frame with Symantec was with MSDev6 when a file was updated in this sequence:

  1. a file is opened
  2. contents are modified in memory
  3. application needs to commit changes
  4. application creates new tmp file with new copy of file + changes
  5. application deletes old file
  6. application copies tmp file to old file name
  7. application deletes the tmp file

The problem would occur between step 5 and step 6. Symantec would do something to slowdown the delete preventing the creation of a file with the same name (CreateFile returned ERROR_DELETE_PENDING). MSDev6 would fail to notice that - meaning step 6 failed. Step 7 still happened though. The delete of the original would eventually finish. So the file no longer existed on disk!

With AVG, we've been experiencing intermittent problems being able to open files that have just been modified.

Our resolution was a try/catch in a reasonable loop as in the question. Our loop count is 5.

sean e