Skip to content

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

Merged
merged 13 commits into from
Aug 23, 2021
Merged

Piped communication on Windows #6180

merged 13 commits into from
Aug 23, 2021

Conversation

TGWDB
Copy link
Contributor

@TGWDB TGWDB commented Jun 15, 2021

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 .

  • Each commit message has a non-empty body, explaining why the change was made.
  • Methods or procedures I have added are documented, following the guidelines provided in CODING_STANDARD.md.
  • The feature or user visible behaviour I have added or modified has been documented in the User Guide in doc/cprover-manual/
  • Regression or unit tests are included, or existing tests cover the modified code (in this case I have detailed which ones those are in the commit message).
  • My commit message includes data points confirming performance improvements (if claimed).
  • My PR is restricted to a single feature or bugfix.
  • White-space or formatting changes outside the feature-related changed lines are in commits of their own.

@codecov
Copy link

codecov bot commented Jun 15, 2021

Codecov Report

Merging #6180 (e530e4a) into develop (4c039ba) will increase coverage by 0.00%.
The diff coverage is 79.48%.

Impacted file tree graph

@@            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     
Impacted Files Coverage Δ
unit/util/piped_process.cpp 30.65% <57.14%> (+2.08%) ⬆️
src/util/piped_process.cpp 76.92% <83.33%> (+1.92%) ⬆️
src/goto-instrument/contracts/contracts.cpp 92.59% <100.00%> (ø)
src/goto-instrument/loop_utils.cpp 90.62% <0.00%> (-3.93%) ⬇️
src/solvers/flattening/arrays.cpp 80.45% <0.00%> (-2.84%) ⬇️
...sensitivity/variable_sensitivity_configuration.cpp 80.95% <0.00%> (-2.07%) ⬇️
...nsitivity/three_way_merge_abstract_interpreter.cpp 93.02% <0.00%> (-1.32%) ⬇️
src/solvers/smt2/smt2_parser.cpp 77.00% <0.00%> (-0.91%) ⬇️
src/solvers/flattening/boolbv_floatbv_op.cpp 60.21% <0.00%> (-0.84%) ⬇️
src/analyses/ai.h 57.36% <0.00%> (-0.78%) ⬇️
... and 59 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update d6fe178...e530e4a. Read the comment docs.

sec_attr.nLength = sizeof(SECURITY_ATTRIBUTES);
sec_attr.bInheritHandle = TRUE;
sec_attr.lpSecurityDescriptor = NULL;
// Use named pipes to allow non-blocking read
Copy link
Member

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.

Copy link
Contributor Author

@TGWDB TGWDB Jun 16, 2021

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.

Copy link
Contributor Author

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.

// 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
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

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.

Copy link
Contributor Author

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.

for(int i = 0; i < commandvec.size(); i++)
{
cmdline.append(commandvec[i].c_str());
cmdline.append(" ");
Copy link
Member

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

Copy link
Contributor Author

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).

Copy link
Member

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.

Copy link
Contributor Author

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);
Copy link
Member

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!

Copy link
Contributor Author

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.

Copy link
Member

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.

Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

TCHAR *szCmdline =
reinterpret_cast<TCHAR *>(malloc(sizeof(TCHAR) * cmdline.size()));
_tcscpy(szCmdline, cmdline.c_str());
success = CreateProcess(
Copy link
Member

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)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, along with quoting.

TerminateProcess(proc_info.hProcess, 0);
CloseHandle(proc_info.hProcess);
CloseHandle(proc_info.hThread);
#else
Copy link
Member

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?

Copy link
Contributor Author

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.

// and causes heap corruption in Windows if we free!
WCHAR *szCmdline =
reinterpret_cast<WCHAR *>(malloc(sizeof(WCHAR) * cmdline.size()));
wcscpy(szCmdline, cmdline.c_str());
Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

while(i < commandvec.size())
{
args[i] = strdup(commandvec[i].c_str());
i++;
Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. :)

Copy link
Member

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.

@TGWDB TGWDB force-pushed the pipes_infr_win branch 2 times, most recently from 498108c to 1ef5767 Compare June 17, 2021 11:07
@TGWDB TGWDB marked this pull request as ready for review August 11, 2021 13:52
@TGWDB
Copy link
Contributor Author

TGWDB commented Aug 11, 2021

Now rebased on develop after merging of #6151 .

@TGWDB TGWDB changed the title Piped communication on Windows [DRAFT and RFC] Piped communication on Windows Aug 11, 2021
Copy link
Contributor

@thomasspriggs thomasspriggs left a 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;
Copy link
Contributor

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)
Copy link
Contributor

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)

// 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pipe

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

// 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

behaviour

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also: MacOS

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both fixed

// 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

much?

Copy link
Contributor Author

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
Copy link
Member

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)

Copy link
Contributor Author

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.

UNIMPLEMENTED_FEATURE("Pipe IPC on windows.")
// Security attributes for pipe creation
SECURITY_ATTRIBUTES sec_attr;
// Ensure pipes are inherited
Copy link
Member

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

PIPE_UNLIMITED_INSTANCES, // Probably doesn't matter
BUFSIZE,
BUFSIZE, // Output and input bufffer sizes
0, // Timeout in ms, 0 = use system default
Copy link
Member

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?

Copy link
Member

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.

&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");
Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@@ -30,8 +101,127 @@

piped_processt::piped_processt(const std::vector<std::string> commandvec)
{
// Default state
Copy link
Member

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?

Copy link
Contributor Author

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).

// 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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also: MacOS

@@ -30,8 +101,127 @@

piped_processt::piped_processt(const std::vector<std::string> commandvec)
Copy link
Collaborator

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.

Copy link
Contributor Author

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.

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\\";
Copy link
Collaborator

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@TGWDB TGWDB force-pushed the pipes_infr_win branch 2 times, most recently from 3ce4876 to 9cc1d5a Compare August 16, 2021 15:20
Make the quote_windows_arg function available for use
TGWDB added 9 commits August 17, 2021 09:56
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
Copy link
Contributor

@thomasspriggs thomasspriggs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

PIPE_UNLIMITED_INSTANCES, // Probably doesn't matter
BUFSIZE,
BUFSIZE, // Output and input bufffer sizes
0, // Timeout in ms, 0 = use system default
Copy link
Member

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.

std::vector<std::string> commands;
#ifdef _WIN32
const std::string expected_error("'abcde' is not recogni");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const std::string expected_error("'abcde' is not recogni");
const std::string expected_error("'abcde' is not recognized");

end_time - start_time);
size_t calc = time_span.count();
#else
// Currently not working under Linxu/MacOS?!
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Currently not working under Linxu/MacOS?!
// Currently not working under Linux/MacOS?!

size_t calc = time_span.count();
#else
// Currently not working under Linxu/MacOS?!
// commands.push_back("sleep 6");
Copy link
Member

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.

TGWDB added 2 commits August 23, 2021 10:40
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.
@TGWDB TGWDB merged commit dfa3810 into diffblue:develop Aug 23, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants