What is the effect of memory-mapped file access on GetLastError()?


A customer was using memory-mapped files and was looking for information as to whether access to the memory-mapped data modifies the value returned by Get­Last­Error. A member of the kernel team replied, "No, memory-mapped I/O does not ever change the value returned by Get­Last­Error."

That answer is simultaneously correct and wrong, a case of looking at the world through kernel-colored glasses.

While it's true that the kernel does not ever change the value returned by Get­Last­Error, it's also the case that you might change it.

If you set up an exception handler, then your exception handler might perform operations that affect the last-error code, and those changes will be visible after the exception handler returns. (This applies to all exception handlers and filters, not just ones related to memory-mapped files.)

If you intend to return EXCEPTION_CONTINUE_EXECUTION because you handled the exception, then you probably should make sure to leave the last-error code the way you found it. Otherwise, the code that you interrupted and then resumed will have had its last-error code changed asynchronously. You just sabotaged it from above.

// Code in italics is wrong

LONG ExceptionFilter(LPEXCEPTION_POINTERS ExceptionPointers)
{
 if (IsAnExceptionICanRepair(ExceptionPointers)) {
   RepairException(ExceptionPointers);
   // fixed up error; continuing
   return EXCEPTION_CONTINUE_EXECUTION;
 }

 if (IsAnExceptionICanHandle(ExceptionPointers)) {
  // We cannot repair it, but we can handle it.
  return EXCEPTION_EXECUTE_HANDLER;
 }

 // Not our exception. Keep looking.
 return EXCEPTION_CONTINUE_SEARCH;
}

If the Is­An­Exception­I­Can­Repair function or Repair­Exception function does anything that affects the last-error code, then when the exception filter is executed for a repairable exception, the last-error code is magically changed without the mainline code's knowledge. All the mainline code did was execute stuff normally, and somehow during a memory access or a floating point operation or some other seemingly-harmless action, the last-error code spontaneously changed!

If you are going to continue execution at the point the exception was raised, then you need to "put things back the way you found them" (except of course for the part where you repair the exception itself).

LONG ExceptionFilter(LPEXCEPTION_POINTERS ExceptionPointers)
{
 PreserveLastError preserveLastError;

 if (IsAnExceptionICanRepair(ExceptionPointers)) {
   RepairException(ExceptionPointers);
   // fixed up error; continuing
   return EXCEPTION_CONTINUE_EXECUTION;
 }

 if (IsAnExceptionICanHandle(ExceptionPointers)) {
  // We cannot repair it, but we can handle it.
  return EXCEPTION_EXECUTE_HANDLER;
 }

 // Not our exception. Keep looking.
 return EXCEPTION_CONTINUE_SEARCH;
}

Exercise: Why isn't it important to restore the last error code if you return EXCEPTION_EXECUTE_HANDLER?

Exercise: Is it important to restore the last error code if you return EXCEPTION_CONTINUE_SEARCH?

Comments (28)
  1. ipoverscsi says:

    For those too lazy to click the link, PreserveLastError is a C++ class that captures the error code on construction and restores it on destruction.

  2. si says:

    yes, because if we break the error code in IsAnExceptionICanRepair or IsAnExceptionICanHandle, return EXCEPTION_CONTINUE_SEARCH and some other handler can handle it they restore a bad error code.

  3. Erwin says:

    "Exercise: Why isn't it important to restore the last error code if you return EXCEPTION_EXECUTE_HANDLER?"

    The code path changes (the _except path is taken immediately), so the last error code does not change "magically"

    "Exercise: Is it important to restore the last error code if you return EXCEPTION_CONTINUE_SEARCH? "

    Yes, if another exception handler repairs the exception and returns an EXCEPTION_CONTINUE_EXECUTION, that exception handler sees your last exception.

  4. Anonymous Coward says:

    Am I being simple if I say that you should just save the last error immediately upon encountering an error if you need it?

    [This assumes you get a chance! Consider: m_hEvent = CreateEvent(...); if (!m_hThing) { GetLastError(); }. You might take an exception trying to write to m_hEvent, or even just trying to fetch the next instruction from memory. Both of these happen before you get a chance to call GetLastError. Remember, these are asynchronous exceptions, not C++ exceptions. -Raymond]
  5. Mason Wheeler says:

    PreserveLastError preserveLastError;

    Ugh.  Anyone who abuses case sensitivity like this ought to have their coding license revoked and their compiler impounded.

  6. Joshua Ganes says:

    @Mason Wheeler – This is exactly how I would capitalize this code. How would you do it?

  7. Mason Wheeler says:

    @Joshua: By not using the same name with different capitalization for the class and the instance.  That's just confusing and makes the code harder to read.  That's one of the reasons why I prefer case insensitive languages.  No case sensitivity = no case sensitivity abuse.

  8. Avi says:

    @Mason Wheeler:

    It's not an abuse.  It's a common idiom in case sensitive languages that types have an initial capital letter and variables do not.  Thus, if one understands the culture of the language in use, there is no confusion.  And by using the same name, the type becomes very easy to infer.

    I've seen this recommended in both Java and Objective-C (languages I use daily).

  9. Joshua Ganes says:

    This isn't really a matter of case sensitivity. Keep in mind that the following code would function exactly the same way:

    PreserveLastError PreserveLastError;

    I feel that the real issue is whether or not to use a class designed in this way. I feel that it's not intuitive to declare a variable (apparently unused) in order to preserve the error code. It is, however, quite practical.

  10. James Schend says:

    @Mason Wheeler: I'd agree with you if you were editing your code in 1990. But this is 2012, every code editor has color-coding, and there's no confusion involved because type names have a different color from variable names.

    If you're not using color-coding, then I'd say the problem exists between keyboard and chair.

  11. Joshua says:

    @James Schend: Some of us have various eye problems that result in having to live without color coding.

  12. Nawak says:

    @Joshua

    Well, to be fair, your eye problem does indeed reside "between keyboard and chair". ;)

  13. voo says:

    @James I don't even see the problem in 1990. Classes start with an uppercase letter, variables with a lowercase one. If you're not using stupid fonts where l and I are indistinguishable and other fun things, it's perfectly clear what what is in every possible situation at one glance.

  14. Adam Rosenfield says:

    I don't like using variables like this preserveLastError whose only purpose is to effect a change on construction and destruction — some ignorant programmer could just come along and say "oh hey, it looks like this variable isn't being used for anything, I'll just delete it", and suddenly your program breaks in impossible ways two months later.

    Yes, the class and variable are well-named in this case, and you should avoid trying to work with obtuse programmers as much as possible, but it's still a possibility to worry about.  I'm particularly fond of the "explicit is better than implicit" rule from the Zen of Python.

  15. Csaboka says:

    @Adam Rosenfield: The problem is, C++ doesn't let you do this explicitly. You could add an explicit GetLastError() to the beginning and a RestoreLastError() before every return, but then you aren't exception-safe: any exception will prevent the code from restoring the error code.

    The exception-safe explicit way would be a try-finally construct, but C++ doesn't support that. All you can do is writing a class for every possible cleanup scenario, and declare an instance of that class any time you need that specific cleanup code.

  16. Peeter Joot says:

    Won't your printf() call potentially change the value of GetLastError() too?  That's not a signal safe function on Unix (where it can do bad things like acquire C runtime library mutexes that could already be held), so I'm suprised to see it used in a Windows exception handler.

    [Good catch. Bad example. I'll change it to a comment. -Raymond]
  17. Csaboka says:

    @Evan: Well, I wouldn't call what ~PreserveLastError() does in this example "resource cleanup". I guess that's why this solution bothers me — if the destructor would truly just clean up resources acquired in the constructor, no one would be surprised. I believe that the Java equivalent:

    int lastError = GetLastError();

    try {

     // do stuff

    } finally {

     RestoreLastError(lastError);

    }

    is both more explicit and more readable than the C++ code:

    {

      PreserveLastError preserveLastError;

      // do stuff

    } // Note: the last error is magically restored here

    Maybe the Java version is prone to copy-pasting (as you didn't abstract your setup and restoration into separate methods), but at least you can clearly see that something is happening at the end of the block.

  18. Simon Buchan says:

    Man, a lot of very strong opinions on such a trivial detail of the post!

    I still find it hard to keep in mind that not every SEH exception is an error. Though I expect that people writing SEH vectored exception handlers would be better about keeping an eye on this sort of thing?

  19. Bekenn says:

    PreserveLastError is a proper use of the RAII idiom, though I agree that an explanatory comment would be worthwhile.  I really like D's scope(exit) construct; thankfully, with C++11, you can recreate its semantics.  The following code works in VC10 and VC11:

    // In some header file:

    template <class Callback>

    class do_at_scope_exit

    {

    public:

    do_at_scope_exit(Callback cb) : callback(cb) { }
    
    ~do_at_scope_exit() { callback(); }
    

    private:

    Callback callback;
    

    };

    #define PASTE(a, b) PASTE2(a, b)

    #define PASTE2(a, b) a ## b

    template <class Callback>

    do_at_scope_exit<Callback> at_scope_exit_do(Callback cb) { return do_at_scope_exit<Callback>(cb); }

    #define at_scope_exit(cb) auto PASTE(at_scope_exit, COUNTER) = at_scope_exit_do(cb)

    // ———————–

    // Use case:

    LONG ExceptionFilter(LPEXCEPTION_POINTERS ExceptionPointers)

    {

    DWORD lastError = ::GetLastError();
    
    at_scope_exit([=]() { ::SetLastError(lastError); });
    

    }

  20. Cheong says:

    > I don't like using variables like this preserveLastError whose only purpose is to effect a change on construction and destruction — some ignorant programmer could just come along and say "oh hey, it looks like this variable isn't being used for anything, I'll just delete it", and suddenly your program breaks in impossible ways two months later.

    I'll confess that my initial thought once saw it is that I can't see any read or write action to it, so it should be removed.

    It would be better if some comments can be put beside the code, I think.

    [Um, the comments are in the accompanying prose. You forget that code is formatted differently for the Web site. -Raymond]
  21. Joshua says:

    @cheong00: Standard idiom in C++. But yeah comment is nice.

  22. Evan says:

    @Adam, Csaboka:

    Further, even *if* C++ had try-finally, using that for resource cleanup is, at least I think, super obnoxious. Deterministic destructors are way better. The only solutions that I consider comparable are something like Python's 'with' or C#'s 'using' and D's 'scope(exit)'.

  23. Deduplicator says:

    @Joshua et aliae:

    Standard idiom in used language, self-explanatory names, and you still want to add a comment?

    Why don't you explain every token, be it comment or source code, as well?

    Thus you'd be in serious danger of drowning in a sea of irrelevance, not able to find the one and only important insight hidden in all the clutter.

  24. Neil says:

    Of course you have to take care not to write PreserveLastError(); by mistake. Or maybe

    #define PreserveLastError() PreserveLastError preserveLastError

    (This does not work for typical RAII classes that need parameters, of course.)

  25. Skyborne says:

    Clearly ;-) the best way is:

    WithErrorSaved last_err_trash;

    "trash" would signal I know I'm not using it, and "With" would indicate RAII.  Like with open(fname) as f: ... in Python.

  26. Steve says:

    @Deduplicator – For most developers, their code code does not cease to be used once it is complied. As someone who spends a lot of time reading other people's code, often written in languages in which I do not regularly write myself, you'd be hard pressed to over-comment code. Making assumptions about the depth of familiarity of your reader with the the language is a mistake.

    Comments are a beautiful thing to accelerate others understanding of the INTENT fo your code. Since developers are not perfect, their code sometimes deviates fronm their intent. Comments help ME to find YOUR mistakes.

  27. 640k says:

    @Steve: You have to assume that a craftsman can use his tools, else he shouldn't be "crafting". This is about who to optimize the readability for. A developer or a non-developer?

    You should only comment the non-obvious. RIAA is obvious in C++.

    Also, as Agile suggest, there is no developer who write poor code and good comments. It's either poor code+lying comments OR self explaining code+redundant comments.

  28. @640K says:

    What a wonderful world you live in, can you post info on how to get to this utopia?

Comments are closed.