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(&currentTime, 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