What does INIT_ONCE_CTX_RESERVED_BITS mean?


Windows Vista adds the One-Time Initialization family of functions which address a common coding pattern: I want a specific chunk of code to run exactly once, even in the face of multiple calls from different threads. There are many implementations of this pattern, such as the infamous double-checked lock. The double-checked lock is very easy to get wrong, due to memory ordering and race conditions, so the kernel folks decided to write it for you.

The straightforward way of using a one-time-initialization object is to have it protect the initialization of some other object. For example, you might have it protect a static object:

INIT_ONCE GizmoInitOnce = INIT_ONCE_STATIC_INIT;
Gizmo ProtectedGizmo;

BOOL CALLBACK InitGizmoOnce(
    PINIT_ONCE InitOnce,
    PVOID Parameter,
    PVOID *Context)
{
    Gizmo *pGizmo = reinterpret_cast<Gizmo*>(Parameter);
    pGizmo->Initialize();
    return TRUE;
}

SomeFunction(...)
{
    // Initialize ProtectedGizmo if not already initialized
    InitOnceExecuteOnce(&GizmoInitOnce,
                        InitGizmoOnce,
                        &ProtectedGizmo,
                        NULL);

    // At this point, ProtectedGizmo has been initialized
    ProtectedGizmo.Something();
    ...
}

Or you might have it protect a dynamic object:

class Widget
{
    Widget()
    {
        InitOnceInitialize(&m_InitOnce);
    }

    void Initialize();

    ...

    static BOOL CALLBACK InitWidgetOnce(
        PINIT_ONCE InitOnce,
        PVOID Parameter,
        PVOID *Context)
    {
        Widget *pWidget = reinterpret_cast<Widget*>(Parameter);
        pWidget->Initialize();
        return TRUE;
    }

    SomeMethod(...)
    {
        // Initialize ourselves if not already initialized
        InitOnceExecuteOnce(&InitWidgetOnce,
                            this,
                            NULL);

        // At this point, we have been initialized
        ... some other stuff ...
    }
}

But it so happens that you can also have the INIT_ONCE object protect itself.

You see, once the INIT_ONCE object has entered the “initialization complete” state, the one-time initialization code only needs a few bits of state. The other bits are unused, so the kernel folks figured, “Well, since we’re not using them, maybe the application wants to use them.”

That’s where INIT_ONCE_CTX_RESERVED_BITS comes in. The INIT_ONCE_CTX_RESERVED_BITS value is the number of bits that the one-time initialization code uses after initialization is complete; the other bits are free for you to use yourself. The value of INIT_ONCE_CTX_RESERVED_BITS is 2, which means that you can store any value that’s a multiple of 4. If it’s a pointer, then the pointer must be DWORD-aligned or better. This requirement is usually easy to meet because heap-allocated objects satisfy it, and the pointer you want to store is usually a pointer to a heap-allocated object. As noted some time ago, kernel object handles are also multiples of four, so those can also be safely stored inside the INIT_ONCE object. (On the other hand, USER and GDI handles are not guaranteed to be multiples of four, so you cannot use this trick to store those types of handles.)

Here’s an example. First, the code which uses the traditional method of having the INIT_ONCE structure protect another variable:

// using the static object pattern for simplicity

INIT_ONCE PathInitOnce = INIT_ONCE_STATIC_INIT;
LPWSTR PathToDatabase = NULL;

BOOL CALLBACK InitPathOnce(
    PINIT_ONCE InitOnce,
    PVOID Parameter,
    PVOID *Context)
{
    LPWSTR Path = (LPWSTR)LocalAlloc(LMEM_FIXED, ...);
    if (Path == NULL) return FALSE;
    ... get the path in Path...
    PathToDatabase = Path;
    return TRUE;
}

SomeFunction(...)
{
    // Get the database path (initializing if necessary)
    if (!InitOnceExecuteOnce(&PathInitOnce,
                             InitPathOnce,
                             NULL,
                             NULL)) {
        return FALSE; // couldn't get the path for some reason
    }

    // The "PathToDatabase" variable now contains the path
    // computed by InitPathOnce.

    OtherFunction(PathToDatabase);
    ...
}

Since the object being protected is pointer-sized and satisfies the necessary alignment constraints, we can merge it into the INIT_ONCE structure.

INIT_ONCE PathInitOnce = INIT_ONCE_STATIC_INIT;

BOOL CALLBACK InitPathOnce(
    PINIT_ONCE InitOnce,
    PVOID Parameter,
    PVOID *Context)
{
    LPWSTR Path = (LPWSTR)LocalAlloc(LMEM_FIXED, ...);
    if (Path == NULL) return FALSE;
    ... get the path in Path...
    *Context = Path;
    return TRUE;
}

SomeFunction(...)
{
    LPWSTR PathToDatabase;
    // Get the database path (initializing if necessary)
    if (!InitOnceExecuteOnce(&PathInitOnce,
                             InitPathOnce,
                             NULL,
                             &PathToDatabase)) {
        return FALSE; // couldn't get the path for some reason
    }

    // The "PathToDatabase" variable now contains the path
    // computed by InitPathOnce.

    OtherFunction(PathToDatabase);
    ...
}

This may seem like a bunch of extra work to save four bytes (or eight bytes on 64-bit Windows), but if you use the asynchronous initialization model, then you have no choice but to use context-based initialization, as we learned when we tried to write our own lock-free one-time initialization code.

Comments (11)
  1. Maurits says:

    Is it guaranteed that new'd pointers are multiples of four?

    [Depends on what kind of pointer, right? -Raymond]
  2. Joshua says:

    [This is difficult when your code doesn't control the thread creation. -Raymond]

    Static instance of class in C++. Problem solved.

    ["Hey, let's do complicated stuff in DLL_PROCESS_ATTACH. What could possibly go wrong?" Also, remember the super secret evil trick for making your program start up faster. -Raymond]
  3. GregM says:

    Static instance of class in C++. Problem solved.

    ["Hey, let's do complicated stuff in DLL_PROCESS_ATTACH. What could possibly go wrong?" -Raymond]

    In case you might doubt what Raymond is saying here, I just spent months trying to get a third-party component supplier to fix an intermittent hang that happened only on the machines at one customer site.  It happened because a static initializer called a function that led through a chain of 5 dlls and 20 calls, including several system calls, and then it caused the load of another dll, and bang, deadlock, because the loader lock is already held.  This was NOT FUN.

  4. Mason Wheeler says:

    I've never understood the point of this pattern in the first place.  If you know you're going to need exactly one of something that's going to be used by multiple threads, the smart thing to do is to create it exactly once *before you spawn the multiple threads that are going to need it*.  No possibility of race conditions that way, and no weird, bug-prone complexity at getting the just-in-time initialization to work right.

    [This is difficult when your code doesn't control the thread creation. -Raymond]
  5. Roisin says:

    Agreed that 1) Homemade one-time initialization in the face of multi-threading is easy to get wrong. 2) Static initialization of non POD C++ objects is dangerous.

    But.. Isn't it a sign of an underlying weakness of the architecture of a given program / framework if there isn't a well-defined / clean / safe method of doing "complex" initialization / termination?

    [Win32 isn't a framework. It's a bunch of building blocks. The CLR is a framework, and it handles this nicely via static class constructors. -Raymond]
  6. Mathieu Garstecki says:

    @Roisin: it's a sign of a language that was designed before multi-thread was the norm. Any progress on that time machine yet ?

    I believe current/future evolutions of C++ try to remedy that.

  7. Clovis says:

    INIT_ONCE_CTX_RESERVED_BITS – ooh, bad encapsulation leakage – I foresee a world of compatibility pain in Windows 8+n (where n >= 0) when the kernel folks want some of those bits back, but app writers have taken the lot.

  8. Gabe says:

    Clovis: The kernel folks can never have those bits back. How would that even work? If your application was using all of the unreserved bits and the kernel wanted some of them back, what would you expect to happen? Would everybody have to write their applications to check the number of reserved bits and then allocate additional space if it wasn't enough?

  9. Nick says:

    "Also, remember the super secret evil trick for making your program start up faster. -Raymond"

    AppInit_Dlls to eagerly load everything into memory, right?

  10. asdf says:

    Is the internal data masked by INIT_ONCE_CTX_RESERVED_BITS guaranteed to be 0 after initonce or do you have to mask them off every time you use the pointer?

    [As it says in the documentation, the Context contains "data stored with the one-time initialization structure." It returns the data you stored. Since the data you stored set the INIT_ONCE_CTX_RESERVED_BITS to zero, the value returned will have those bits set to zero. -Raymond]
  11. Freek says:

    Thanks for the reference to the double checked lock.

    While reading it I remembered seeing code like that within our software. I immediately wrote a regex to find this double checked lock and I found 5 occurrences…

    Thanks!

Comments are closed.