January 21st, 2021

What happens to the value returned from the function passed to Queue­User­Work­Item?

The Queue­User­Work­Item function schedules a function to be called from a thread pool thread. It’s one of the so-called legacy thread pool functions, a group of functions introduced in Windows 2000 in the first version of the system thread pool. It has since been superseded by the new thread pool functions like Create­Threadpool­Work, but the old functions continue to work, for compatibility. (They are implemented as wrappers around the new thread pool functions.)

Curiously, the Queue­User­Work­Item function takes a LPTHREAD_START_ROUTINE function pointer as the function to run on a thread pool thread. This is curious because the LPTHREAD_START_ROUTINE function returns a DWORD. What does the system do with the DWORD returned by a work item?

Nothing.

The return value is simply discarded.

It doesn’t matter what you return, as long as you return something. (And do make sure you return something. Don’t just fall off the end of the function and return uninitialized garbage, because that uninitialized garbage could be deadly.)

Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

8 comments

Discussion is closed. Login to edit/delete existing comments.

  • Joshua Hudson

    Mind boggles why the garbage is deadly. When ANSI C was new, it allowed falling off the end of an int function so long as the caller didn’t check. Even itamium only faulted when you wrote the value to memory, which you won’t because it’s call-clobbered.

    • Emily Bowman

      If anything, we’re all spoiled these days because the compiler will generally just go ahead and insert a “return 0;” if you just fall off the end of an int-defined function. You have to work pretty hard to corrupt the stack on accident now.

      • 紅樓鍮 · Edited

        I thought modern compilers are more aggressive in exploiting undefined behaviors?

        … the compiler will generally just go ahead and insert a “return 0;” if you just fall off the end of an int-defined function.

        If I'm not mistaken, your statement is only applicable to the main function.

        Read more
      • Kasper Brandt

        Practically speaking it's limited how undefined it can get because the compiler can't see the call site in this case. It needs to emit the function as if it was going to be called with the signature you claimed. The cast itself is valid as long as its cast back to a function pointer compatible with the signature, and since this is happening in an external dll the compiler won't know that there is any...

        Read more
      • Adam Rosenfield

        Compatible type is defined in C99 §6.2.7, with references to 6.7.2, 6.7.3, and 6.7.5. It basically means the same type, but with a lot of additional pedantic rules (e.g. enumerations are compatible with an implementation-defined integer type).

      • 紅樓鍮

        A pointer to a function of one type may be converted to a pointer to a function of another type and back again……
        which is not what we're doing here.

        If a converted pointer is used to call a function whose type is not compatible with the pointed-to type……
        I'm not sure what "compatible" means here; it could mean ABI-compatible or type-identical or something else. Unfortunately I couldn't easily find an answer to it on...

        Read more
      • 紅樓鍮 · Edited

        You're right that at a specific ABI boundary there is no notion of "undefined behavior"; everything is defined by registers and memory.

        However, it doesn't apply to the case of undefinedness that is already on the language level. For example, inside an int-returning function the compiler is free to assume that the closing curly brace is never reached (since that would be UB) and thus for
        <code>
        the compiler may choose not to generate even...

        Read more
      • Kasper Brandt

        > On the topic of function pointer casting then, observe that it’s still language-level UB since the correct signature of the function has been declared in the translation unit, and the cast into a contradictory function pointer type is expressed in language-level expressions.

        Casting between incompatible function pointers isn't UB in C, it is only UB when the function is called through an incompatible function pointer type.

        From ISO/IEC 9899:2018 Section 6.3.2.3, paragraph 8:

        > A pointer to...

        Read more