FF2.13. Under heavy transaction load my users sometimes get "File could not be opened. File may be in use by another process." It happens more frequently when they have a virus checker switched on. I have changed the ciReopenSleep constant in ffsrbase.pas from 25ms to 100ms and have one data point which suggests this has cured the problem. This constant is used in FFForceFlushFile procedure to Sleep then retry the CreateFile command in FFOpenFilePrim when it fails the first time with an INVALID_HANDLE_VALUE result. (It might make sense to introduce a sequence of Sleep and retry commands with progressively longer Sleeps into the FFForceFlushFile procedure.) Maybe this is related to "File I/O error on Win2K Advanced Server - ID: 673411" which possibly, only guessing from the error message, might be an earlier incarnation of the same bug.
[Follow-up by OP]
The modifications below seem to have worked so far.
This problem appears to be an interaction with the Symantec Backup Exec
component bundled in the Symantec anti-virus package not the anti-virus
program itself. It is worse when the backup is taking place across a VPN
rather than a LAN. (I guess larger files also take longer and increase
the frequency of occurrence.)
On a LAN almost all the failed attempts to immediately open a file worked
when the program waited 25ms and retried, and the remainder worked when
it waited another 50ms and retried. None took longer than 75ms total.
But on a VPN it sometimes needed longer than 250ms so in the routine
below it does wait and retries for 1000ms (1 second) total.
Because the FFOpenFilePrim32 is now doing the wait and retry I have
removed this from the FFForceFlushFile routine as below. It is best
to have the wait and retry in FFOpenFilePrim32 because it will then
apply to all file opens.
function FFOpenFilePrim32(aName : PAnsiChar;
aOpenMode : TffOpenMode;
aShareMode : TffShareMode;
aWriteThru : Boolean;
aCreateFile : Boolean) : THandle;
var
AttrFlags : TffWord32;
CreateMode : TffWord32;
OpenMode : TffWord32;
ShareMode : TffWord32;
WinError : TffWord32;
function OpenTheFile: THandle;
begin
Result := CreateFile(aName,
OpenMode,
ShareMode,
nil, {!! Security attrs}
CreateMode,
AttrFlags,
0);
end;
begin
{$IFDEF Tracing}
FFAddTrace(foOpen, aName^, succ(StrLen(aName)));
{$ENDIF}
{initialise parameters to CreateFile}
if (aOpenMode = omReadOnly) then
OpenMode := GENERIC_READ
else
OpenMode := GENERIC_READ or GENERIC_WRITE;
if (aShareMode = smExclusive) then
ShareMode := 0
else if (aShareMode = smShareRead) then {!!.06}
ShareMode := FILE_SHARE_READ {!!.06}
else
ShareMode := FILE_SHARE_READ or FILE_SHARE_WRITE;
if aCreateFile then
CreateMode := CREATE_ALWAYS
else
CreateMode := OPEN_EXISTING;
if aWriteThru then
AttrFlags := FILE_ATTRIBUTE_NORMAL or FILE_FLAG_WRITE_THROUGH
else
AttrFlags := FILE_ATTRIBUTE_NORMAL;
Result := OpenTheFile;
if (Result = INVALID_HANDLE_VALUE) then begin
Sleep(20);
Result := OpenTheFile;
if (Result = INVALID_HANDLE_VALUE) then begin
Sleep(80);
Result := OpenTheFile;
if (Result = INVALID_HANDLE_VALUE) then begin
Sleep(150);
Result := OpenTheFile;
if (Result = INVALID_HANDLE_VALUE) then begin
Sleep(250);
Result := OpenTheFile;
if (Result = INVALID_HANDLE_VALUE) then begin
Sleep(500);
Result := OpenTheFile;
if (Result = INVALID_HANDLE_VALUE) then begin
// Give up after 1 second.
// It seems that Symantec Backup Exec may hold a file
// to take a copy after it has been changed and closed.
// A test on a LAN produced no examples that needed longer
// than 75ms, but with the backup operating across a VPN
// it sometimes exceeded 0.25 seconds.
WinError := GetLastError;
{$IFDEF Tracing}
FFAddTrace(foUnknown, WinError, sizeof(WinError));
{$ENDIF}
FFRaiseException(EffServerException, ffStrResServer, fferrOpenFailed,
[aName, WinError, SysErrorMessage(WinError)]);
end;
end;
end;
end;
end;
end;
{$IFDEF Tracing}
FFAddTrace(foUnknown, Result, sizeof(Result));
{$ENDIF}
end;
procedure FFForceFlushFile(aFI : PffFileInfo);
begin
FFVerifyFileInfo(aFI, true);
if not (fffaTemporary in aFI^.fiAttributes) then begin
FFCloseFilePrim(aFI);
with aFI^ do
fiHandle := FFOpenFilePrim(@fiName^[1], fiOpenMode, fiShareMode,
false, false);
end;
end;
This should be improved to only retry when GetLastError gives 32 which is 'The process cannot access the file because it is being used by another process'. For other results such as 3 'The system cannot find the path specified' we want an immediate error raised. So, after each 'if Result = INVALID_HANDLE_VALUE' we need to call GetLastError and report the error immediately if the error is not 32. The nested if statements can be replaced by a loop something like this:
WaitFor := 20; // Milliseconds.
Waited := 0;
Result := 0;
Opened := false;
while not Opened do begin
Result := OpenTheFile;
if Result = INVALID_HANDLE_VALUE then begin
err := GetLastError;
if (err <> 32) or (Waited > 2500) then begin
raise the error ...
end;
Waited := Waited + WaitFor;
Sleep(WaitFor);
WaitFor := WaitFor + 50;
end
else
Opened := true;
end;