Delphi: Why do I sometimes get an I/O Error 103 with this code?

45,555

Solution 1

I don't see what is wrong with automatic retry. I don't see that you can do anything else. If some other process is reading the file, then your Append/Rewrite will fail. And since the file is a log, there is a good chance that something, such as a log viewer or a text editor, will be reading it at the instant you try to open it.

Try opening the file a few times with a delay in-between attempts before failing definitively. You could use an exponential backoff if you wanted to be fancy.

Solution 2

Okay, it's over a year late, but I'm going to add my comment to this, as it explains why this is happening.

I had the exact same problem in a multi-threaded application with code almost identical to the snippet above and I had critical sections protecting the code.

The problem occurred most readily when one logging operation swiftly followed another. The second operation would fail for the above reason.

I thought it was anti-virus software too, but the error happened on one machine and not the other, where both had Norton 360 installed. The machine with the problem was brand new Windows 7 and the one without was Windows XP. A colleague also had the problem running the system under a virtualised Windows Vista machine with no virus checker installed.

So my question was, "why was this XP machine so different?".

For one, it wasn't virgin, and that is the answer it seems:

Opportunistic locking and NT caching were turned off. Most (mature) Delphi developers will know that when using BDE, these are turned off in order to maintain DBF and DB file integrity in multi-user situations. These settings were not disabled on the newer machines because we no longer develop for Paradox data files!

Delayed write caching seems to leave a read/write lock on the file until the OS has done its business, which could be several milliseconds later.

So, in my case, the second log event was being blocked by the first.

Okay, I'm not suggesting that you turn off opportunistic locking + NT Caching. One of the major reasons we moved away from BDE was to avoid persuading customers to tinker with such settings. So, there are four practical solutions:

1) To retry for an acceptable period of time, as mentioned by dangph.

2) to open the file when the application loads and hold it open for the full duration of the application. Not so useful if you are running multiple instances of the application.

3) Lazily put a sleep(1) before the logging code and hope that is long enough for the lock to be released. But that risks slowing your system down if you are doing lots of logging.

or 4) Put a try...except..end around your code. But then you are probably guaranteed to miss 100% of the second messages (referring to my case).

Solution 3

You should generally put opening the file before the try of a try..finally:

if fileexists then
  append(..)
else
  rewrite(..);
try
  // do something with the file
finally
  CloseFile(..);
end;

and

AssignFile(lFile, AFileName);
Rewrite(lFile);
CloseFile(lFile);

(try finally does not make any sense in the last case)

Otherwise closing the file might fail because it could not be opened and this would mask the real error.

But I don't think that is the issue here.

Solution 4

Besides anti-virus it can also be indexing software or file management software, like Google Desktop. However, the real problem here is, that the error message doesn't help you solve the problem. I suggest that you rewrite the code to use TFileStream, instead, just in order to improve your error messages.

Solution 5

Could you be looking at a stray error from something else compiled in a $I- state?

Share:
45,555
Oliver Giesen
Author by

Oliver Giesen

Professional Software Developer, Heavy Metal Drummer and 3D Enthusiast from Germany. Started programming on a Sinclair ZX Spectrum 48k in 1983. Moved on via QuickBasic on the PC to TurboPascal and eventually Delphi (since 1997). Also occasionally dabble in PHP (e.g. Joomla, WordPress, MediaWiki), Python (e.g. trac, Blender) and still gaining momentum in C#/ASP.NET. Specialized in COM-Addins for MS Office (especially Outlook), client/server database-applications (MSSQL) and whatever else comes my way. Also interested/invested in version control (former mailing list moderator and contributor to the CVSGUI project - nowadays using Git). I also enjoy doing graphics design at the level of "(slightly) advanced amateur", mostly using Open Source Tools like Gimp, Inkscape and Blender and recently got into 3D printing.

Updated on July 05, 2022

Comments

  • Oliver Giesen
    Oliver Giesen almost 2 years

    In several of my apps I have code similar to the following:

    if ForceDirectories(ExtractFilePath(lLogName)) then
      begin
        AssignFile(lLog, lLogName);
        try
          if FileExists(lLogName) then
            Append(lLog)
          else
            Rewrite(lLog);
          Writeln(lLog, lLogLine);
        finally
          {$I-}CloseFile(lLog);{$I+}
        end;
      end;
    

    In one application, the first time I try to execute this I consistently get an I/O Error 103 exception on the line with the Append statement (the file does exist prior to calling this). All subsequent attempts at the operation will work fine however - until I restart the app.

    All the docs I found about this error so far indicated that this would either be caused by calling CloseFile without prior Reset or Rewrite (Append typically isn't mentioned) or if the file was in use by another process. As the exception occurs before the call to CloseFile it obviously couldn't be the former.

    I already tried inserting a Reset right after the AssignFile for good measure but then I get the exception on that line.

    There is also no other application overtly accessing that file. I say "overtly" because I do have a slight suspicion that anti-virus (TrendMicro in my case) might be the cuplrit here (so maybe the file is in use). If that was indeed the problem, what would be the best way around it? Hard-coding an automatic retry does not really feel like a clean solution to me...


    Another case where I sometimes get the 103 error is this code, which I use to create an empty file (or more often to empty an existing file):

    AssignFile(lFile, AFileName);
    try
      Rewrite(lFile);
    finally
      CloseFile(lFile);
    end;
    

    In this case it's much harder to reproduce. It happens a lot less often. Most of the time this seems to happen on the first run after I recompiled the application. Could this again be the anti-virus getting in the way? I have only ever seen this happen on my development machine and never gotten a report from a customer. As with the first scenario this only ever happens once per application session (if at all). Subsequent attempts are always successful.

    Any suggestions for a different, potentially more fail-safe approach to creating empty files or emptying existing ones?

  • Oliver Giesen
    Oliver Giesen about 15 years
    The apps in question are multi-threaded but this code is always run from the main thread. A good hint nevertheless.
  • skamradt
    skamradt about 15 years
    +1 I strongly support the use of TFileStream. Once you make the switch, you won't want to go back.
  • Rob Kennedy
    Rob Kennedy about 15 years
    You're right. AssignFile doesn't actually acquire any resources, so it' too early to enter a try-finally block right after that call. Only enter try-finally after Append, Reset, or Rewrite -- those are the functions that open a file.
  • Marco van de Voort
    Marco van de Voort almost 15 years
    Can't append and rewrite fail then? I think it can.
  • dummzeuch
    dummzeuch almost 15 years
    @Marco: Yes, it can fail, but if it fails, closing the file is not required because it has never been opened.
  • Michał Niklas
    Michał Niklas about 13 years
    Yes, using TFileStream you will be able to work with files bigger than 2GB. On such files append() and writeln() cannot work (IOResult <> 0 or exception).