Standard handles are really meant for single-threaded programs


When I discussed the conventions for managing standard handles, Sven2 noted that I implied that you need to call Set­Std­Handle with a new handle if you close the current handle and asked "Wouldn't it make more sense to call it the other way round? I.e., first set the new handle, then close the old one? It would ensure that any other thread that runs in parallel won't get an invalid handle."

Yes, that would make more sense, but only by a little bit. If you have one thread changing a standard handle at the same time another thread tries to use it, you still have the race condition, as Cesar noted, where the thread that gets the standard handle gets the handle closed out from under it. So you still have a race condition. All you did was narrow the window a little bit.

This is basically a fundamental limitation of the standard handles. They are a shared process-wide resource, and if you're going to be mucking with them from multiple threads, it's your responsibility to apply whatever synchronization you need in order to avoid the problems associated with messing with process-wide resources. (This is similar to the problem with inherited handles and Create­Process.)

The most common way of ensuring that one thread doesn't change a standard handle while another thread is using it is simple: Never change a standard handle. Consider standard handles a setting provided by the parent process. If the parent process says that standard output should go to a particular place, then send it to that place. Don't try to override the decision and send it somewhere else.

If you really must change a standard handle, you'd be best off doing so right at the start before you start kicking off background threads. Another model you might try is to treat the initial thread as the "console UI thread" and make that the only thread that can communicate with the standard handles. Background threads can do work, but if they want to write to standard output or read from standard input, they need to ask the main thread to do it. This is probably a good plan anyway, because it avoids messy interleaved output as well as confusing input. (If two threads read from standard input at the same time, it's not clear to the user which thread their input will go to.)

Personally, I would recommend combining both approaches: (1) Never change a standard handle, and (2) Restrict all usage of standard handles to your main thread to avoid problems with interleaving.

Comments (19)
  1. Joshua says:

    [If you really must change a standard handle, you'd be best off doing so right at the start]

    That's my model.

  2. alegr1 says:

    If you ever close a handle, don't let a separate thread use it. Duplicate the handle for another thread, or synchronize usage and closing carefully.

  3. Cesar says:

    Unix systems have the dup2() system call, which make one file descriptor a clone of another. So instead of closing, one could open the new target, use dup2() to make the original file descriptor point to it, and close the (now redundant) new file descriptor. I have not checked, but I believe this operation is atomic (taking the file descriptor table lock in the kernel).

    I wonder if Win32 has something like that. DuplicateHandle does not seem to allow one to specify the target handle; it seems to always create a new fresh handle, instead of being able to replace an open handle.

  4. ErikF says:

    @Cesar: Why wonder when you can find out? Here's the _dup, _dup2 page for VS2013: msdn.microsoft.com/…/8syseb29.aspx . Yes, it's part of the VS CRT but that makes sense considering that Win32 isn't libc.

    [dup2 operates on file descriptors, not handles. The CRT emulates file descriptors by having a private table that maps descriptors to handles, so the CRT's dup2 can just update the private table. -Raymond]
  5. Joshua says:

    /me wonders what Cesar thinks SuspendThread is for.

  6. Gabe says:

    Joshua: Where does SuspendThread enter the picture?

  7. meh says:

    That's why I use unicorn handles in all my programs.

  8. Cesar says:

    @Joshua: SuspendThread is for debugging. Unless you know precisely what code is running at that moment within the thread (but in that case, the code itself should wait for whatever event you want), you are asking for trouble (the thread might be holding some resource your code will directly or indirectly need, leading to a deadlock). The MSDN documentation for SuspendThread says the same thing.

    But I echo @Gabe: what does SuspendThread have to do with this standard handles discussion?

  9. Gabe says:

    Cesar: I've never written a garbage collector, but if I were to do so, I would use SuspendThread. Although you could argue that a GC is similar to a debugger in its need to walk stacks, inspect the heap, and look at registers.

    [If you use SuspendThread, then you would have to use a noncopying collector (and probably a conservative collector) because the thread you interrupted may have already loaded the address of an object, and you therefore can't move it. -Raymond]
  10. Joshua says:

    It's pretty simple actually. By suspending all other threads we can make atomic operations. Be careful. The only safe way to allocate memory in the protected region is VirtualAlloc.

  11. Ben Voigt says:

    @Joshua: First you have to get a list of all other threads in the process.  Including ones started between the time you got the list and the time you suspended the thread starting new threads.  And threads spawned for signal handling (console control handler).  And threads injected remotely.  And threads started by libraries you didn't right, which happen to have taken a lock you need, from a thread you don't know about.

    No, sorry.  Suspending other threads is not the right implementation of a critical section.

  12. Joshua says:

    @Ben Voigt: I had a draft implementation of this. It works by enumerating threads so yes it catches remote-injected threads. The only difficult part it taking the loader lock so that any newly injected threads have to wait.

    It's hard to argue about right implementation when it's the only possible one since the kernel has no dup2 equivalent and it's missed.

  13. Gabe says:

    If I were writing a GC, it would likely either be for an environment like C (where it would have to be very conservative because you never know what might be a pointer into an object), or it would be for a more controlled environment (like managed code).

    For a copying collector, there would be times when it is not safe to do a GC. In order to find out if a thread is executing in one of those unsafe zones, I would have to check the thread's PC or stack to look for known unsafe areas.

    If a thread isn't in a safe zone, I would have to change the return address of the the earliest unsafe zone to redirect to a function that would wait for the GC to be over. Or I suppose maybe I could single-step the thread until it returns to a safe zone. :)

    So if I can just tell a thread to wait on an event to signal the end of the GC, I don't need SuspendThread, right? Well, I need to be able to mess with the thread's stack or PC in the first place. I don't want my thread inspector to be in a race with the inspectee. I would need SuspendThread to freeze the thread's stack in order to tell it to wait on the GC event.

    In other words: in a controlled environment I could change a standard handle by freezing all the threads (using return address redirection for any thread currently using the handle), swapping the handle, and then replacing any reference to the old handle with the new one.

    [Detecting whether the program counter is in an unsafe zone is hard. -Raymond]
  14. Gabe says:

    Is it that hard to determine if an instruction is in an usafe zone?

    Since this is a controlled environment, the code generator could easily make a table of all unsafe zones (like it would for exception handlers). When checking a return address on the stack or a program counter, I could look it up in the table to see if it's unsafe.

    I may be naive, as I haven't actually implemented anything like this, but it seems like a decent solution.

    [An instruction is unsafe if, at the start of the instruction, there is a pointer to the heap in any live register. So your table is going to be crazy huge.
    BEGIN_UNSAFE ; first parameter is in ecx
    sub esp, 4
    mov [esp], ecx ; save first parameter
    END_UNSAFE
    mov ecx, [globalObject]
    BEGIN_UNSAFE
    call GlobalObject.GetCurrentValue
    END_UNSAFE
    mov ecx, [esp]; recover first parameter
    BEGIN_UNSAFE
    mov [ecx], eax
    END_UNSAFE
    pop ecx
    ret
    -Raymond
    ]
  15. Joshua says:

    [Detecting whether the program counter is in an unsafe zone is hard. -Raymond]

    My response: Check if a register equals a stack frame variable that we already know points to an object. My table is a table of stack frames. True this is very slightly conservative on which objects can move or not.

  16. Gabe says:

    When a heap pointer is in a register that doesn't make GC unsafe, it simply means that I have to use that register as a GC root. Obviously I would need some table to indicate that.

    Actual unsafe zones would be much more rare, like cases where I have a pointer to the inside of an object, or where an object has been allocated but not yet initialized.

  17. Kevin says:

    @Joshua: What if someone writes something like this?:

       void *foo = &something_on_the_heap[5];

  18. Cesar says:

    @Gabe: in a controlled environment you could create your own standard handle abstraction, making this whole discussion moot.

  19. Joshua says:

    Kevin: Now that's what I call paying attention. This generates:

      mov eax, [ebx + 5*4]

      mov [esp + offset], eax

    Guess what the GC does. (Gee nice to have a controlled compiler in kahoots with the GC isn't it?)

Comments are closed.

Skip to main content