A set of benchmarks to compare a new prototype MiniZinc implementation
1/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */
2
3/*
4 * Main authors:
5 * Guido Tack <guido.tack@monash.edu>
6 */
7
8/* This Source Code Form is subject to the terms of the Mozilla Public
9 * License, v. 2.0. If a copy of the MPL was not distributed with this
10 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
11
12#ifndef __MINIZINC_PROCESS_HH__
13#define __MINIZINC_PROCESS_HH__
14
15#include <minizinc/solver.hh>
16
17const auto SolverInstance__ERROR = MiniZinc::SolverInstance::ERROR; // before windows.h
18#ifdef _WIN32
19#define NOMINMAX
20#include <Windows.h>
21#include <tchar.h>
22//#include <atlstr.h>
23#else
24#include <sys/select.h>
25#include <sys/time.h>
26#include <sys/wait.h>
27#include <unistd.h>
28#endif
29#include <mutex>
30#include <signal.h>
31#include <string>
32#include <sys/types.h>
33#include <thread>
34#include <vector>
35
36namespace MiniZinc {
37
38#ifdef _WIN32
39
40template <class S2O>
41void ReadPipePrint(HANDLE g_hCh, bool* _done, std::ostream* pOs, S2O* pSo, std::mutex* mtx) {
42 bool& done = *_done;
43 assert(pOs != 0 || pSo != 0);
44 while (!done) {
45 char buffer[5255];
46 char nl_buffer[5255];
47 DWORD count = 0;
48 BOOL bSuccess = ReadFile(g_hCh, buffer, sizeof(buffer) - 1, &count, NULL);
49 if (bSuccess && count > 0) {
50 int nl_count = 0;
51 for (int i = 0; i < count; i++) {
52 if (buffer[i] != 13) {
53 nl_buffer[nl_count++] = buffer[i];
54 }
55 }
56 nl_buffer[nl_count] = 0;
57 std::lock_guard<std::mutex> lck(*mtx);
58 if (pSo) pSo->feedRawDataChunk(nl_buffer);
59 if (pOs) (*pOs) << nl_buffer << std::flush;
60 } else {
61 if (pSo) pSo->feedRawDataChunk("\n"); // in case the last chunk had none
62 done = true;
63 }
64 }
65}
66
67void TimeOut(HANDLE hProcess, bool* doneStdout, bool* doneStderr, int timeout,
68 std::timed_mutex* mtx);
69
70#endif
71
72template <class S2O>
73class Process {
74protected:
75 std::vector<std::string> _fzncmd;
76 S2O* pS2Out;
77 int timelimit;
78 bool sigint;
79#ifndef _WIN32
80 static void handleInterrupt(int signal) {
81 if (signal == SIGINT)
82 hadInterrupt = true;
83 else
84 hadTerm = true;
85 }
86 static bool hadInterrupt;
87 static bool hadTerm;
88#endif
89public:
90 Process(std::vector<std::string>& fzncmd, S2O* pso, int tl, bool si)
91 : _fzncmd(fzncmd), pS2Out(pso), timelimit(tl), sigint(si) {
92 assert(0 != pS2Out);
93 }
94 int run(void) {
95#ifdef _WIN32
96 // TODO: implement hard timelimits for windows
97
98 SECURITY_ATTRIBUTES saAttr;
99 saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
100 saAttr.bInheritHandle = TRUE;
101 saAttr.lpSecurityDescriptor = NULL;
102
103 HANDLE g_hChildStd_IN_Rd = NULL;
104 HANDLE g_hChildStd_IN_Wr = NULL;
105 HANDLE g_hChildStd_OUT_Rd = NULL;
106 HANDLE g_hChildStd_OUT_Wr = NULL;
107 HANDLE g_hChildStd_ERR_Rd = NULL;
108 HANDLE g_hChildStd_ERR_Wr = NULL;
109
110 // Create a pipe for the child process's STDOUT.
111 if (!CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr, 0))
112 std::cerr << "Stdout CreatePipe" << std::endl;
113 // Ensure the read handle to the pipe for STDOUT is not inherited.
114 if (!SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0))
115 std::cerr << "Stdout SetHandleInformation" << std::endl;
116
117 // Create a pipe for the child process's STDERR.
118 if (!CreatePipe(&g_hChildStd_ERR_Rd, &g_hChildStd_ERR_Wr, &saAttr, 0))
119 std::cerr << "Stderr CreatePipe" << std::endl;
120 // Ensure the read handle to the pipe for STDERR is not inherited.
121 if (!SetHandleInformation(g_hChildStd_ERR_Rd, HANDLE_FLAG_INHERIT, 0))
122 std::cerr << "Stderr SetHandleInformation" << std::endl;
123
124 // Create a pipe for the child process's STDIN
125 if (!CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr, 0))
126 std::cerr << "Stdin CreatePipe" << std::endl;
127 // Ensure the write handle to the pipe for STDIN is not inherited.
128 if (!SetHandleInformation(g_hChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0))
129 std::cerr << "Stdin SetHandleInformation" << std::endl;
130
131 PROCESS_INFORMATION piProcInfo;
132 STARTUPINFO siStartInfo;
133 BOOL bSuccess = FALSE;
134
135 // Set up members of the PROCESS_INFORMATION structure.
136 ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));
137
138 // Set up members of the STARTUPINFO structure.
139 // This structure specifies the STDIN and STDOUT handles for redirection.
140 ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
141 siStartInfo.cb = sizeof(STARTUPINFO);
142 siStartInfo.hStdError = g_hChildStd_ERR_Wr;
143 siStartInfo.hStdOutput = g_hChildStd_OUT_Wr;
144 siStartInfo.hStdInput = g_hChildStd_IN_Rd;
145 siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
146
147 std::string cmdline = FileUtils::combineCmdLine(_fzncmd);
148 char* cmdstr = strdup(cmdline.c_str());
149
150 BOOL processStarted = CreateProcess(NULL,
151 cmdstr, // command line
152 NULL, // process security attributes
153 NULL, // primary thread security attributes
154 TRUE, // handles are inherited
155 0, // creation flags
156 NULL, // use parent's environment
157 NULL, // use parent's current directory
158 &siStartInfo, // STARTUPINFO pointer
159 &piProcInfo); // receives PROCESS_INFORMATION
160
161 if (!processStarted) {
162 std::stringstream ssm;
163 ssm << "Error occurred when executing FZN solver with command \"" << cmdstr << "\".";
164 throw InternalError(ssm.str());
165 }
166
167 CloseHandle(piProcInfo.hThread);
168 delete cmdstr;
169
170 // Stop ReadFile from blocking
171 CloseHandle(g_hChildStd_OUT_Wr);
172 CloseHandle(g_hChildStd_ERR_Wr);
173 // Just close the child's in pipe here
174 CloseHandle(g_hChildStd_IN_Rd);
175 bool doneStdout = false;
176 bool doneStderr = false;
177 // Threaded solution seems simpler than asyncronous pipe reading
178 std::mutex pipeMutex;
179 std::timed_mutex terminateMutex;
180 terminateMutex.lock();
181 thread thrStdout(&ReadPipePrint<S2O>, g_hChildStd_OUT_Rd, &doneStdout, nullptr, pS2Out,
182 &pipeMutex);
183 thread thrStderr(&ReadPipePrint<S2O>, g_hChildStd_ERR_Rd, &doneStderr, &pS2Out->getLog(),
184 nullptr, &pipeMutex);
185 thread thrTimeout(TimeOut, piProcInfo.hProcess, &doneStdout, &doneStderr, timelimit,
186 &terminateMutex);
187 thrStdout.join();
188 thrStderr.join();
189 terminateMutex.unlock();
190 thrTimeout.join();
191 DWORD exitCode = 0;
192 if (GetExitCodeProcess(piProcInfo.hProcess, &exitCode) == FALSE) {
193 exitCode = 1;
194 }
195 CloseHandle(piProcInfo.hProcess);
196
197 // Hard timeout: GenerateConsoleCtrlEvent()
198
199 return exitCode;
200 }
201#else
202 int pipes[3][2];
203 pipe(pipes[0]);
204 pipe(pipes[1]);
205 pipe(pipes[2]);
206
207 if (int childPID = fork()) {
208 close(pipes[0][0]);
209 close(pipes[1][1]);
210 close(pipes[2][1]);
211 close(pipes[0][1]);
212
213 fd_set fdset;
214 FD_ZERO(&fdset);
215
216 struct timeval starttime;
217 gettimeofday(&starttime, NULL);
218
219 struct timeval timeout_orig;
220 timeout_orig.tv_sec = timelimit / 1000;
221 timeout_orig.tv_usec = (timelimit % 1000) * 1000;
222 struct timeval timeout = timeout_orig;
223
224 hadInterrupt = false;
225 hadTerm = false;
226 struct sigaction sa;
227 struct sigaction old_sa_int;
228 struct sigaction old_sa_term;
229 sa.sa_handler = &handleInterrupt;
230 sa.sa_flags = 0;
231 sigfillset(&sa.sa_mask);
232 sigaction(SIGINT, &sa, &old_sa_int);
233 sigaction(SIGTERM, &sa, &old_sa_term);
234
235 bool done = hadTerm || hadInterrupt;
236 bool timed_out = false;
237 while (!done) {
238 FD_SET(pipes[1][0], &fdset);
239 FD_SET(pipes[2][0], &fdset);
240 int sel = select(FD_SETSIZE, &fdset, NULL, NULL, timelimit == 0 ? NULL : &timeout);
241 if (sel == -1) {
242 if (errno != EINTR) {
243 // some error has happened
244 throw InternalError(std::string("Error in communication with solver: ") +
245 strerror(errno));
246 }
247 }
248 if (timelimit != 0) {
249 timeval currentTime;
250 gettimeofday(¤tTime, NULL);
251 if (sel != 0) {
252 timeval elapsed;
253 elapsed.tv_sec = currentTime.tv_sec - starttime.tv_sec;
254 elapsed.tv_usec = currentTime.tv_usec - starttime.tv_usec;
255 if (elapsed.tv_usec < 0) {
256 elapsed.tv_sec--;
257 elapsed.tv_usec += 1000000;
258 }
259 // Reset timeout to original limit
260 timeout = timeout_orig;
261 // Subtract elapsed time
262 timeout.tv_usec = timeout.tv_usec - elapsed.tv_usec;
263 if (timeout.tv_usec < 0) {
264 timeout.tv_sec--;
265 timeout.tv_usec += 1000000;
266 }
267 timeout.tv_sec = timeout.tv_sec - elapsed.tv_sec;
268 } else {
269 timeout.tv_usec = 0;
270 timeout.tv_sec = 0;
271 }
272 if (hadTerm || hadInterrupt || timeout.tv_sec < 0 ||
273 (timeout.tv_sec == 0 && timeout.tv_usec == 0)) {
274 timed_out = true;
275 if (sigint) {
276 kill(childPID, SIGINT);
277 timeout.tv_sec = 0;
278 timeout.tv_usec = 200000;
279 timeout_orig = timeout;
280 starttime = currentTime;
281 sigint = false;
282 } else {
283 kill(childPID, SIGTERM);
284 pS2Out->feedRawDataChunk("\n"); // in case last chunk did not end with \n
285 done = true;
286 }
287 }
288 }
289
290 for (int i = 1; i <= 2; ++i) {
291 if (FD_ISSET(pipes[i][0], &fdset)) {
292 char buffer[1000];
293 int count = read(pipes[i][0], buffer, sizeof(buffer) - 1);
294 if (count > 0) {
295 buffer[count] = 0;
296 if (1 == i) {
297 // cerr << "mzn-fzn: raw chunk stdout::: " << flush;
298 // cerr << buffer << flush;
299 pS2Out->feedRawDataChunk(buffer);
300 } else {
301 pS2Out->getLog() << buffer << std::flush;
302 }
303 } else if (1 == i) {
304 pS2Out->feedRawDataChunk("\n"); // in case last chunk did not end with \n
305 done = true;
306 }
307 }
308 }
309 }
310
311 close(pipes[1][0]);
312 close(pipes[2][0]);
313 int exitStatus = timed_out ? 0 : 1;
314 int childStatus;
315 int pidStatus = waitpid(childPID, &childStatus, 0);
316 if (!timed_out && pidStatus > 0) {
317 if (WIFEXITED(childStatus)) {
318 exitStatus = WEXITSTATUS(childStatus);
319 }
320 }
321 sigaction(SIGINT, &old_sa_int, NULL);
322 sigaction(SIGTERM, &old_sa_term, NULL);
323 if (hadInterrupt) {
324 kill(getpid(), SIGINT);
325 }
326 if (hadTerm) {
327 kill(getpid(), SIGTERM);
328 }
329 return exitStatus;
330 } else {
331 close(STDOUT_FILENO);
332 close(STDERR_FILENO);
333 close(STDIN_FILENO);
334 dup2(pipes[0][0], STDIN_FILENO);
335 dup2(pipes[1][1], STDOUT_FILENO);
336 dup2(pipes[2][1], STDERR_FILENO);
337 close(pipes[0][0]);
338 close(pipes[0][1]);
339 close(pipes[1][1]);
340 close(pipes[1][0]);
341 close(pipes[2][1]);
342 close(pipes[2][0]);
343
344 std::vector<char*> cmd_line;
345 for (auto& iCmdl : _fzncmd) {
346 cmd_line.push_back(strdup(iCmdl.c_str()));
347 }
348
349 char** argv = new char*[cmd_line.size() + 1];
350 for (unsigned int i = 0; i < cmd_line.size(); i++) argv[i] = cmd_line[i];
351 argv[cmd_line.size()] = 0;
352
353 int status = execvp(argv[0], argv); // execvp only returns if an error occurs.
354 assert(status == -1); // the returned value will always be -1
355 std::stringstream ssm;
356 ssm << "Error occurred when executing FZN solver with command \"";
357 for (auto& s : cmd_line) ssm << s << ' ';
358 ssm << "\".";
359 throw InternalError(ssm.str());
360 }
361 }
362#endif
363};
364
365#ifndef _WIN32
366template <class S2O>
367bool Process<S2O>::hadInterrupt;
368template <class S2O>
369bool Process<S2O>::hadTerm;
370#endif
371
372} // namespace MiniZinc
373#endif