On the gradual improvements in how the system deals with the failure to initialize a critical section

The documentation for the Initialize­Critical­Section function says

Return value

This function does not return a value.

Windows Server 2003 and Windows XP: In low memory situations, Initialize­Critical­Section can raise a STATUS_NO_MEMORY exception. This exception was eliminated starting with Windows Vista.

In earlier versions of Windows, the Initialize­Critical­Section function could fail in low memory conditions, in which case it raised a STATUS_NO_MEMORY exception.

Wait, let's go back even further.

In very old versions of Windows, the Initialize­Critical­Section function could fail in low memory conditions, in which case it raised a STATUS_NO_MEMORY exception. However, the code wasn't particularly careful about exactly when it raised the exception, and it turns out that it didn't bother to completely unwind the partial-initialization-so-far before raising the exception. This means that if a program tried to recover from a failed Initialize­Critical­Section by catching the STATUS_NO_MEMORY exception, it still experienced a memory leak.

Yes, it's rather ironic that if the kernel couldn't initialize the critical section due to low resources, it leaked memory, which made the low resource situation even worse.

There was a similar sad story with Enter­Critical­Section and even Leave­Critical­Section: Under low resource conditions, those functions could fail and raise a STATUS_NO_MEMORY exception. Those are even worse because by the time you get the exception, it's probably too late to back out of whatever you were doing. I mean, maybe if you're really clever, you can recover from a failed Enter­Critical­Section by abandoning the operation (and undoing all the work done so far), but I can't think of any case where a program could do anything reasonable if Leave­Critical­Section fails.

And as Michael Grier noted, if Leave­Critical­Section raised an exception, not only wasn't there anything you could reasonably do about it, but it also left the critical section in a corrupted state!

The only thing you can do is to just crash the process before things get any worse.

I think it was in Windows XP that the kernel folks fixed the code so that it cleaned up the partially-initialized critical section before raising the STATUS_NO_MEMORY exception, so that a program could safely catch the exception and not leak memory. I believe they also fixed it so that the Enter­Critical­Section and Leave­Critical­Section functions would not raise exceptions. If called properly, then they always succeeded. So at least those weird cases of "raising an exception and leaving the critical section fatally corrupted" went away.

And then in Windows Vista, the kernel folks decided to get rid of the problem once and for all and remove all the failure cases from all the critical section functions. The Initialize­Critical­Section and Initialize­Critical­Section­And­Spin­Count functions always succeeded. The Enter­Critical­Section and Leave­Critical­Section functions would not raise exceptions when used properly.

So for over a decade now, the Initialize­Critical­Section and Initialize­Critical­Section­And­Spin­Count functions never fail. This means that (assuming they are called properly), Initialize­Critical­Section never raises a STATUS_NO_MEMORY exception. The Initialize­Critical­Section­And­Spin­Count function a return value that says whether it succeeded, but it always succeeds and returns a nonzero value. The return value is now superfluous.

Bonus chatter: The documentation for the Initialize­Critical­Section­And­Spin­Count function says

Return value

This function always returns a nonzero value.

Windows Server 2003 and Windows XP: If the function succeeds, the return value is nonzero. If the function fails, the return value is zero (0). To get extended error information, call Get­Last­Error. This behavior was changed starting with Windows Vista.

I'm told that some people come away from the documentation still worried about the possibility that the Initialize­Critical­Section­And­Spin­Count might fail on Windows Vista and later. They see that on Windows Server 2003 and Windows XP, the function tells you whether or not it succeeded, but on Windows Vista it always reports success. "That means that if the function fails, Windows Vista will lie to me and report success even though it failed!" No, that's not what it's saying. It's saying that starting in Windows Vista, the function never fails.

Comments (18)
  1. Medinoc says:

    I’m very curious about how the failure condition was removed, especially without increasing the size of the CRITICAL_SECTION structure. Is it related to the (apparently Vista-introduced if I read the doc right) ability to have a critical section without debug info (which sets that pointer to -1)?

    1. camhusmj38 says:

      In the XP timeframe, they introduced a Keyed Event which is used as an alternative to the Event Object that is normally allocated for a Critical Section. A Keyed Event uses a pointer-sized value as a key. There is a single low memory Keyed Event for the entire system which means that no allocation is necessary. The performance of Keyed Events was improved in Vista and that’s why we can have SRW locks etc.

  2. Goran says:

    For a very long time, people used to build with /eha, catching … and whatnot. Probably still do.

    But doing that prevents the ” only thing you can do is to just crash the process before things get any worse” solution to the problems.

    So in practice chances are, programs who fell on those must have been borked very badly.

    BTW… .NET, until v4 (erm… I think), was catching SEs as well, that was unhealthy, too.

  3. Harold H20 says:

    “No, that’s not what it’s saying. It’s saying that starting in Windows Vista, the function never fails.”

    Really? That’s great!! Now the next question is, since Microsoft has invented a function that never fails, why don’t they design ALL their functions like that? No more failures!!!

    1. Richard says:

      Don’t allocate memory.
      That’s basically how to “never fail”.

      Doesn’t mean it can’t crash – if calling exceeds stack depth…

      It’s also pretty useless advice, as most of the interesting things you can do involve allocating memory.

      Quite a lot of applications really do work this way though. They allocate all the memory they will ever need at startup, and so if they start, they will run and cannot run out of memory.

      This is the usual approach on embedded microcontrollers with extremely limited memory – 4KB is enough for anyone, right?

      1. Chris Crowther says:

        It’s pretty common in games as well, I believe. They block allocate at start-up and then handle their own memory management.

  4. The_Assimilator says:

    In all honesty the Initialize­Critical­Section­And­Spin­Count documentation isn’t clear unless you happen to have read this blog post. I would rewrite it to something along the lines of “This function returns a nonzero value on success, and zero (0) on failure. Windows Vista and later: This function is guaranteed to succeed.”

    1. Yup, I have a doc change request pending to make the description more clear.

      1. Joshua says:

        I’m pretty sure it fails if I pass it 0x100 for the pointer value. I haven’t had the guts to find out if it returns false or crashes.

        1. Voo says:

          That’s one of those basic rules of programming: If you pass arguments to a function that violate it’s contract you’re operating outside the defined contract and can no longer rely on any of its guarantees. So you get undefined behavior.

          1. Someone says:

            “So you get undefined behavior.” This case is not about C compiler assumptions, so in my opinion, “undefined behavior” is just not the correct term here.

            The Initialize­Critical­Section­And­Spin­Count call itself may silently override the memory pointed to by the first argument, or it may trigger an Access Violation on the attempt, but that’s it. Any other outcome would be bug in the implementation of an API to initialize some given memory block.

          2. GWO says:

            Replying to someone – but what are the effects of that silent overwrite? What if the memory is partially overwritten again by the true owner of that memory? Well, now your mutex may be corrupt, and you may get a dead-lock, or you may get incorrect locking that results in silent data corruption somewhere else (that might be protected by that mutex).

            Maybe that data corruption results in a flag being set that that makes your program think its been invoked in a mode that formats all the hard-disks… That’s why undefined behaviour is undefined.

          3. Someone says:

            @GWO: all true. But this is not the same as stating that a function call of the OS API can do random things, as how a C compiler can emit random code when faced with Undefined Behavior. The case of Joshua will throw an Exception, always. I would consider the behavior of such APIs well-defined, not undefined.

          4. Joshua says:

            There’s no particular reason it couldn’t contain if (criticalsection < 65536) {SetLastError(access violation); return FALSE;} I rather expect it's there for NULL. Some people write their null arguments as catch any very low pointer.

          5. Replying to someone: There is also undefined behavior in operating systems. It means “Anything can happen (within your security boundary).” For user-mode code, it means that your process (and anything your process has access to) can behave unpredictably.” For examples, see pretty much every security vulnerability report. There are versions of Windows where passing a null pointer to InitializeCriticalSection did not guarantee an instant fault.

  5. martin says:

    I had to try it. I run following simple program on my 32 bit windows 7 with 3 GB RAM:
    #define CS_BLOCK (1024*1024)
    #define CS_BLOCKS 40
    void main()
    int i,j;
    size_t n = 0;

    memset(gArr,0, sizeof(gArr));

    for(j = 0; j < CS_BLOCKS;j++)
    for(i = 0; i < CS_COUNT; i++)
    The program allocates 960MB of RAM for the array and waits for a key. The it starts to initialise all the critical sections.
    If you run at and watch the task manager, you will see, that InitializeCriticalSection() allocates memory, if possible. This code is in userspace.
    Then when there is not enough RAM, kernel takes the job (enable "show kernel times" in the task manager).

  6. Dave says:

    I’ve always wondered how Initialize­Critical­Section could fail with an out-of-memory error, given that the memory is supplied by the calling process… is there some kernel-mode pointer that needs to be allocated for refer to it?

  7. Dennis says:

    Hey, Raymond, I just discovered that you keep writing these. Wow. Thanks a bunch!

    Just curious though, do you have a Twitter account that we can follow? Or even better, maybe a YouTube channel to do screencasts to explain these things? Would be awesome!

Comments are closed.

Skip to main content