Monday, November 23, 2015

Check to see if a file is locked by another process. Error: The process cannot access the file 'filelocation' because it is being used by another process.

Sometimes when dealing with scenarios where you need to generate a file and then do something else with said file the code base can tell you the file is created and ready for use but the OS has not released the file lock. This can also occur when trying to grab or do something with a file that is located on a network share that other users may be modifying at any given time. Because of these scenarios AX can give you an error

"The process cannot access the file 'filelocation' because it is being used by another process."


The following code will allow you to check to see if the os has a file in a 'locked' status. I ended up adding a special wait 4 second clause after retrying 10x because I was finding all of the retries were finishing within under 1 second so the system didn't have a chance to finish what it was doing. You can remove the 2nd retry if this clause does not suite your needs.


/// <summary>
///  Check to see if a file a locked
/// </summary>
/// <param name="fileName">
/// File Name + Path
/// </param>
/// <returns>
/// If the file is locked
/// </returns>
/// <remarks>
/// The first 10 trys were executing under 1 sec so I added a sleep if its still locked after 10 trys to be safe.
/// </remarks>
public boolean isFileLocked(FileName fileName)
{
    Counter retryCount;
    System.Exception clrException;
    System.Type exceptionType;
    boolean validRetry;
    boolean isLocked;
   
    new InteropPermission(InteropKind::ClrInterop);
    new FileIOPermission (Filename, 'rw').assert();
    try
    {
        //check to see if we can open the file
        System.IO.File::Open(fileName, System.IO.FileMode::Open);
        CodeAccessPermission::revertAssert();
    }
    catch(Exception::CLRError)
    {
        validRetry = false;
       
        //get the last CLR error and display it to the user
        clrException = CLRInterop::getLastException();
        if(clrException.get_InnerException() != null)
        {
            clrException = clrException.get_InnerException();
            exceptionType = clrException.GetType();
            //check to see if the exception type is a valid one we need to retry
            switch(CLRInterop::getAnyTypeForObject(exceptionType.get_FullName()))
            {
                case 'System.UnauthorizedAccessException':
                    validRetry = true;
                    break;
                case 'System.IO.IOException':
                    validRetry = true;
                    break;
                default:
                    throw error(CLRInterop::getAnyTypeForObject(clrException.get_Message()));
            }
        }
        //check to see if we have a valid file to retry
        if(validRetry)
        {
            ++retryCount;
            if(retryCount <= 10)
            {
                //execute a retry the first 10x right away
                retry;
            }
            else if(retryCount == 11)
            {
                //on the last try sleep for 4 seconds then try again
                sleep(4000);
                retry;
            }
            else
            {
                isLocked = true;
                CodeAccessPermission::revertAssert();
            }
        }
    }
    return isLocked;
}


I am currently using the tool Easy File Locker located here in order to test this code out.

3 comments:

  1. Hi Adam,

    The catch is very generic and will try to load the file more than 10 times even if the file name is not provided. Exception::CLR can be used and then inner exception type checked to see if we need to retry. E.g. (excuse the converting of System.Type to string for checking the exception type. I'm sure there is a better way to do this)

    catch (Exception::CLRError)
    {
    ex = ClrInterop::getLastException();
    if (ex != null)
    {
    ex = ex.get_InnerException();
    if (ex != null)
    {
    systemType = ex.GetType();
    s = systemType.ToString();

    if (s == 'System.IO.IOException')
    {
    info('File IO exception caught. Retry code here');
    }
    else
    {
    error(ex.ToString());
    }
    }
    }
    }

    ReplyDelete
  2. Good catch I did not think about making sure the file name was provided or not as its a required parm but a blank value could still be passed in. Using the clr exception catch would easily catch this scenario so we don't try to check a file that doesn't even exist as a blank value or even if an invalid path was passed in as well.

    ReplyDelete
  3. One of the other things I found was while using the tool 'Easy File Locker' in order to verify the inner exception I was also getting a 'System.UnauthorizedAccessException' so I added that into the catch method as well. I updated my post to reflect the catch changes you suggested. Thanks for taking the time to read this post and suggest an improvement.

    ReplyDelete