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 like SysInternals Process Monitor to log access to the file in real-time and check for overlapping attempts to open the file.