-
Notifications
You must be signed in to change notification settings - Fork 274
Piped communication on Windows #6180
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Codecov Report
@@ Coverage Diff @@
## develop #6180 +/- ##
=========================================
Coverage 75.98% 75.98%
=========================================
Files 1507 1512 +5
Lines 163237 163458 +221
=========================================
+ Hits 124032 124208 +176
- Misses 39205 39250 +45
Continue to review full report at Codecov.
|
sec_attr.nLength = sizeof(SECURITY_ATTRIBUTES); | ||
sec_attr.bInheritHandle = TRUE; | ||
sec_attr.lpSecurityDescriptor = NULL; | ||
// Use named pipes to allow non-blocking read |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What access rights does this security descriptor grant? The default one for named pipes gives read access to the EVERYONE group.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I read the documentation here correctly then by defining the security attributes for null
for lpSecurityDescriptor
(sec_attr
approx line 30 of src/util/piped_process.cpp
) this sets the security to be the default for the current session access token. In particular this is NOT available to all users.
That said, I'll add comment about this to the code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added comment to code as well.
src/util/piped_process.cpp
Outdated
// Use named pipes to allow non-blocking read | ||
child_std_IN_Rd = CreateNamedPipe( | ||
TEXT("\\\\.\\pipe\\cbmc\\SMT2\\child\\IN"), | ||
PIPE_ACCESS_INBOUND, // Reading for us |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens when there are multiple instances of CBMC running simultaneously? Do they all try to talk over the same named pipe?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed with use of random number.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rand
isn't random, but produces a deterministic sequence unless you change the seed.
Consider using something you know to be unique, say a process ID.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was seeded, but process ID is better - done.
src/util/piped_process.cpp
Outdated
for(int i = 0; i < commandvec.size(); i++) | ||
{ | ||
cmdline.append(commandvec[i].c_str()); | ||
cmdline.append(" "); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will require quoting. Use quote_windows_arg
from util/run.cpp
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer to leave this to the calling code to properly ensure the handling of quotations (also this breaks unit tests and makes for messy dependencies for now).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a no-go, since it has security implications.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added quoting here.
} | ||
// TODO, make this define and choice better | ||
# define WIN_POLL_WAIT 10 | ||
Sleep(WIN_POLL_WAIT); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no better option than polling? This will heat a lot of CPUs!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't found evidence of one that works nicely with our asynchronous reading from named pipes. The options available for waiting appear to work with blocking pipes, but this does not work with our interactive approach (at least at the moment).
If someone who knows Windows better can suggest an alternative I'll look into it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd say this is a blocker for using this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MsgWaitForMultipleObjectsEx
might do the right thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WaitForMultipleObjects
(and similar ones) rely upon the events they wait on being synchronous. This (so far in testing and from what I've read) means we have to wait for the child process to finish before we can read from the pipe. I saw some example code that used this approach with asynchronous pipes, but then used a busy loop with peek
in the same way I've implemented currently.
I'll keep looking, and play around more now I've sorted out most of the other quirks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See comment/discussion at the top of the file for more on why this is done this way.
src/util/piped_process.cpp
Outdated
TCHAR *szCmdline = | ||
reinterpret_cast<TCHAR *>(malloc(sizeof(TCHAR) * cmdline.size())); | ||
_tcscpy(szCmdline, cmdline.c_str()); | ||
success = CreateProcess( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should use CreateProcessW
, and widen(cmdline)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, along with quoting.
src/util/piped_process.cpp
Outdated
TerminateProcess(proc_info.hProcess, 0); | ||
CloseHandle(proc_info.hProcess); | ||
CloseHandle(proc_info.hThread); | ||
#else |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this clean up the named pipe?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to the docs when all handles are closed/released then Windows cleans up the pipe. I've pushed some more aggressive killing and disconnecting of the child process, but it probably makes no difference in practice.
src/util/piped_process.cpp
Outdated
// and causes heap corruption in Windows if we free! | ||
WCHAR *szCmdline = | ||
reinterpret_cast<WCHAR *>(malloc(sizeof(WCHAR) * cmdline.size())); | ||
wcscpy(szCmdline, cmdline.c_str()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You need to add one WCHAR
to the size of the allocated buffer for the terminating zero.
To avoid this problem altogether, use _wcsdup
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
src/util/piped_process.cpp
Outdated
while(i < commandvec.size()) | ||
{ | ||
args[i] = strdup(commandvec[i].c_str()); | ||
i++; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need for the strdup
, and the subsequent free
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The child process wants to be able to modify the arguments (i.e. non const
) while the commandvec
with c_str()
can only provide const
instances.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This memory is not shared with the child; the child gets a copy. const
is fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add the const
to the type of args
. There is then no need for the const_cast
.
498108c
to
1ef5767
Compare
Now rebased on develop after merging of #6151 . |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great to see some more progress on this, but there are some 🚫 issues which we should address before merging this.
if(process_state != statet::CREATED) | ||
{ | ||
return send_responset::ERROR; | ||
return send_responset::ERRORED; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
⛏️ I would have kept this rename in a separate commit, in order to keep the commits smaller and restricted to a single change. I consider this something to consider for next time, rather than something worth editing the commit history to fix.
LPDWORD nbytes = 0; | ||
LPDWORD rbytes = 0; | ||
LPDWORD rmbytes = 0; | ||
while(timeout < 0 || waited_time >= timeout) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
⛏️ The magic value of -1
for timeout
is set due to the unix API being used. I suggest the "unwrap" is moved into the non-Windows code and this line be written as while(!wait_time || waited_time >= *wait_time)
src/util/piped_process.cpp
Outdated
// access from the same session token/permissions. This SHOULD be sufficient | ||
// for what we need. | ||
// | ||
// Non-blocking pipes allow immediate reading of any data on the pipre which |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pipe
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
src/util/piped_process.cpp
Outdated
// for what we need. | ||
// | ||
// Non-blocking pipes allow immediate reading of any data on the pipre which | ||
// matches the Linux/MasOC pipe behvariour and also allows reading of the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
behaviour
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also: MacOS
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both fixed
src/util/piped_process.cpp
Outdated
// finished, again undoing the interactive behaviour desired. | ||
// Since none of the above work effectivley, the chosen approach is to use a | ||
// non-blocking peek to see if there is anthing to read, and use a sleep and | ||
// poll behaviour that might be puch busier than we want. At the time of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
much?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
// means the example above cannot read "The Jabberwocky" until the ping has | ||
// finished, again undoing the interactive behaviour desired. | ||
// Since none of the above work effectivley, the chosen approach is to use a | ||
// non-blocking peek to see if there is anthing to read, and use a sleep and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would something like this work instead of sleep or is this no better but just with more overhead?
do {
// check whether there's something new
} while (WaitForSingleObject(hEvent, 100) == WAIT_TIMEOUT)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those don't work on non-blocking pipes, they immediately return and so would not actually sleep.
src/util/piped_process.cpp
Outdated
UNIMPLEMENTED_FEATURE("Pipe IPC on windows.") | ||
// Security attributes for pipe creation | ||
SECURITY_ATTRIBUTES sec_attr; | ||
// Ensure pipes are inherited |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
comment should be one line further down
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
src/util/piped_process.cpp
Outdated
PIPE_UNLIMITED_INSTANCES, // Probably doesn't matter | ||
BUFSIZE, | ||
BUFSIZE, // Output and input bufffer sizes | ||
0, // Timeout in ms, 0 = use system default |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the timeout to detect that the other side hasn't connected, I guess?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any insights on this? Would be good to explain to save follow-ups the time for investigating how the API works.
src/util/piped_process.cpp
Outdated
&sec_attr); // For inheritance by child | ||
if(child_std_IN_Rd == INVALID_HANDLE_VALUE) | ||
{ | ||
throw std::runtime_error("Input pipe creation failed for child_std_IN_Rd"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we have exception types in util/exception_util.h
that are more suitable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
src/util/piped_process.cpp
Outdated
@@ -30,8 +101,127 @@ | |||
|
|||
piped_processt::piped_processt(const std::vector<std::string> commandvec) | |||
{ | |||
// Default state |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be possible to split up this mega-function a bit?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Split out a small helper function, but the function is pretty straightforward, just many lines due to many arguments to function calls to create Windows processes/pipes (also lots of commends/explanation).
src/util/piped_process.cpp
Outdated
// for what we need. | ||
// | ||
// Non-blocking pipes allow immediate reading of any data on the pipre which | ||
// matches the Linux/MasOC pipe behvariour and also allows reading of the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also: MacOS
src/util/piped_process.cpp
Outdated
@@ -30,8 +101,127 @@ | |||
|
|||
piped_processt::piped_processt(const std::vector<std::string> commandvec) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I recognise this was here before, but any insight as to why this isn't being passed as a reference? The const
doesn't seem terribly useful at present.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No insight, but fixed now.
src/util/piped_process.cpp
Outdated
sec_attr.lpSecurityDescriptor = NULL; | ||
// Use named pipes to allow non-blocking read | ||
// Build the base name for the pipes | ||
std::string base_name = "\\\\.\\pipe\\cbmc\\SMT2\\child\\"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is generic code in util
, I don't think it should hard-code SMT2
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
3ce4876
to
9cc1d5a
Compare
Make the quote_windows_arg function available for use
This adds in windows versions of the piped_process communication functions.
Adds unit tests for Windows piped process communication. These duplicate the Linux/MacOS ones in most cases. There is also a termination one that does not have a working Linux/MacOS version. (Also z3 is not tested on Windows).
This removes the copy operations from the piped_process class since we do not want to implement these. Also fix unit tests that used these.
This fixes the unit tests to use the std::chrono library insead of windows.h for timing information.
Instead of calling the destructor explicitly, use braces to scope and use implicit destructor calling.
This fixes the including of windows.h to limited scope. Requires some forward definition and uses unique_ptr.
Replace std::* exceptions in Windows code with system_exceptiont
Split out a helper function to process an array of strings into a single wide string for use in Windows commands.
Reduce the number of possible states for the piped_process statet since we only really need to know if it's running or has an error for now.
This changes to pass the vector of strings for the piped_process command by reference instead of by value. Also add some doxygen and static keyword
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
src/util/piped_process.cpp
Outdated
PIPE_UNLIMITED_INSTANCES, // Probably doesn't matter | ||
BUFSIZE, | ||
BUFSIZE, // Output and input bufffer sizes | ||
0, // Timeout in ms, 0 = use system default |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any insights on this? Would be good to explain to save follow-ups the time for investigating how the API works.
unit/util/piped_process.cpp
Outdated
std::vector<std::string> commands; | ||
#ifdef _WIN32 | ||
const std::string expected_error("'abcde' is not recogni"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const std::string expected_error("'abcde' is not recogni"); | |
const std::string expected_error("'abcde' is not recognized"); |
unit/util/piped_process.cpp
Outdated
end_time - start_time); | ||
size_t calc = time_span.count(); | ||
#else | ||
// Currently not working under Linxu/MacOS?! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Currently not working under Linxu/MacOS?! | |
// Currently not working under Linux/MacOS?! |
unit/util/piped_process.cpp
Outdated
size_t calc = time_span.count(); | ||
#else | ||
// Currently not working under Linxu/MacOS?! | ||
// commands.push_back("sleep 6"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Put in #if 0
rather than commenting.
Add comment on timeout in windows.
Tidy up unit test code to have cleaner string in windows failed command test, and useful #if for non-working code.
Piped inter-process communication for Windows.
Old text below.
This is a PR to test CI and see how the piped communication progresses on Windows. This started based on #6151 and so will need to either wait for that to be merged and rebased, or supersede that PR.
Note that this is part of the larger SMT plan available at #6134 .