|
2 | 2 | /// Subprocess communication with pipes.
|
3 | 3 | /// \author Diffblue Ltd.
|
4 | 4 |
|
| 5 | +// NOTES ON WINDOWS PIPES IMPLEMENTATION |
| 6 | +// |
| 7 | +// This is a note to explain the choices related to the Windows pipes |
| 8 | +// implementation and to serve as information for future work on the |
| 9 | +// Windows parts of this class. |
| 10 | +// |
| 11 | +// Windows supports two kinds of pipes: anonymous and named. |
| 12 | +// |
| 13 | +// Anonymous pipes can only operate in blocking mode. This is a problem for |
| 14 | +// this class because blocking mode pipes (on Windows) will not allow the |
| 15 | +// other end to read until the process providing the data has terminated. |
| 16 | +// (You might think that this is not necessary, but in practice this is |
| 17 | +// the case.) For example, if we ran |
| 18 | +// echo The Jabberwocky; ping 127.0.0.1 -n 6 >nul |
| 19 | +// on the command line in Windows we would see the string "The Jabberwocky" |
| 20 | +// immediately, and then the command would end about 6 seconds later after the |
| 21 | +// pings complete. However, a blocking pipe will see nothing until the ping |
| 22 | +// command has finished, even if the echo has completed and (supposedly) |
| 23 | +// written to the pipe. |
| 24 | +// |
| 25 | +// For the above reason, we NEED to be able to use non-blocking pipes. Since |
| 26 | +// anonymous pipes cannot be non-blocking (in theory they have a named pipe |
| 27 | +// underneath, but it's not clear you could hack this to be non-blocking |
| 28 | +// safely), we have to use named pipes. |
| 29 | +// |
| 30 | +// Named pipes can be non-blocking and this is how we create them. |
| 31 | +// |
| 32 | +// Aside on security: |
| 33 | +// Named pipes can be connected to by other processes and here we have NOT |
| 34 | +// gone deep into the security handling. The default used here is to allow |
| 35 | +// access from the same session token/permissions. This SHOULD be sufficient |
| 36 | +// for what we need. |
| 37 | +// |
| 38 | +// Non-blocking pipes allow immediate reading of any data on the pipre which |
| 39 | +// matches the Linux/MasOC pipe behvariour and also allows reading of the |
| 40 | +// string "The Jabberwocky" from the example above before waiting for the ping |
| 41 | +// command to terminate. This reading can be done with any of the usual pipe |
| 42 | +// read/peek functions, so we use those. |
| 43 | +// |
| 44 | +// There is one problem with the approach used here, that there is no Windows |
| 45 | +// function that can wait on a non-blocking pipe. There are a few options that |
| 46 | +// appear like they would work (or claim they work). Details on these and why |
| 47 | +// they don't work are over-viewed here: |
| 48 | +// - WaitCommEvent claims it can wait for events on a handle (e.g. char |
| 49 | +// written) which would be perfect. Unfortunately on a non-blocking pipe |
| 50 | +// this returns immediately. Using this on a blocking pipe fails to detect |
| 51 | +// that a character is written until the other process terminates in the |
| 52 | +// example above, making this ineffective for what we want. |
| 53 | +// - Setting the pipe timeout or changing blocking after creation. This is |
| 54 | +// theoretically possible, but in practice either has no effect, or can |
| 55 | +// cause a segmentation fault. This was attempted with the SetCommTimeouts |
| 56 | +// function and cause segfault. |
| 57 | +// - Using a wait for event function (e.g. WaitForMultipleObjects, also single |
| 58 | +// object, event, etc.). These can in theory wait until an event, but have |
| 59 | +// the problem that with non-blocking pipes, the wait will not happen since |
| 60 | +// they return immediately. One might think they can work with a blocking |
| 61 | +// pipe and a timeout (i.e. have a blocking read and a timeout thread and |
| 62 | +// wait for one of them to happen to see if there is something to read or |
| 63 | +// whether we could timeout). However, while this can create the right |
| 64 | +// wait and timeout behaviour, since the underlying pipe is blocking this |
| 65 | +// means the example above cannot read "The Jabberwocky" until the ping has |
| 66 | +// finished, again undoing the interactive behaviour desired. |
| 67 | +// Since none of the above work effectivley, the chosen approach is to use a |
| 68 | +// non-blocking peek to see if there is anthing to read, and use a sleep and |
| 69 | +// poll behaviour that might be puch busier than we want. At the time of |
| 70 | +// writing this has not been made smart, just a first choice option for how |
| 71 | +// frequently to poll. |
| 72 | +// |
| 73 | +// Conclusion |
| 74 | +// The implementation is written this way to mitigate the problems with what |
| 75 | +// can and cannot be done with Windows pipes. It's not always pretty, but it |
| 76 | +// does work and handles what we want. |
| 77 | + |
5 | 78 | #ifdef _WIN32
|
6 | 79 | # include "run.h" // for Windows arg quoting
|
7 | 80 | # include "unicode.h" // for widen function
|
|
0 commit comments