Skip to content

Commit 92a9a42

Browse files
committed
Add explanation on Windows pipes implementation design
This adds a lot of comments about the Windows pipes implementation to explain the choces made and also where things do not work despite the documentation/APIs claiming they should.
1 parent 1ef5767 commit 92a9a42

File tree

1 file changed

+73
-0
lines changed

1 file changed

+73
-0
lines changed

src/util/piped_process.cpp

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,79 @@
22
/// Subprocess communication with pipes.
33
/// \author Diffblue Ltd.
44

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+
578
#ifdef _WIN32
679
# include "run.h" // for Windows arg quoting
780
# include "unicode.h" // for widen function

0 commit comments

Comments
 (0)