简体   繁体   中英

Error: "Cannot open file "20210609.log". The process cannot access the file because it is being used by another process"

I've got some third party code that writes to a log file one line at a time using the code below:

procedure TLog.WriteToLog(Entry: ansistring);
var
    strFile: string;
    fStream: TFileStream;
    strDT: ansistring;
begin
    if ((strLogDirectory<>'') and (strFileRoot<>'')) then
    begin
        if not(DirectoryExists(strLogDirectory)) then
            ForceDirectories(strLogDirectory);
        strFile:=strLogDirectory + '\' + strFileRoot + '-' + strFilename;
        if FileExists(strFile) then
            fStream:=TFileStream.Create(strFile, fmOpenReadWrite)
        else
            fStream:=TFileStream.Create(strFile, fmCreate);
        fStream.Seek(0, soEnd);
        if blnUseTimeStamp then
            strDT:=formatdatetime(strDateFmt + ' hh:mm:ss', Now) + ' ' + Entry + chr(13) + chr(10)
        else
            strDT:=Entry + chr(13) + chr(10);
        fStream.WriteBuffer(strDT[1], length(strDT));
        FreeandNil(fStream);
    end;
end;

This has previously been working fine at a client site but in the last few weeks it is now getting the error in the title.

There is no other process that should have the file open. I suspect it is Anti-Virus but the client claims they have disabled the AntiV and they still get the error.

The error ONLY seems to occur when the code is in a loop and writing lines fast.

WHAT I WANT TO KNOW: Assuming it is not the Anti-Virus (or similar) causing the problem, could it be due to the operating system not clearing a flag (or something similar) before the next time it tries to write to the file?

WHAT I WANT TO KNOW: Assuming it is not the Anti-Virus (or similar) causing the problem, could it be due to the operating system not clearing a flag (or something similar) before the next time it tries to write to the file?

No, this is not a speed issue, or a caching issue. It is a sharing violation, which means there MUST be another open handle to the same file, where that handle has sharing rights assigned (or lack of) which are incompatible with the rights being requested by this code.

For example, if that other handle is not sharing read+write access, then this code will fail to open the file when creating the TFileStream with fmOpenReadWrite . If any handle is open to the file, this code will fail when creating the TFileStream with fmCreate , as that requests Exclusive access to the file by default.

I would suggest something more like this instead:

procedure TLog.WriteToLog(Entry: AnsiString);
var
  strFile: string;
  fStream: TFileStream;
  strDT: AnsiString;
  fMode: Word;
begin
  if (strLogDirectory <> '') and (strFileRoot <> '') then
  begin
    ForceDirectories(strLogDirectory);
    strFile := IncludeTrailingPathDelimiter(strLogDirectory) + strFileRoot + '-' + strFilename;
    fMode := fmOpenReadWrite or fmShareDenyWrite;
    if not FileExists(strFile) then fMode := fMode or fmCreate;
    fStream := TFileStream.Create(strFile, fMode);
    try
      fStream.Seek(0, soEnd);
      if blnUseTimeStamp then
        strDT := FormatDateTime(strDateFmt + ' hh:mm:ss', Now) + ' ' + Entry + sLineBreak
      else
        strDT := Entry + sLineBreak;
      fStream.WriteBuffer(strDT[1], Length(strDT));
    finally
      fStream.Free;
    end;
  end;
end;

However, do note that using FileExists() introduces a TOCTOU race condition . The file might be deleted/created by someone else after the existence is checked and before the file is opened/created. Best to let the OS handle this for you.

At least on Windows, you can use CreateFile() directly with the OPEN_ALWAYS flag ( TFileStream only ever uses CREATE_ALWAYS , CREATE_NEW , or OPEN_EXISTING ), and then assign the resulting THandle to a THandleStream , eg:

procedure TLog.WriteToLog(Entry: AnsiString);
var
  strFile: string;
  hFile: THandle;
  fStream: THandleStream;
  strDT: AnsiString;
begin
  if (strLogDirectory <> '') and (strFileRoot <> '') then
  begin
    ForceDirectories(strLogDirectory);
    strFile := IncludeTrailingPathDelimiter(strLogDirectory) + strFileRoot + '-' + strFilename;
    hFile := CreateFile(PChar(strFile), GENERIC_WRITE, FILE_SHARE_READ, nil, OPEN_ALWAYS, 0, 0);
    if hFile = INVALID_HANDLE_VALUE then RaiseLastOSError;
    try
      fStream := THandleStream.Create(hFile);
      try
        fStream.Seek(0, soEnd);
        if blnUseTimeStamp then
          strDT := FormatDateTime(strDateFmt + ' hh:mm:ss', Now) + ' ' + Entry + sLineBreak
        else
          strDT := Entry + sLineBreak;
        fStream.WriteBuffer(strDT[1], Length(strDT));
      finally
        fStream.Free;
      end;
    finally
      CloseHandle(hFile);
    end;
  end;
end;

In any case, you can use a tool like SysInternals Process Explorer to verify if there is another handle open to the file, and which process it belongs to. If the offending handle in question is being closed before you can see it in PE, then use a tool likeSysInternals Process Monitor to log access to the file in real-time and check for overlapping attempts to open the file.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM