Rules can exist not because there's a problem, but in order to prevent future problems


I lost the link, but one commenter noted that the Read­File function documentation says

Applications must not read from, write to, reallocate, or free the input buffer that a read operation is using until the read operation completes.

The commenter noted, "What is the point of the rule that disallows reading from or writing to the input buffer while the I/O is in progress? If there is no situation today where this actually causes a problem, then why is the rule there?"

Not all rules exist to address current problems. They can also exist to prevent future problems.

In general, you don't want the application messing with an I/O buffer because the memory may have been given to the device, and now the device has to deal with bus contention. And there isn't really much interesting you can do with the buffer before the I/O completes. You can't assume that the I/O will complete the first byte of the buffer first, and the last byte of the buffer last. The I/O request may get split into multiple pieces, and the individual pieces may complete out of order.

So the rule against accessing the buffer while I/O is in progress is not a significant impediment in practice because you couldn't reliably obtain any information from the buffer until the I/O completed. And the rule leaves room for the future versions of the operating system to take advantage of the fact that the application will not read from or write to the buffer.

Tomorrow, I'll tell a story of a case where accessing the I/O buffer before the I/O completed really did cause problems in Windows 95.

Comments (19)
  1. J Cobb says:

    Maybe I am missing something, but why would you want to read from the buffer before the IO completes anyway, since you would just read invalid data (ie. What ever was in it before).

  2. Kevin says:

    The "reallocate or free" part reminds me of the Ground Rules; I imagine this is just a special case of them:

    blogs.msdn.com/.../555511.aspx

  3. Joshua says:

    Writing to the buffer ReadFile will be writing to looks pretty undefined even without that warning. Even if it doesn't break, I could imagine pretty arbitrary badness in the readback later.

  4. Dave Bacher says:

    Reallocating the buffer is bad because it could move.

    Freeing the buffer is bad because there's a pointer.

    Reading from the buffer is bad because until the read has completed, whatever process is doing the read is writing to the buffer.

    Writing to the buffer is a race condition.

    I don't see a case where the rule, as written, doesn't firmly apply on versions of Windows back to 1.0.

  5. dave says:

    @J Cobb

    If you were misguided, you might write code that attempted to determine when 'some of the read' had completed by looking for magic values in the buffer (maybe which you'd previously zeroed, or something).  Your incorrect assumptions would include that the buffer must be either in its initialized state or would contain the final read data, that there were guarantees about the order in which bytes in the buffer would be filled in, and so forth.  Probably all of your assumptions would turn out to be true on your test cases :-)

    Actually, I did know of a system where the boot code (a PDP-8) was executing out of the high end of a buffer into which a DMA read was in progress. When the data stream being read in got to the top of the buffer, it would overwrite the wait-loop with a jump to somewhere else. Life was simpler in those days.

  6. Steven says:

    Those are all reasons why it's a bad idea to read from the buffer -- but you still could.  But, there's a difference between "bad idea" and "forbidden".  I think what Raymond and the commenter were getting at is that, as written, the documentation for ReadFile indicates that reading the buffer is strictly forbidden, not just a bad idea.

    For example, as written, the operating system *could* unmap the memory range of the buffer for the duration of the read, so that trying to read from the buffer could cause a hardware exception and crash your program.  Sure, it doesn't right *now*, but the next version of Windows *could* (I don't know a real reason why it would, although I can imagine some kind of primitive DMA implementation, or a fancy new form of hardware cluster or NUMA memory, or some high-performance optimization which bypasses the CPU cache coherency protocol -- something that means the device driver doesn't want anyone else even *accessing* the memory).

    Sure, it would be tough for the kernel guys to implement -- ReadFile() doesn't put any real requirements on how you allocate the buffer -- but, as written, they *could*.

  7. dave says:

    re: forbidden/bad idea

    I don't read that into it. I read "must not" as "if you do this, and your app explodes in a pile of shrapnel, don't come whining to us that ReadFile is broken; it isn't."

    It's the same to me as "your app must not write to the 11th byte of a 10-byte array".  The difference between "bad idea" and "forbidden" is of little interest there.

  8. Steven says:

    @Dave:

    To me, that sounds like we're on the same page.  The ReadFile() documentation is saying that reading from the buffer could cause your program to explode in a pile of shrapnel; it's not guaranteeing that the reading from the buffer would be successful -- even if the results are incomplete or would contain garbage.  You must not do it, period.

  9. Joshua says:

    @Steven, Dave:

    This sounds like it shouldn't be right. I, and others like me, declare buffers on the STACK and pass them to ReadFile. I shudder to think of the consequences of an unmapped stack frame.

  10. Steven says:

    @Joshua

    We might be getting a little bogged down with my pie-in-the-sky example of a "hypothetical, crazy thing the kernel could do".  I was just trying to illustrate scenarios why reading the buffer might actually be "forbidden" instead of just a "bad idea".

    My point was just that, as written, intentionally or not, they've reserved the right to do all this crazy stuff because they've said you're not supposed to even *try* to read from the buffer ("forbidden"), not that reading from the buffer wouldn't return you any useful information ("bad idea (but won't kill you)").  Maybe they're hedging their bets against strange new hardware, maybe it's just poor documentation, but that's what they've written.

    As an aside, though: the idea of unmapping part of the stack doesn't strike me as completely crazy -- that's effectively how virtual memory works, or switching to another process, or DOS' expanded memory system, or those old program overlays to let you get past the 640k DOS limit.  In any case, as long as everything gets put back in time (which, if you're following instructions, and don't access it until ReadFile() completes, it would be) you don't notice and life goes on.

    Actually wasn't there some version of Windows NT or DOS/DesqView that would give the same stack selector to different threads, so you couldn't actually pass a pointer from one thread's stack to another?  That *is* unmapping the stack.  (I can't find an actual reference to this; I might not be remembering correctly.)

    As I said, tough for the kernel guys to implement... but no more fundamentally impossible or crazy than other things we now consider completely normal.

  11. Steven says:

    RE: Same stack selector, different threads

    I think I was thinking about this article: blogs.msdn.com/.../10624045.aspx

    But that's the FS register, not the SS register.  My bad -- didn't remember it correctly.

  12. JVert says:

    Windows NT at various points has run on machines where the I/O was not cache-coherent. On a machine like this, you would get corrupt data in your ReadFile buffer if you just read from the buffer after the kernel had flushed the cache but before the I/O had completed.

  13. alegr1 says:

    If Windows ever runs on a system with no implicit DMA cache coherency (ARM?), this may cause data corruption. On systems without implicit cache coverency, before reading into a buffer, you're supposed to flush/invalidate cache lines that hold the buffer addresses. If you touch the buffer after that, you're bringing the old contents back to the cache, and then the processor will not see the result of the read.

  14. Cube 8 says:

    Hmm... The time machine has been invented after all...

  15. cheong00 says:

    There are many times people ask why the API is not written to allow anyone crazy enough to "shoot himself in the foot", there are other times, where people ask why it's written in the way that allow people to "shoot himself in the foot" without stopping him.

    One of the common examples is still from the ReadFile() API. People want to have decorations (attributes or compiler directives) that causes the compiler to check, if the return value if ReadFile() is discarded, the compiler should at least generate a warning.

  16. cheong00 says:

    I mean the lpNumberOfBytesRead of the function if this not NULL (hence not a overlapped I/O).

  17. Bryan says:

    J Cobb -

    Why would you want to look in a buffer before finish? (Not saying this is a good way to code) But, if your data source is relatively slow and your data has a record or other structure, then starting on some records when they arrive reduces how long it takes to process the buffer.

  18. DWalker says:

    @Bryan:  You are assuming the buffer is filled in some predictable order.  It has already been mentioned that a buffer might be filled in any unpredictable order -- Think about a buffer being filled from the higher addresses toward the lower addresses; from the middle toward the end and then from the middle toward the beginning; or maybe all of the even-numbered bytes are filled first, then all of the odd-numbered bytes.

    You are assuming that a buffer will contain "some [complete and usable] records" before it contains all of them.  Why would you assume that you can start using some part of a buffer before the I/O is complete?

  19. Simon Farnsworth says:

    @Bryan

    If you want to handle records as they arrive, you should use async I/O, issue one read per record (via ReadFile or ReadFileEx), and rely on the kernel completing them in a sensible order for the underlying devices (e.g. merging neighbouring reads into a big block read and completing the merged reads in one go).

Comments are closed.

Skip to main content