Generated on for Gecode by doxygen 1.15.0
blackbox-process-windows.cpp
Go to the documentation of this file.
1/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */
2/*
3 * Main authors:
4 * Jip J. Dekker <jip.dekker@monash.edu>
5 *
6 * Contributing authors:
7 * Mikael Zayenz Lagerkvist <lagerkvist@gecode.dev>
8 *
9 * Copyright:
10 * Jip J. Dekker, 2026
11 */
12#if defined(_WIN32)
13#if !defined(_WIN32_WINNT) || (_WIN32_WINNT < 0x0600)
14#undef _WIN32_WINNT
15#define _WIN32_WINNT 0x0600
16#endif
17#if !defined(WINVER) || (WINVER < 0x0600)
18#undef WINVER
19#define WINVER 0x0600
20#endif
21
23#include <gecode/flatzinc.hh>
24#include <cassert>
25#include <sstream>
26#include <windows.h>
27
28namespace Gecode { namespace FlatZinc {
29namespace {
30
31const size_t max_exec_response_size = 1024 * 1024;
32
33std::wstring
34utf8_to_wide(const std::string &s) {
35 if (s.empty()) {
36 return std::wstring();
37 }
38 int n = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, s.c_str(),
39 static_cast<int>(s.size()), NULL, 0);
40 if (n == 0) {
41 throw Error("Blackbox", "Invalid UTF-8 string in blackbox path or argument");
42 }
43 std::wstring w(static_cast<size_t>(n), L'\0');
44 if (MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, s.c_str(),
45 static_cast<int>(s.size()), &w[0], n) == 0) {
46 throw Error("Blackbox", "Invalid UTF-8 string in blackbox path or argument");
47 }
48 return w;
49}
50
51std::string
52windows_error(const std::string &prefix, DWORD err) {
53 return prefix + " (Windows error " + std::to_string(err) + ")";
54}
55
56class WindowsHandle {
57private:
58 HANDLE handle;
59public:
60 explicit WindowsHandle(HANDLE handle0=NULL) : handle(handle0) {}
61 ~WindowsHandle(void) { reset(); }
62
63 WindowsHandle(const WindowsHandle &) = delete;
64 WindowsHandle &operator=(const WindowsHandle &) = delete;
65
66 HANDLE get(void) const { return handle; }
67 HANDLE *put(void) {
68 reset();
69 return &handle;
70 }
71 HANDLE release(void) {
72 HANDLE handle0 = handle;
73 handle = NULL;
74 return handle0;
75 }
76 bool valid(void) const {
77 return (handle != NULL) && (handle != INVALID_HANDLE_VALUE);
78 }
79 void reset(HANDLE handle0=NULL) {
80 if (valid()) {
81 CloseHandle(handle);
82 }
83 handle = handle0;
84 }
85};
86
87class WindowsAttributeList {
88private:
89 std::vector<char> buffer;
90 LPPROC_THREAD_ATTRIBUTE_LIST list;
91 bool initialized;
92public:
93 WindowsAttributeList(void) : list(NULL), initialized(false) {}
94 ~WindowsAttributeList(void) {
95 if (initialized) {
96 DeleteProcThreadAttributeList(list);
97 }
98 }
99
100 void init(void) {
101 SIZE_T size = 0;
102 InitializeProcThreadAttributeList(NULL, 1, 0, &size);
103 if (size == 0) {
104 throw Error("BlackBoxExec",
105 windows_error("ProcThreadAttributeList size query failed",
106 GetLastError()));
107 }
108 buffer.resize(size);
109 list = reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(buffer.data());
110 if (!InitializeProcThreadAttributeList(list, 1, 0, &size)) {
111 throw Error("BlackBoxExec",
112 windows_error("InitializeProcThreadAttributeList failed",
113 GetLastError()));
114 }
115 initialized = true;
116 }
117
118 void set_inherited_handles(HANDLE *handles, DWORD count) {
119 if (!UpdateProcThreadAttribute(list, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
120 handles, sizeof(HANDLE) * count, NULL,
121 NULL)) {
122 throw Error("BlackBoxExec",
123 windows_error("PROC_THREAD_ATTRIBUTE_HANDLE_LIST failed",
124 GetLastError()));
125 }
126 }
127
128 LPPROC_THREAD_ATTRIBUTE_LIST get(void) const { return list; }
129};
130bool
131qualified_path(const std::wstring &program) {
132 return (program.find_first_of(L"\\/") != std::wstring::npos) ||
133 ((program.size() > 1) && (program[1] == L':'));
134}
135
136class WindowsProcessSession : public BlackBoxProcessSession {
137protected:
138 HANDLE job;
139 HANDLE process;
140 HANDLE pipe_send;
141 HANDLE pipe_receive;
142
143 static std::string last_error(const std::string &prefix) {
144 return prefix + " (Windows error " + std::to_string(GetLastError()) + ")";
145 }
146
147 static void close_handle(HANDLE &h) {
148 if (h != NULL) {
149 CloseHandle(h);
150 h = NULL;
151 }
152 }
153
154 static std::wstring quote_argument(const std::wstring &arg) {
155 std::wstring q(L"\"");
156 unsigned int backslashes = 0;
157 for (wchar_t ch : arg) {
158 if (ch == L'\\') {
159 backslashes++;
160 } else if (ch == L'"') {
161 q.append(backslashes * 2 + 1, L'\\');
162 q += ch;
163 backslashes = 0;
164 } else {
165 q.append(backslashes, L'\\');
166 q += ch;
167 backslashes = 0;
168 }
169 }
170 q.append(backslashes * 2, L'\\');
171 q += L'"';
172 return q;
173 }
174
175 void open_windows(const std::string &program,
176 const std::vector<std::string> &args);
177 void close_windows(void);
178
179public:
180 WindowsProcessSession(const std::string &program, const std::vector<std::string> &args)
181 : job(NULL), process(NULL), pipe_send(NULL), pipe_receive(NULL)
182 {
183 open_windows(program, args);
184 }
185
186 ~WindowsProcessSession(void) { close(); }
187
188 std::string exchange(const std::string &out_buf) {
189 size_t written = 0;
190 while (written < out_buf.size()) {
191 DWORD count = 0;
192 DWORD remaining =
193 static_cast<DWORD>(out_buf.size() - written);
194 BOOL success =
195 WriteFile(pipe_send, out_buf.data() + written, remaining, &count,
196 nullptr);
197 if (!success || count == 0) {
198 throw Error("BlackBoxExec",
199 last_error("Writing blackbox process input failed"));
200 }
201 written += count;
202 }
203
204 char c[2] = {0, 0};
205 std::ostringstream oss;
206 size_t response_size = 0;
207 while (c[0] != '\n') {
208 DWORD count = 0;
209 BOOL success = ReadFile(pipe_receive, c, sizeof(c) - 1, &count, NULL);
210 if (!success) {
211 if (GetLastError() == ERROR_BROKEN_PIPE) {
212 throw Error("BlackBoxExec",
213 "Blackbox process provided an incomplete response");
214 }
215 throw Error(
216 "BlackBoxExec",
217 "Failed to read blackbox process output from pipe");
218 } else if (count == 0) {
219 throw Error("BlackBoxExec",
220 "Blackbox process provided an incomplete response");
221 }
222 assert(count == 1);
223 if (++response_size > max_exec_response_size) {
224 throw Error("BlackBoxExec",
225 "Blackbox process response exceeds the size limit");
226 }
227 oss << c[0];
228 }
229 return oss.str();
230 }
231
232 void close(void) {
233 close_windows();
234 }
235};
236
237void
238WindowsProcessSession::open_windows(const std::string &program,
239 const std::vector<std::string> &args) {
240 // Build the command line before opening OS handles so allocation/conversion
241 // failures cannot leak partially constructed process state.
242 std::wstring program_w = utf8_to_wide(program);
243 std::wstring prog = quote_argument(program_w);
244 for (const std::string &a : args) {
245 prog += L" ";
246 prog += quote_argument(utf8_to_wide(a));
247 }
248 std::vector<wchar_t> cmdline(prog.begin(), prog.end());
249 cmdline.push_back(L'\0');
250
251 SECURITY_ATTRIBUTES saAttr;
252 saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
253 saAttr.bInheritHandle = TRUE;
254 saAttr.lpSecurityDescriptor = NULL;
255
256 WindowsHandle child_stdin_read;
257 WindowsHandle child_stdin_write;
258 WindowsHandle child_stdout_read;
259 WindowsHandle child_stdout_write;
260 WindowsHandle child_stderr_write;
261 if (!CreatePipe(child_stdout_read.put(), child_stdout_write.put(), &saAttr,
262 0)) {
263 throw Error("BlackBoxExec", last_error("Stdout CreatePipe failed"));
264 }
265 if (!SetHandleInformation(child_stdout_read.get(), HANDLE_FLAG_INHERIT, 0)) {
266 throw Error("BlackBoxExec",
267 last_error("Stdout SetHandleInformation failed"));
268 }
269 if (!CreatePipe(child_stdin_read.put(), child_stdin_write.put(), &saAttr,
270 0)) {
271 throw Error("BlackBoxExec", last_error("Stdin CreatePipe failed"));
272 }
273 if (!SetHandleInformation(child_stdin_write.get(), HANDLE_FLAG_INHERIT, 0)) {
274 throw Error("BlackBoxExec",
275 last_error("Stdin SetHandleInformation failed"));
276 }
277
278 HANDLE parent_stderr = GetStdHandle(STD_ERROR_HANDLE);
279 if ((parent_stderr != NULL) && (parent_stderr != INVALID_HANDLE_VALUE)) {
280 if (!DuplicateHandle(GetCurrentProcess(), parent_stderr,
281 GetCurrentProcess(), child_stderr_write.put(), 0, TRUE,
282 DUPLICATE_SAME_ACCESS)) {
283 throw Error("BlackBoxExec",
284 last_error("stderr DuplicateHandle failed"));
285 }
286 } else {
287 HANDLE nul = CreateFileW(L"NUL", GENERIC_WRITE,
288 FILE_SHARE_READ | FILE_SHARE_WRITE, &saAttr,
289 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
290 if (nul == INVALID_HANDLE_VALUE) {
291 throw Error("BlackBoxExec", last_error("stderr NUL CreateFile failed"));
292 }
293 child_stderr_write.reset(nul);
294 }
295
296 WindowsAttributeList attr_list;
297 attr_list.init();
298 PROCESS_INFORMATION piProcInfo;
299 STARTUPINFOEXW siStartInfo;
300 ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));
301 ZeroMemory(&siStartInfo, sizeof(STARTUPINFOEXW));
302 siStartInfo.StartupInfo.cb = sizeof(STARTUPINFOEXW);
303 siStartInfo.StartupInfo.hStdOutput = child_stdout_write.get();
304 siStartInfo.StartupInfo.hStdInput = child_stdin_read.get();
305 siStartInfo.StartupInfo.hStdError = child_stderr_write.get();
306 siStartInfo.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
307
308 HANDLE inherit_handles[3] = {child_stdin_read.get(), child_stdout_write.get(),
309 child_stderr_write.get()};
310 attr_list.set_inherited_handles(inherit_handles, 3);
311 siStartInfo.lpAttributeList = attr_list.get();
312
313 WindowsHandle process_job(CreateJobObjectW(NULL, NULL));
314 if (!process_job.valid()) {
315 throw Error("BlackBoxExec", last_error("CreateJobObject failed"));
316 }
317 JOBOBJECT_EXTENDED_LIMIT_INFORMATION job_info;
318 ZeroMemory(&job_info, sizeof(job_info));
319 job_info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
320 if (!SetInformationJobObject(process_job.get(),
321 JobObjectExtendedLimitInformation, &job_info,
322 sizeof(job_info))) {
323 throw Error("BlackBoxExec", last_error("SetInformationJobObject failed"));
324 }
325
326 BOOL processStarted =
327 CreateProcessW(qualified_path(program_w) ? program_w.c_str() : NULL,
328 cmdline.data(), // command line
329 nullptr, // process security attributes
330 nullptr, // primary thread security attributes
331 TRUE, // handles from attribute list
332 EXTENDED_STARTUPINFO_PRESENT | CREATE_SUSPENDED,
333 nullptr, // use parent's environment
334 nullptr, // use parent's current directory
335 &siStartInfo.StartupInfo,
336 &piProcInfo); // receives PROCESS_INFORMATION
337
338 if (!processStarted) {
339 throw Error("BlackBoxExec",
340 windows_error("starting blackbox process failed for program `" +
341 program + "'", GetLastError()));
342 }
343 WindowsHandle process_handle(piProcInfo.hProcess);
344 WindowsHandle thread_handle(piProcInfo.hThread);
345 if (!AssignProcessToJobObject(process_job.get(), process_handle.get())) {
346 DWORD err = GetLastError();
347 DWORD terminate_err = ERROR_SUCCESS;
348 if (!TerminateProcess(process_handle.get(), 1)) {
349 terminate_err = GetLastError();
350 }
351 DWORD wait = WaitForSingleObject(process_handle.get(), 5000);
352 std::string message = windows_error(
353 "Unable to assign blackbox process to required job", err);
354 if (terminate_err != ERROR_SUCCESS) {
355 message += "; " + windows_error("TerminateProcess cleanup failed",
356 terminate_err);
357 }
358 if (wait == WAIT_FAILED) {
359 message += "; " + last_error("process cleanup wait failed");
360 } else if (wait == WAIT_TIMEOUT) {
361 message += "; process cleanup timed out";
362 }
363 throw Error("BlackBoxExec", message);
364 }
365
366 if (ResumeThread(thread_handle.get()) == static_cast<DWORD>(-1)) {
367 DWORD err = GetLastError();
368 DWORD terminate_err = ERROR_SUCCESS;
369 if (!TerminateJobObject(process_job.get(), 1)) {
370 terminate_err = GetLastError();
371 }
372 HANDLE assigned_job = process_job.release();
373 DWORD close_err = ERROR_SUCCESS;
374 if (!CloseHandle(assigned_job)) {
375 close_err = GetLastError();
376 }
377 DWORD wait = WaitForSingleObject(process_handle.get(), 5000);
378 std::string message = windows_error(
379 "ResumeThread failed for blackbox process", err);
380 if (terminate_err != ERROR_SUCCESS) {
381 message += "; " + windows_error("TerminateJobObject cleanup failed",
382 terminate_err);
383 }
384 if (close_err != ERROR_SUCCESS) {
385 message += "; " + windows_error("job cleanup close failed", close_err);
386 }
387 if (wait == WAIT_FAILED) {
388 message += "; " + last_error("process cleanup wait failed");
389 } else if (wait == WAIT_TIMEOUT) {
390 message += "; process cleanup timed out";
391 }
392 throw Error("BlackBoxExec", message);
393 }
394
395 pipe_send = child_stdin_write.release();
396 pipe_receive = child_stdout_read.release();
397 process = process_handle.release();
398 job = process_job.release();
399}
400
401void
402WindowsProcessSession::close_windows(void) {
403 close_handle(pipe_send);
404 close_handle(pipe_receive);
405 if (process != NULL) {
406 DWORD wait = WaitForSingleObject(process, 1000);
407 if (wait == WAIT_TIMEOUT) {
408 if (job != NULL) {
409 TerminateJobObject(job, 1);
410 } else {
411 TerminateProcess(process, 1);
412 }
413 WaitForSingleObject(process, 5000);
414 }
415 close_handle(process);
416 }
417 close_handle(job);
418}
419} // namespace
420
422create_blackbox_process(const std::string& program,
423 const std::vector<std::string>& args) {
424 return new WindowsProcessSession(program, args);
425}
426
427}}
428#endif
429
430// STATISTICS: flatzinc-other
Platform process session used by the executable blackbox backend.
Exception class for FlatZinc errors
Definition flatzinc.hh:727
Interpreter for the FlatZinc language.
BlackBoxProcessSession * create_blackbox_process(const std::string &, const std::vector< std::string > &)
Create the process implementation selected for the target platform.
bool valid(const FloatVal &n)
Return whether float n is a valid number.
Definition limits.hpp:39
unsigned int size(I &i)
Size of all ranges of range iterator i.
void reset(void)
Reset all failpoint state.
void exchange(Type &a, Type &b, Less &less)
Exchange elements according to order.
Definition sort.hpp:42
Gecode toplevel namespace
void count(Home home, const IntVarArgs &x, int n, IntRelType irt, int m, IntPropLevel ipl=IPL_DEF)
Post propagator for .
Definition count.cpp:40
void wait(Home home, FloatVar x, std::function< void(Space &home)> c)
Execute c when x becomes assigned.
Definition exec.cpp:39