93be33a6c2 Add complete predicate REVERT: 4f10c82056 Merge branch 'MiniZinc:master' into feature/on_restart REVERT: 0848ce7ec7 Add changelog for 2.5.5 REVERT: 44e2f770d5 Add test for insertion of ArrayLits into CSE REVERT: 8a68d3dea8 Don't insert par expressions into CSE map unless they're an ArrayLit REVERT: 6bf6f1180f Increase version number of development build REVERT: dcaac92a74 Make min/max on array of opt vars return non-optional var. This is consistent with other functions on optional arrays like sum, product, exists, forall. REVERT: 32aa288884 Update changelog REVERT: a4edf0669f Fix flattening of all-par set literals REVERT: 8c1c9605f6 Fix chain compressor, needs to ignore par constants REVERT: 0cad1c6306 Use file_path on include paths to ensure separator consistency REVERT: 05ad7d1931 Update changelog REVERT: 22f5e2557b Define HAVE_CONFIG_H only for UNIX REVERT: 81c7778d55 Define HAVE_CONFIG_H for CBC REVERT: 1f56608e10 mzn-test: don't check symmetry breaking constraints REVERT: 1a9767457e mzn-test: fix parsing %%%mzn-stat: ... output REVERT: a41533fd54 MIP: report CPU time in %%%mzn-stat: solveTime= REVERT: 9d490acd52 Updated docs on OR-Tools v8 installation REVERT: c513f6599f Add changelog for 2.5.4 REVERT: b2eef2772b Follow ids to declarations when flattening par arrays. Fixes #448. REVERT: c5c846d426 Check if result of flattening rhs of a vardecl is par. REVERT: c496052767 Escape strings when pretty printing include items REVERT: 9e379c995e Canonicalise file names before adding include items into "already seen" list REVERT: d5d5d0d88c Use generic flattening inside generators. Fixes #451. REVERT: dc8630a6e9 Small fix to multi-pass library change: use original include path if it is absolute REVERT: 79c6092bd8 Strip library paths from includes in multi-pass compilation. Fixes #455. REVERT: 897875d6d7 Compile infinite domains with holes into constraints. Fixes #457. REVERT: b4e700dc67 Don't create copies of global declarations when creating par versions of functions REVERT: 0e8cc42bb1 Fix typechecker to coerce bool to int in the objective. REVERT: e05523b344 Add test for dzn output of arrays REVERT: 1e0269000e Don't evaluate output_only arrays when generating dzn output. REVERT: 57018c31d6 Fix matrix transposition in lex2 globals REVERT: 2617c0c829 Fix output variables in lex_chain tests REVERT: ef1a250c98 another efort to fix tests REVERT: c00e199dfd Fix test globals_lex_chain.mzn REVERT: b5c997d045 Fix code analysis REVERT: 3352cf0bd5 SCIP constraint handler for lex_chain_..._orbitope REVERT: 4e71a2cc97 Globals lex_chain_..., including lex_chain_..._orbitope REVERT: d807428baf Move test specifcation into the correct folder REVERT: 5be74bc74d MIP decompositions for lex_less_(bool, int, float) REVERT: 36a554ba40 Don't modify infinte domain of optional variables. Fixes #456. REVERT: f9e5306d75 Run clang-format REVERT: 4b57667608 Fix comment reference to relevant test case REVERT: 648f2ab36d Fix equality of indirection annotations REVERT: ef7be5fd78 MIP decompositions for lex_lesseq_(bool=int, float) REVERT: 6511b14e73 Propagate cv flag correctly. REVERT: 6f27ecf1c0 Never insert par expressions into the CSE map. REVERT: 7414f3ca0f Fix cplex id in example configuration REVERT: 7ad7cec506 Update strictly_decreasing with documentation and opt version REVERT: 8029f6e957 Support undefined enums in type checker. REVERT: 79e0f0f546 Revert using mzn_in_symmetry_breaking_constraint() for SCIP's orbisack REVERT: e88efda76c Fix format REVERT: 4802031dc1 Added test mzn_in_symmetry_breaking_constraint() REVERT: e21cc2515a More format fixes 02 ... REVERT: 5bbd67c130 More format fixes... REVERT: d5f9b777ea Format fixes REVERT: 43757a09a0 Remove MIP-specific fzn_less(eq)_bool(_reif).mzn REVERT: c93b5736a3 SCIP: orbisack constraint handler 'fzn_lex_lesseq__orbisack' REVERT: 4516bb4e2c mzn-test.py: add OR-Tools as checker REVERT: e2176f017d Add fix and test for crash with empty enum. REVERT: ac7db35951 Fix documentation bugs. REVERT: 47ba245832 Fix the incorrect renaming of key in model output interface REVERT: 925796ed20 Fail on empty var domains in agenda REVERT: 1ec19d7025 Fix error messages in CMake FindGurobi, FindCPlex REVERT: 6d169475c1 mzn-test.py: Use -i for intermediate solutions REVERT: df2f3e423a Allow coercion of JSON lists to enum definitions REVERT: 2b0b8165e5 Fix clang-tidy errors REVERT: 6597bc1920 Change the CI build image location REVERT: 360c988452 Remove illegal duplicate keys in .gitlab-ci.yml REVERT: 6a5d69c64b Add missing par opt versions of coercion functions REVERT: 63014e3d8f Don't propagate annotations into annotation calls. Avoids infinite recursion. REVERT: 54b19428ab Don't use GRB_INT_PAR_NONCONVEX if it's undefined REVERT: a5bb56c47d Added piecewise_linear for non-continuous intervals git-subtree-dir: software/minizinc git-subtree-split: 93be33a6c254e54be7cd38abb7ebd6a6022f0c46
529 lines
17 KiB
C++
529 lines
17 KiB
C++
/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
|
|
|
/*
|
|
* Main authors:
|
|
* Guido Tack <guido.tack@monash.edu>
|
|
*/
|
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#pragma once
|
|
|
|
#include <minizinc/solver.hh>
|
|
|
|
const auto SolverInstance__ERROR = MiniZinc::SolverInstance::ERROR; // before windows.h
|
|
#ifdef _WIN32
|
|
#define NOMINMAX
|
|
#include <Windows.h>
|
|
#include <tchar.h>
|
|
#undef ERROR
|
|
//#include <atlstr.h>
|
|
#else
|
|
#include <sys/select.h>
|
|
#include <sys/time.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
#include <condition_variable>
|
|
#include <csignal>
|
|
#include <deque>
|
|
#include <mutex>
|
|
#include <string>
|
|
#include <sys/types.h>
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
namespace MiniZinc {
|
|
|
|
#ifdef _WIN32
|
|
|
|
template <class S2O>
|
|
void ReadPipePrint(HANDLE g_hCh, bool* _done, std::ostream* pOs,
|
|
std::deque<std::string>* outputQueue, std::mutex* mtx, std::mutex* cv_mutex,
|
|
std::condition_variable* cv) {
|
|
bool& done = *_done;
|
|
assert(pOs != 0 || outputQueue != 0);
|
|
while (!done) {
|
|
char buffer[5255];
|
|
char nl_buffer[5255];
|
|
DWORD count = 0;
|
|
BOOL bSuccess = ReadFile(g_hCh, buffer, sizeof(buffer) - 1, &count, NULL);
|
|
if (bSuccess && count > 0) {
|
|
int nl_count = 0;
|
|
for (int i = 0; i < count; i++) {
|
|
if (buffer[i] != 13) {
|
|
nl_buffer[nl_count++] = buffer[i];
|
|
}
|
|
}
|
|
nl_buffer[nl_count] = 0;
|
|
std::lock_guard<std::mutex> lck(*mtx);
|
|
if (outputQueue) {
|
|
std::unique_lock<std::mutex> lk(*cv_mutex);
|
|
bool wasEmpty = outputQueue->empty();
|
|
outputQueue->push_back(nl_buffer);
|
|
lk.unlock();
|
|
if (wasEmpty) {
|
|
cv->notify_one();
|
|
}
|
|
}
|
|
if (pOs) (*pOs) << nl_buffer << std::flush;
|
|
} else {
|
|
if (outputQueue) {
|
|
std::unique_lock<std::mutex> lk(*cv_mutex);
|
|
bool wasEmpty = outputQueue->empty();
|
|
outputQueue->push_back("\n");
|
|
done = true;
|
|
lk.unlock();
|
|
if (wasEmpty) {
|
|
cv->notify_one();
|
|
}
|
|
} else {
|
|
done = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
template <class S2O>
|
|
class Process {
|
|
protected:
|
|
std::vector<std::string> _fzncmd;
|
|
S2O* _pS2Out;
|
|
int _timelimit;
|
|
bool _sigint;
|
|
#ifdef _WIN32
|
|
static BOOL WINAPI handleInterrupt(DWORD fdwCtrlType) {
|
|
switch (fdwCtrlType) {
|
|
case CTRL_C_EVENT: {
|
|
std::unique_lock<std::mutex> lck(_interruptMutex);
|
|
hadInterrupt = true;
|
|
_interruptCondition.notify_all();
|
|
return TRUE;
|
|
}
|
|
default:
|
|
return FALSE;
|
|
}
|
|
}
|
|
static std::mutex _interruptMutex;
|
|
static std::condition_variable _interruptCondition;
|
|
#else
|
|
static void handleInterrupt(int signal) {
|
|
if (signal == SIGINT) {
|
|
hadInterrupt = true;
|
|
} else {
|
|
hadTerm = true;
|
|
}
|
|
}
|
|
static bool hadTerm;
|
|
#endif
|
|
static bool hadInterrupt;
|
|
|
|
public:
|
|
Process(std::vector<std::string>& fzncmd, S2O* pso, int tl, bool si)
|
|
: _fzncmd(fzncmd), _pS2Out(pso), _timelimit(tl), _sigint(si) {
|
|
assert(nullptr != _pS2Out);
|
|
}
|
|
int run() {
|
|
#ifdef _WIN32
|
|
SetConsoleCtrlHandler(handleInterrupt, TRUE);
|
|
|
|
SECURITY_ATTRIBUTES saAttr;
|
|
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
|
|
saAttr.bInheritHandle = TRUE;
|
|
saAttr.lpSecurityDescriptor = NULL;
|
|
|
|
HANDLE g_hChildStd_IN_Rd = NULL;
|
|
HANDLE g_hChildStd_IN_Wr = NULL;
|
|
HANDLE g_hChildStd_OUT_Rd = NULL;
|
|
HANDLE g_hChildStd_OUT_Wr = NULL;
|
|
HANDLE g_hChildStd_ERR_Rd = NULL;
|
|
HANDLE g_hChildStd_ERR_Wr = NULL;
|
|
|
|
// Create a pipe for the child process's STDOUT.
|
|
if (!CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr, 0))
|
|
std::cerr << "Stdout CreatePipe" << std::endl;
|
|
// Ensure the read handle to the pipe for STDOUT is not inherited.
|
|
if (!SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0))
|
|
std::cerr << "Stdout SetHandleInformation" << std::endl;
|
|
|
|
// Create a pipe for the child process's STDERR.
|
|
if (!CreatePipe(&g_hChildStd_ERR_Rd, &g_hChildStd_ERR_Wr, &saAttr, 0))
|
|
std::cerr << "Stderr CreatePipe" << std::endl;
|
|
// Ensure the read handle to the pipe for STDERR is not inherited.
|
|
if (!SetHandleInformation(g_hChildStd_ERR_Rd, HANDLE_FLAG_INHERIT, 0))
|
|
std::cerr << "Stderr SetHandleInformation" << std::endl;
|
|
|
|
// Create a pipe for the child process's STDIN
|
|
if (!CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr, 0))
|
|
std::cerr << "Stdin CreatePipe" << std::endl;
|
|
// Ensure the write handle to the pipe for STDIN is not inherited.
|
|
if (!SetHandleInformation(g_hChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0))
|
|
std::cerr << "Stdin SetHandleInformation" << std::endl;
|
|
|
|
PROCESS_INFORMATION piProcInfo;
|
|
STARTUPINFOW siStartInfo;
|
|
BOOL bSuccess = FALSE;
|
|
|
|
// Set up members of the PROCESS_INFORMATION structure.
|
|
ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));
|
|
|
|
// Set up members of the STARTUPINFO structure.
|
|
// This structure specifies the STDIN and STDOUT handles for redirection.
|
|
ZeroMemory(&siStartInfo, sizeof(STARTUPINFOW));
|
|
siStartInfo.cb = sizeof(STARTUPINFOW);
|
|
siStartInfo.hStdError = g_hChildStd_ERR_Wr;
|
|
siStartInfo.hStdOutput = g_hChildStd_OUT_Wr;
|
|
siStartInfo.hStdInput = g_hChildStd_IN_Rd;
|
|
siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
|
|
|
|
std::string cmdline = FileUtils::combine_cmd_line(_fzncmd);
|
|
wchar_t* cmdstr = _wcsdup(FileUtils::utf8_to_wide(cmdline).c_str());
|
|
|
|
HANDLE hJobObject = CreateJobObject(NULL, NULL);
|
|
|
|
BOOL processStarted = CreateProcessW(NULL,
|
|
cmdstr, // command line
|
|
NULL, // process security attributes
|
|
NULL, // primary thread security attributes
|
|
TRUE, // handles are inherited
|
|
0, // creation flags
|
|
NULL, // use parent's environment
|
|
NULL, // use parent's current directory
|
|
&siStartInfo, // STARTUPINFO pointer
|
|
&piProcInfo); // receives PROCESS_INFORMATION
|
|
|
|
if (!processStarted) {
|
|
std::stringstream ssm;
|
|
ssm << "Error occurred when executing FZN solver with command \""
|
|
<< FileUtils::wide_to_utf8(cmdstr) << "\".";
|
|
throw InternalError(ssm.str());
|
|
}
|
|
|
|
BOOL assignedToJob = AssignProcessToJobObject(hJobObject, piProcInfo.hProcess);
|
|
if (!assignedToJob) {
|
|
throw InternalError("Failed to assign process to job.");
|
|
}
|
|
|
|
CloseHandle(piProcInfo.hThread);
|
|
delete cmdstr;
|
|
|
|
// Stop ReadFile from blocking
|
|
CloseHandle(g_hChildStd_OUT_Wr);
|
|
CloseHandle(g_hChildStd_ERR_Wr);
|
|
// Just close the child's in pipe here
|
|
CloseHandle(g_hChildStd_IN_Rd);
|
|
bool doneStdout = false;
|
|
bool doneStderr = false;
|
|
|
|
// Threaded solution seems simpler than asyncronous pipe reading
|
|
std::mutex pipeMutex;
|
|
|
|
std::mutex cv_mutex;
|
|
std::condition_variable cv;
|
|
|
|
std::deque<std::string> outputQueue;
|
|
thread thrStdout(&ReadPipePrint<S2O>, g_hChildStd_OUT_Rd, &doneStdout, nullptr, &outputQueue,
|
|
&pipeMutex, &cv_mutex, &cv);
|
|
thread thrStderr(&ReadPipePrint<S2O>, g_hChildStd_ERR_Rd, &doneStderr, &_pS2Out->getLog(),
|
|
nullptr, &pipeMutex, nullptr, nullptr);
|
|
thread thrTimeout([&] {
|
|
auto shouldStop = [&] { return hadInterrupt || (doneStderr && doneStdout); };
|
|
std::unique_lock<std::mutex> lck(_interruptMutex);
|
|
if (_timelimit != 0) {
|
|
if (!_interruptCondition.wait_for(lck, std::chrono::milliseconds(_timelimit), shouldStop)) {
|
|
// If we timed out, generate an interrupt but ignore it ourselves
|
|
bool oldHadInterrupt = hadInterrupt;
|
|
GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0);
|
|
_interruptCondition.wait(lck, [&] { return hadInterrupt; });
|
|
hadInterrupt = oldHadInterrupt;
|
|
}
|
|
} else {
|
|
_interruptCondition.wait(lck, shouldStop);
|
|
}
|
|
// At this point the child should be stopped/stopping
|
|
if (!doneStderr || !doneStdout) {
|
|
if (!_interruptCondition.wait_for(lck, std::chrono::milliseconds(200),
|
|
[&] { return doneStderr && doneStdout; })) {
|
|
// Force terminate the child after 200ms
|
|
TerminateJobObject(hJobObject, 0);
|
|
};
|
|
}
|
|
});
|
|
|
|
while (true) {
|
|
std::unique_lock<std::mutex> lk(cv_mutex);
|
|
cv.wait(lk, [&] { return !outputQueue.empty(); });
|
|
while (!outputQueue.empty()) {
|
|
try {
|
|
_pS2Out->feedRawDataChunk(outputQueue.front().c_str());
|
|
outputQueue.pop_front();
|
|
} catch (...) {
|
|
TerminateJobObject(hJobObject, 0);
|
|
doneStdout = true;
|
|
doneStderr = true;
|
|
lk.unlock();
|
|
thrStdout.join();
|
|
thrStderr.join();
|
|
{
|
|
// Make sure thrTimeout terminates
|
|
std::unique_lock<std::mutex> lck(_interruptMutex);
|
|
_interruptCondition.notify_all();
|
|
}
|
|
thrTimeout.join();
|
|
SetConsoleCtrlHandler(handleInterrupt, FALSE);
|
|
std::rethrow_exception(std::current_exception());
|
|
}
|
|
}
|
|
if (doneStdout) break;
|
|
}
|
|
|
|
thrStdout.join();
|
|
thrStderr.join();
|
|
{
|
|
// Make sure thrTimeout terminates
|
|
std::unique_lock<std::mutex> lck(_interruptMutex);
|
|
_interruptCondition.notify_all();
|
|
}
|
|
thrTimeout.join();
|
|
DWORD exitCode = 0;
|
|
if (GetExitCodeProcess(piProcInfo.hProcess, &exitCode) == FALSE) {
|
|
exitCode = 1;
|
|
}
|
|
CloseHandle(piProcInfo.hProcess);
|
|
|
|
SetConsoleCtrlHandler(handleInterrupt, FALSE);
|
|
if (hadInterrupt) {
|
|
// Re-trigger signal if it was not caused by our own timeout
|
|
GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0);
|
|
}
|
|
return exitCode;
|
|
}
|
|
#else
|
|
int pipes[3][2];
|
|
pipe(pipes[0]);
|
|
pipe(pipes[1]);
|
|
pipe(pipes[2]);
|
|
|
|
if (int childPID = fork()) {
|
|
close(pipes[0][0]);
|
|
close(pipes[1][1]);
|
|
close(pipes[2][1]);
|
|
close(pipes[0][1]);
|
|
|
|
fd_set fdset;
|
|
FD_ZERO(&fdset); // NOLINT(readability-isolate-declaration)
|
|
|
|
struct timeval starttime;
|
|
gettimeofday(&starttime, nullptr);
|
|
|
|
struct timeval timeout_orig;
|
|
timeout_orig.tv_sec = _timelimit / 1000;
|
|
timeout_orig.tv_usec = (_timelimit % 1000) * 1000;
|
|
struct timeval timeout = timeout_orig;
|
|
|
|
hadInterrupt = false;
|
|
hadTerm = false;
|
|
struct sigaction sa;
|
|
struct sigaction old_sa_int;
|
|
struct sigaction old_sa_term;
|
|
sa.sa_handler = &handleInterrupt;
|
|
sa.sa_flags = 0;
|
|
sigfillset(&sa.sa_mask);
|
|
sigaction(SIGINT, &sa, &old_sa_int);
|
|
sigaction(SIGTERM, &sa, &old_sa_term);
|
|
int signal = _sigint ? SIGINT : SIGTERM;
|
|
bool handledInterrupt = false;
|
|
bool handledTerm = false;
|
|
|
|
bool done = hadTerm || hadInterrupt;
|
|
bool timed_out = false;
|
|
while (!done) {
|
|
FD_SET(pipes[1][0], &fdset);
|
|
FD_SET(pipes[2][0], &fdset);
|
|
int sel =
|
|
select(FD_SETSIZE, &fdset, nullptr, nullptr, _timelimit == 0 ? nullptr : &timeout);
|
|
if (sel == -1) {
|
|
if (errno != EINTR) {
|
|
// some error has happened
|
|
throw InternalError(std::string("Error in communication with solver: ") +
|
|
strerror(errno));
|
|
}
|
|
}
|
|
bool timeoutImmediately = false;
|
|
if (hadInterrupt && !handledInterrupt) {
|
|
signal = SIGINT;
|
|
handledInterrupt = true;
|
|
timeoutImmediately = true;
|
|
}
|
|
if (hadTerm && !handledTerm) {
|
|
signal = SIGTERM;
|
|
handledTerm = true;
|
|
timeoutImmediately = true;
|
|
}
|
|
if (timeoutImmediately) {
|
|
// Set timeout to immediately expire
|
|
_timelimit = -1;
|
|
timeout.tv_sec = 0;
|
|
timeout.tv_usec = 0;
|
|
timeout_orig = timeout;
|
|
timeval currentTime;
|
|
gettimeofday(¤tTime, nullptr);
|
|
starttime = currentTime;
|
|
}
|
|
|
|
bool killed = false;
|
|
if (_timelimit != 0) {
|
|
timeval currentTime;
|
|
gettimeofday(¤tTime, nullptr);
|
|
if (sel != 0) {
|
|
timeval elapsed;
|
|
elapsed.tv_sec = currentTime.tv_sec - starttime.tv_sec;
|
|
elapsed.tv_usec = currentTime.tv_usec - starttime.tv_usec;
|
|
if (elapsed.tv_usec < 0) {
|
|
elapsed.tv_sec--;
|
|
elapsed.tv_usec += 1000000;
|
|
}
|
|
// Reset timeout to original limit
|
|
timeout = timeout_orig;
|
|
// Subtract elapsed time
|
|
timeout.tv_usec = timeout.tv_usec - elapsed.tv_usec;
|
|
if (timeout.tv_usec < 0) {
|
|
timeout.tv_sec--;
|
|
timeout.tv_usec += 1000000;
|
|
}
|
|
timeout.tv_sec = timeout.tv_sec - elapsed.tv_sec;
|
|
} else {
|
|
timeout.tv_usec = 0;
|
|
timeout.tv_sec = 0;
|
|
}
|
|
if (timeout.tv_sec < 0 || (timeout.tv_sec == 0 && timeout.tv_usec == 0)) {
|
|
timed_out = true;
|
|
if (signal == SIGKILL) {
|
|
killed = true;
|
|
done = true;
|
|
}
|
|
if (killpg(childPID, signal) == -1) {
|
|
// Fallback to killing the child if killing the process group fails
|
|
kill(childPID, signal);
|
|
}
|
|
timeout.tv_sec = 0;
|
|
timeout.tv_usec = 200000;
|
|
timeout_orig = timeout;
|
|
starttime = currentTime;
|
|
// Upgrade signal for next attempt
|
|
signal = signal == SIGINT ? SIGTERM : SIGKILL;
|
|
}
|
|
}
|
|
|
|
bool addedNl = false;
|
|
for (int i = 1; i <= 2; ++i) {
|
|
if (FD_ISSET(pipes[i][0], &fdset)) {
|
|
char buffer[1000];
|
|
int count = read(pipes[i][0], buffer, sizeof(buffer) - 1);
|
|
if (count > 0) {
|
|
buffer[count] = 0;
|
|
if (1 == i) {
|
|
// cerr << "mzn-fzn: raw chunk stdout::: " << flush;
|
|
// cerr << buffer << flush;
|
|
try {
|
|
_pS2Out->feedRawDataChunk(buffer);
|
|
} catch (...) {
|
|
// Exception during solns2out, kill process and re-throw
|
|
if (killpg(childPID, SIGKILL) == -1) {
|
|
// Fallback to killing the child if killing the process group fails
|
|
kill(childPID, SIGKILL);
|
|
}
|
|
throw;
|
|
}
|
|
} else {
|
|
_pS2Out->getLog() << buffer << std::flush;
|
|
}
|
|
} else if (1 == i) {
|
|
_pS2Out->feedRawDataChunk("\n"); // in case last chunk did not end with \n
|
|
addedNl = true;
|
|
done = true;
|
|
}
|
|
}
|
|
}
|
|
if (killed && !addedNl) {
|
|
_pS2Out->feedRawDataChunk("\n"); // in case last chunk did not end with \n
|
|
}
|
|
}
|
|
|
|
close(pipes[1][0]);
|
|
close(pipes[2][0]);
|
|
int exitStatus = timed_out ? 0 : 1;
|
|
int childStatus;
|
|
int pidStatus = waitpid(childPID, &childStatus, 0);
|
|
if (!timed_out && pidStatus > 0) {
|
|
if (WIFEXITED(childStatus)) {
|
|
exitStatus = WEXITSTATUS(childStatus);
|
|
}
|
|
}
|
|
sigaction(SIGINT, &old_sa_int, nullptr);
|
|
sigaction(SIGTERM, &old_sa_term, nullptr);
|
|
if (hadInterrupt) {
|
|
kill(getpid(), SIGINT);
|
|
}
|
|
if (hadTerm) {
|
|
kill(getpid(), SIGTERM);
|
|
}
|
|
return exitStatus;
|
|
}
|
|
if (setpgid(0, 0) == -1) {
|
|
throw InternalError("Failed to set pgid of subprocess");
|
|
}
|
|
close(STDOUT_FILENO);
|
|
close(STDERR_FILENO);
|
|
close(STDIN_FILENO);
|
|
dup2(pipes[0][0], STDIN_FILENO);
|
|
dup2(pipes[1][1], STDOUT_FILENO);
|
|
dup2(pipes[2][1], STDERR_FILENO);
|
|
close(pipes[0][0]);
|
|
close(pipes[0][1]);
|
|
close(pipes[1][1]);
|
|
close(pipes[1][0]);
|
|
close(pipes[2][1]);
|
|
close(pipes[2][0]);
|
|
|
|
std::vector<char*> cmd_line;
|
|
for (auto& iCmdl : _fzncmd) {
|
|
cmd_line.push_back(strdup(iCmdl.c_str()));
|
|
}
|
|
|
|
char** argv = new char*[cmd_line.size() + 1];
|
|
for (unsigned int i = 0; i < cmd_line.size(); i++) {
|
|
argv[i] = cmd_line[i];
|
|
}
|
|
argv[cmd_line.size()] = nullptr;
|
|
|
|
int status = execvp(argv[0], argv); // execvp only returns if an error occurs.
|
|
assert(status == -1); // the returned value will always be -1
|
|
std::stringstream ssm;
|
|
ssm << "Error occurred when executing FZN solver with command \"";
|
|
for (auto& s : cmd_line) {
|
|
ssm << s << ' ';
|
|
}
|
|
ssm << "\".";
|
|
throw InternalError(ssm.str());
|
|
}
|
|
#endif
|
|
};
|
|
|
|
template <class S2O>
|
|
bool Process<S2O>::hadInterrupt;
|
|
#ifdef _WIN32
|
|
template <class S2O>
|
|
std::mutex Process<S2O>::_interruptMutex;
|
|
template <class S2O>
|
|
std::condition_variable Process<S2O>::_interruptCondition;
|
|
#else
|
|
template <class S2O>
|
|
bool Process<S2O>::hadTerm;
|
|
#endif
|
|
|
|
} // namespace MiniZinc
|