Skip to content

Commit 8562df3

Browse files
Merge pull request #6675 from thomasspriggs/tas/windows_pipe_robustness
Improvements to robustness of piped process support on Windows
2 parents 1080730 + 52d5788 commit 8562df3

File tree

5 files changed

+70
-48
lines changed

5 files changed

+70
-48
lines changed

regression/cbmc-incr-smt2/bitvector-arithmetic-operators/simple_equation.desc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
CORE
22
simple_equation.c
3-
--incremental-smt2-solver 'z3 --smt2 -in' --trace
3+
--incremental-smt2-solver 'z3 --smt2 -in' --trace --verbosity 10
44
\[main\.assertion\.1\] line \d+ a plus a always equals two times a: SUCCESS
55
\[main\.assertion\.2\] line \d+ a minus a always equals 0: SUCCESS
66
\[main\.assertion\.3\] line \d+ a plus its additive inverse equals 0: SUCCESS

src/solvers/smt2_incremental/smt_solver_process.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ smt_piped_solver_processt::smt_piped_solver_processt(
1313
std::string command_line,
1414
message_handlert &message_handler)
1515
: command_line_description{"\"" + command_line + "\""},
16-
process{split_string(command_line, ' ', false, true)},
16+
process{split_string(command_line, ' ', false, true), message_handler},
1717
log{message_handler}
1818
{
1919
}

src/util/piped_process.cpp

Lines changed: 43 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -88,18 +88,22 @@
8888
# include <unistd.h> // library for read/write/sleep/etc. functions
8989
#endif
9090

91-
# include <cstring> // library for strerror function (on linux)
92-
# include <iostream>
93-
# include <vector>
91+
#include "exception_utils.h"
92+
#include "invariant.h"
93+
#include "narrow.h"
94+
#include "optional.h"
95+
#include "piped_process.h"
96+
#include "string_utils.h"
9497

95-
# include "exception_utils.h"
96-
# include "invariant.h"
97-
# include "narrow.h"
98-
# include "optional.h"
99-
# include "piped_process.h"
100-
# include "string_utils.h"
98+
#include <cstring> // library for strerror function (on linux)
99+
#include <iostream>
100+
#include <vector>
101101

102+
#ifdef _WIN32
103+
# define BUFSIZE (1024 * 64)
104+
#else
102105
# define BUFSIZE 2048
106+
#endif
103107

104108
#ifdef _WIN32
105109
/// This function prepares a single wide string for the windows command
@@ -120,7 +124,10 @@ prepare_windows_command_line(const std::vector<std::string> &commandvec)
120124
}
121125
#endif
122126

123-
piped_processt::piped_processt(const std::vector<std::string> &commandvec)
127+
piped_processt::piped_processt(
128+
const std::vector<std::string> &commandvec,
129+
message_handlert &message_handler)
130+
: log{message_handler}
124131
{
125132
# ifdef _WIN32
126133
// Security attributes for pipe creation
@@ -347,34 +354,40 @@ piped_processt::send_responset piped_processt::send(const std::string &message)
347354
}
348355
#ifdef _WIN32
349356
const auto message_size = narrow<DWORD>(message.size());
357+
PRECONDITION(message_size > 0);
350358
DWORD bytes_written = 0;
351-
const auto write_file = [&]() {
352-
return WriteFile(
353-
child_std_IN_Wr, message.c_str(), message_size, &bytes_written, NULL);
354-
};
355-
if(!write_file())
359+
const int retry_limit = 10;
360+
for(int send_attempts = 0; send_attempts < retry_limit; ++send_attempts)
356361
{
357-
// Error handling with GetLastError ?
358-
return send_responset::FAILED;
359-
}
360-
// `WriteFile` can return a success status but write 0 bytes if we write
361-
// messages quickly enough. This problem has been observed when using a
362-
// release build, but resolved when using a debug build or `--verbosity 10`.
363-
// Presumably this happens if cbmc gets too far ahead of the sub process.
364-
// Flushing the buffer and retrying should then succeed to write the message
365-
// in this case.
366-
if(bytes_written == 0)
367-
{
368-
FlushFileBuffers(child_std_IN_Wr);
369-
if(!write_file())
362+
// `WriteFile` can return a success status but write 0 bytes if we write
363+
// messages quickly enough. This problem has been observed when using a
364+
// release build, but resolved when using a debug build or `--verbosity 10`.
365+
// Presumably this happens if cbmc gets too far ahead of the sub process.
366+
// Flushing the buffer and retrying should then succeed to write the message
367+
// in this case.
368+
if(!WriteFile(
369+
child_std_IN_Wr, message.c_str(), message_size, &bytes_written, NULL))
370370
{
371-
// Error handling with GetLastError ?
371+
const DWORD error_code = GetLastError();
372+
log.debug() << "Last error code is " + std::to_string(error_code)
373+
<< messaget::eom;
372374
return send_responset::FAILED;
373375
}
376+
if(bytes_written != 0)
377+
break;
378+
// Give the sub-process chance to read the waiting message(s).
379+
const auto wait_milliseconds = narrow<DWORD>(1 << send_attempts);
380+
log.debug() << "Zero bytes send to sub process. Retrying in "
381+
<< wait_milliseconds << " milliseconds." << messaget::eom;
382+
FlushFileBuffers(child_std_IN_Wr);
383+
Sleep(wait_milliseconds);
374384
}
375385
INVARIANT(
376386
message_size == bytes_written,
377-
"Number of bytes written to sub process must match message size.");
387+
"Number of bytes written to sub process must match message size."
388+
"Message size is " +
389+
std::to_string(message_size) + " but " + std::to_string(bytes_written) +
390+
" bytes were written.");
378391
#else
379392
// send message to solver process
380393
int send_status = fputs(message.c_str(), command_stream);

src/util/piped_process.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ typedef struct _PROCESS_INFORMATION PROCESS_INFORMATION; // NOLINT
1313
typedef void *HANDLE; // NOLINT
1414
#endif
1515

16+
#include "message.h"
1617
#include "nodiscard.h"
1718
#include "optional.h"
19+
1820
#include <vector>
1921

2022
#define PIPED_PROCESS_INFINITE_TIMEOUT \
@@ -79,7 +81,10 @@ class piped_processt
7981
/// Initiate a new subprocess with pipes supporting communication
8082
/// between the parent (this process) and the child.
8183
/// \param commandvec The command and arguments to create the process
82-
explicit piped_processt(const std::vector<std::string> &commandvec);
84+
/// \param message_handler Optional message handler for logging debug messages
85+
explicit piped_processt(
86+
const std::vector<std::string> &commandvec,
87+
message_handlert &message_handler);
8388

8489
// Deleted due to declaring an explicit destructor and not wanting copy
8590
// constructors to be implemented.
@@ -108,6 +113,7 @@ class piped_processt
108113
int pipe_output[2];
109114
#endif
110115
statet process_state;
116+
messaget log;
111117
};
112118

113119
#endif // endifndef CPROVER_UTIL_PIPED_PROCESS_H

unit/util/piped_process.cpp

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@
22
/// \author Diffblue Ltd.
33
/// Unit tests for checking the piped process communication mechanism.
44

5-
# include <testing-utils/use_catch.h>
6-
# include <util/optional.h>
7-
# include <util/piped_process.h>
8-
# include <util/string_utils.h>
5+
#include <util/optional.h>
6+
#include <util/piped_process.h>
7+
#include <util/string_utils.h>
8+
9+
#include <testing-utils/message.h>
10+
#include <testing-utils/use_catch.h>
11+
912
// Used for testing destructor/timing
1013
#include <chrono>
1114

@@ -22,7 +25,7 @@ TEST_CASE(
2225
commands.push_back("/bin/echo");
2326
commands.push_back(to_be_echoed);
2427
#endif
25-
piped_processt process(commands);
28+
piped_processt process(commands, null_message_handler);
2629

2730
// This is an indirect way to detect when the pipe has something. This
2831
// could (in theory) also return when there is an error, but this unit
@@ -45,7 +48,7 @@ TEST_CASE(
4548
const std::string expected_error("Launching abcde failed");
4649
commands.push_back("abcde");
4750
#endif
48-
piped_processt process(commands);
51+
piped_processt process(commands, null_message_handler);
4952

5053
// This is an indirect way to detect when the pipe has something. This
5154
// could (in theory) also return when there is an error, but this unit
@@ -81,7 +84,7 @@ TEST_CASE(
8184
std::chrono::steady_clock::now();
8285
{
8386
// Scope restriction to cause destruction
84-
piped_processt process(commands);
87+
piped_processt process(commands, null_message_handler);
8588
}
8689
std::chrono::steady_clock::time_point end_time =
8790
std::chrono::steady_clock::now();
@@ -95,7 +98,7 @@ TEST_CASE(
9598
# if 0
9699
commands.push_back("sleep 6");
97100
time_t calc = time(NULL);
98-
piped_processt process(commands);
101+
piped_processt process(commands, null_message_handler);
99102
process.~piped_processt();
100103
calc = time(NULL) - calc;
101104
# else
@@ -114,7 +117,7 @@ TEST_CASE(
114117
std::vector<std::string> commands;
115118
commands.push_back("z3");
116119
commands.push_back("-in");
117-
piped_processt process(commands);
120+
piped_processt process(commands, null_message_handler);
118121

119122
REQUIRE(
120123
process.send("(echo \"hi\")\n") ==
@@ -136,7 +139,7 @@ TEST_CASE(
136139
commands.push_back("z3");
137140
commands.push_back("-in");
138141
const std::string termination_statement = "(exit)\n";
139-
piped_processt process(commands);
142+
piped_processt process(commands, null_message_handler);
140143

141144
REQUIRE(
142145
process.send("(echo \"hi\")\n") ==
@@ -166,7 +169,7 @@ TEST_CASE(
166169
commands.push_back("z3");
167170
commands.push_back("-in");
168171
commands.push_back("-smt2");
169-
piped_processt process(commands);
172+
piped_processt process(commands, null_message_handler);
170173

171174
std::string message =
172175
"(set-logic QF_LIA) (declare-const x Int) (declare-const y Int) (assert (> "
@@ -190,7 +193,7 @@ TEST_CASE(
190193
commands.push_back("z3");
191194
commands.push_back("-in");
192195
commands.push_back("-smt2");
193-
piped_processt process(commands);
196+
piped_processt process(commands, null_message_handler);
194197

195198
std::string statement =
196199
"(set-logic QF_LIA) (declare-const x Int) (declare-const y Int) (assert (> "
@@ -212,7 +215,7 @@ TEST_CASE(
212215
std::vector<std::string> commands;
213216
commands.push_back("z3");
214217
commands.push_back("-in");
215-
piped_processt process(commands);
218+
piped_processt process(commands, null_message_handler);
216219

217220
REQUIRE(
218221
process.send("(echo \"hi\")\n") ==
@@ -242,7 +245,7 @@ TEST_CASE(
242245
commands.push_back("z3");
243246
commands.push_back("-in");
244247
commands.push_back("-smt2");
245-
piped_processt process(commands);
248+
piped_processt process(commands, null_message_handler);
246249

247250
std::string statement =
248251
"(set-logic QF_LIA) (declare-const x Int) (declare-const y Int) (assert (> "
@@ -298,7 +301,7 @@ TEST_CASE(
298301
commands.push_back("z3");
299302
commands.push_back("-in");
300303
commands.push_back("-smt2");
301-
piped_processt process(commands);
304+
piped_processt process(commands, null_message_handler);
302305

303306
std::string statement =
304307
"(set-logic QF_LIA) (declare-const x Int) (declare-const y Int) (assert (> "

0 commit comments

Comments
 (0)