Don’t let more than one process try to read from stdin at the same time

A customer reported a problem with a program that ran a series of other programs in parallel.

We have a main program (call it main.exe) that runs a bunch of child processes with stdout and stderr redirected. (We are not redirecting stdin.) We've found that some of the child processes get stuck inside the C runtime startup code on a call to Get­File­Type on the stdin handle. What could be the reason for this? Is there something we can do that doesn't require us to modify the child processes? (They are third party code we do not have control over.)

This is one of those once you've debugged this problem you never forget it type of problems.

Notice that each of the child processes inherits the same stdin from main.exe, since you aren't redirecting stdin. Since the stdin handle was not opened as overlapped, all I/O to the handle is serialized.

The C runtime calls Get­File­Type at startup to determine whether or not to use buffering. When each child process starts up, it calls Get­File­Type, enters its main, and goes about its business. Everything is great until one of them tries to read from stdin. At that point, everything falls apart.

The next child process to start calls Get­File­Type, but instead of returning with a result, it waits for the previous I/O request (the read) to complete because the handle is marked synchronous, and synchronous handles permit only one operation at a time. The user, of course, doesn't realize that the first program is waiting for input (the prompt got redirected), so the user sits and waits for the program while the program sits and waits for the user.

To solve this problem, you first need to decide what you want to happen to stdin. Right now, you gave stdin to a dozen child processes, and each line of input the user types will be randomly assigned to one of those programs. In this case, the customer's answer is "I don't care about stdin; these programs aren't supposed to be reading from stdin anyway", in which case you can redirect stdin of the child processes to NUL.

Bonus chatter: This is also why, when you hit Ctrl+C to exit a console program which launched child processes with CREATE_NEW_PROCESS_GROUP, the command prompt that comes back sometimes behaves kind of strangely. Since the child processes were launched in a separate process group, the Ctrl+C killed the main program but left the child processes running. If any of those child processes read from stdin, you get the "Randomly assign input" effect described above because you have two programs racing to read from stdin: the orphaned child process and cmd.exe.

Comments (19)
  1. Joshua says:

    Been there, done that, got that headache.

    Also happens on UNIX systems if you screw job control up badly enough.

  2. dmitry_vk says:

    I'd say that the problem is actually worse. Not only child processes examine stdin/stderr/stdout but also libraries loaded with LoadLibrary. Try e.g. reading from stdin in one thread and loading ws2_32.dll from another – they'll deadlock.

  3. kinokijuf says:

    This belongs in the category „What if two programs did this?”.

  4. Paramanand says:

    Thanks Raymond for the bonus chatter. I used to get this kind of situation when running some batch files (like a build job) and pressing Ctrl+C to stop it.

  5. Kyle says:

    @Cesar –

    I'm not sure why this is surprising as I would expect all file operations (be they querying operations, etc.) would block when operating on a non-overlapped file handle that already has an operation blocking on it.  If anything Windows is being extremely consistent: namely blocking regardless of the file type and file operation when an operation is pending on a non-overlapped handle.  Why should such consistency be documented?  Only inconsistent behavior really needs documentation here.

    Anyway, the whole thing is moot as you should be handling the standard input properly to begin with, which in the instance that Raymond mentions here is clearly incorrect.

  6. Crescens2k says:


    Instead of saying it shouldn't count, come up with reasons why you don't think it should count.

    But remember that this function works on character mode devices, files and pipes. Also, the handle is a structure in memory that can change as time passes and be accessed from multiple threads at once. Oh, and do it in terms of the object manager, not what you expect as a programmer.

  7. Joshua says:

    @Crescens2k: Because it does not count on *nix systems. Fundamentally, there is no good reason for a query for file metadata to block on a read of the file from another thread.

  8. Crescens2k says:


    Well, here is a surprise for you, this isn't *nix, this is Windows. So please tell me why you would expect a fundamentally different operating system to work the same?

    This is like saying a motorbike shouldn't tip over when you lean to the side because cars don't.

  9. Cesar says:

    Well, this behavior is *very* surprising. I would never expect GetFileType() to block, since the information it returns should already be available to the kernel. And even if it blocked, I would expect it to block only if the handle is for a file, and the filesystem is busy (for instance if the handle is from a remote filesystem which is slow or disconnected). I would *not* expect GetFileType() to block simply because of a blocking read on the same handle!

    This is the kind of surprising behavior which really has to be documented on the function's documentation. Not everyone would know that non-overlapped handles can only have one operation active at a time, *and* that GetFileType() would count as one of these operations (since it is a simple query, I would expect it to *not* count).

  10. alegr1 says:

    Moreover, CloseHandle for the last handle on a non-overlapped file obj will block, too, if there are operations pending on the file object. This may cause quite obscure deadlocks.

  11. Joshua says:

    @Crescens2k: How about because dir on the directory doesn't block on any pending file operations in that directory. Blocking the handle just seems sloppy to me in this case.

  12. Someone says:

    @Kyle: "I'm not sure why this is surprising as I would expect all file operations (be they querying operations, etc.) would block when operating on a non-overlapped file handle that already has an operation blocking on it."

    I find it very suprising that operations can block which only handle file meta data.

    The whole blocking concept is not about "there can be only one API call active at any time", its about "a call does not return until all of its arguments are processed by the OS".

  13. Someone says:

    My previous remark was about the "overlapping" construct in Windows in general. For reading data from a stream (file, pipe, socket,…) "blocking" has usually the more specific meaning of "do not return until: Error or EOF or <data has read>".

    This all does not imply anything about calls from different threads or about calls that query metadata.

  14. Crescens2k says:


    The thing is, dir would use a new handle to list the files in the directory. STDIN is using the SAME handle that is currently doing something to query for information.

    Remember, a handle is what user mode applications use to access an object, and this is where the problems are occuring. There is nothing stopping accessing the object multiple times at once via different handles, but since someone has explicitly stated somewhere that this handle can only be accessed by one thing at a time, then the blocking occurs on the HANDLE.

    So try to remember that handles to an object and an object are two seperate things, and this is occuring at the handle level.

  15. Ian says:

    @Cesar: It *is* documented in MSDN. The remarks for CreateNamedPipe() state the following:

    "The pipe server should not perform a blocking read operation until the pipe client has started. Otherwise, a race condition can occur. This typically occurs when initialization code, such as the C run-time, needs to lock and examine inherited handles."

    Note the last bit: The C run-time needs to lock and examine inherited handles at program initialization.

  16. Joshua says:

    @Crescens2k: It's not the same handle. It's in two different processes.

  17. Crescens2k says:


    If you are trying to learn how the CRT startup works by refuting everything I say, I would suggest looking at the code in the VC directory instead. The CRT source is there and is a good read if you are curious.

    Otherwise I would suggest you read up a bit more on handles in Windows. It is vital.

    A handle can be inheritable. This means that the same handle can be passed between two processes and a second process can use it without any problems by marking it as inheritable. You can read more about it here…/ms683463(v=vs.85).aspx

    So it is entirely possible for two processes to use the same handle. You will also see near the bottom that the IO handles can be inherited through STARTUPINFO.

    Next, for getting the actual console handles, there is a function named GetStdHandle. This is what the CRT uses to initialise the CRT somewhere deep in the _ioinit function of the startup code. For a particular console window, it will give out the same handle to all processes using it. (This particular case is easy to infer through experimentation and the fact that this blog post exists in the first place).

  18. Crescens2k says:

    I forgot to add two things to my previous post.

    First, something which may add a bit of confusion is that console handles are pseudohandles. What this means is that the handles themselves given out by GetStdHandle are not the actual handles, but can be used like them and gets redirected automatically to the right handle. Unless of course the parent process used SetStdHandle, then the handle obtained is the actual handle.

    Secondly, I forgot to link…/ms682075(v=vs.85).aspx and mention the fact that it is documented that handles are inherited between parent and child processes for consoles unless you explicitly start a new console session, or start it disconnected from a console.

  19. 640k says:


    It should be documented *better*!

Comments are closed.