LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Toggle main menu visibility
Loading...
Searching...
No Matches
subprocess.h
Go to the documentation of this file.
1
/*
2
* Copyright (c) 2012-2026, QORE Lab, Imperial College London
3
* All rights reserved.
4
*/
5
#ifndef LINE_UTIL_SUBPROCESS_H
6
#define LINE_UTIL_SUBPROCESS_H
7
8
/**
9
* @file
10
* @ingroup line_util
11
* Running an external command and capturing its output, with a deadline.
12
*
13
* The port needs this for the Docker orchestration behind the symbolic backend,
14
* where the JAR uses ProcessBuilder. popen() is not enough: it offers no timeout
15
* and runs the command through a shell, so an image tag would be word-split and
16
* glob-expanded. This forks and execs an argv vector directly, so no shell ever
17
* sees the arguments, and enforces the deadline by polling the pipe and killing
18
* the child when it expires.
19
*
20
* A TIMED-OUT CHILD IS KILLED AND REAPED, never merely abandoned: an orphaned
21
* `docker run` would keep a container alive that nobody holds the name of.
22
*/
23
24
#include <cerrno>
25
#include <csignal>
26
#include <cstddef>
27
#include <cstring>
28
#include <ctime>
29
#include <string>
30
#include <vector>
31
32
#include <fcntl.h>
33
#include <poll.h>
34
#include <sys/types.h>
35
#include <sys/wait.h>
36
#include <unistd.h>
37
38
namespace
line
{
39
namespace
util
{
40
41
/** Outcome of a captured command. */
42
struct
ProcResult
{
43
int
exitCode
= -1;
///< Exit status, or -1 when the command could not run
44
std::string
out
;
///< Everything the command wrote to stdout
45
bool
timedOut
=
false
;
///< True when the deadline expired and the child was killed
46
};
47
48
namespace
detail {
49
50
/** Milliseconds on the monotonic clock, for deadlines that survive a clock step. */
51
inline
long
monotonic_millis() {
52
struct
timespec ts;
53
clock_gettime(CLOCK_MONOTONIC, &ts);
54
return
static_cast<
long
>
(ts.tv_sec) * 1000L + ts.tv_nsec / 1000000L;
55
}
56
57
inline
void
exec_never_returns(
const
std::vector<std::string>& argv) {
58
std::vector<char*> raw;
59
raw.reserve(argv.size() + 1);
60
for
(std::size_t i = 0; i < argv.size(); ++i)
61
raw.push_back(
const_cast<
char
*
>
(argv[i].c_str()));
62
raw.push_back(
nullptr
);
63
::execvp(raw[0], &raw[0]);
64
::_exit(127);
65
}
66
67
}
// namespace detail
68
69
/**
70
* Runs a command, capturing stdout and discarding stderr.
71
*
72
* `mergeStderr` sends the child's stderr down the SAME pipe instead, which is
73
* what MATLAB's `[status, cmdout] = system(cmd)` does and what a wrapper needs
74
* on the failure path: an external engine reports why it refused on stderr, and
75
* dropping it leaves the wrapper reporting an exit code and nothing else.
76
*
77
* @param argv the command and its arguments, argv[0] resolved on PATH
78
* @param timeoutSeconds deadline; not positive waits indefinitely
79
* @param mergeStderr capture stderr too, interleaved with stdout
80
* @return the exit code, the captured output and whether the deadline expired
81
*/
82
inline
ProcResult
capture
(
const
std::vector<std::string>& argv,
int
timeoutSeconds,
83
bool
mergeStderr =
false
) {
84
ProcResult
r;
85
if
(argv.empty())
return
r;
86
87
int
pipefd[2];
88
if
(::pipe(pipefd) != 0)
return
r;
89
90
const
pid_t pid = ::fork();
91
if
(pid < 0) {
92
::close(pipefd[0]);
93
::close(pipefd[1]);
94
return
r;
95
}
96
if
(pid == 0) {
97
::close(pipefd[0]);
98
::dup2(pipefd[1], STDOUT_FILENO);
99
if
(mergeStderr) {
100
::dup2(pipefd[1], STDERR_FILENO);
101
}
else
{
102
const
int
devnull = ::open(
"/dev/null"
, O_WRONLY);
103
if
(devnull >= 0) {
104
::dup2(devnull, STDERR_FILENO);
105
::close(devnull);
106
}
107
}
108
::close(pipefd[1]);
109
detail::exec_never_returns(argv);
110
}
111
112
::close(pipefd[1]);
113
const
long
deadline =
114
timeoutSeconds > 0 ? detail::monotonic_millis() + timeoutSeconds * 1000L : -1;
115
char
buf[4096];
116
while
(
true
) {
117
int
wait = -1;
118
if
(deadline >= 0) {
119
const
long
left = deadline - detail::monotonic_millis();
120
if
(left <= 0) {
121
r.
timedOut
=
true
;
122
break
;
123
}
124
wait =
static_cast<
int
>
(left);
125
}
126
struct
pollfd pfd;
127
pfd.fd = pipefd[0];
128
pfd.events = POLLIN;
129
pfd.revents = 0;
130
const
int
pr = ::poll(&pfd, 1, wait);
131
if
(pr == 0) {
132
r.
timedOut
=
true
;
133
break
;
134
}
135
if
(pr < 0) {
136
if
(errno == EINTR)
continue
;
137
break
;
138
}
139
const
ssize_t k = ::read(pipefd[0], buf,
sizeof
(buf));
140
if
(k > 0) {
141
r.
out
.append(buf,
static_cast<
std::size_t
>
(k));
142
continue
;
143
}
144
if
(k < 0 && errno == EINTR)
continue
;
145
break
;
// end of stream
146
}
147
::close(pipefd[0]);
148
149
if
(r.
timedOut
) {
150
::kill(pid, SIGKILL);
151
int
status = 0;
152
::waitpid(pid, &status, 0);
153
r.
exitCode
= -1;
154
return
r;
155
}
156
int
status = 0;
157
if
(::waitpid(pid, &status, 0) == pid && WIFEXITED(status))
158
r.
exitCode
= WEXITSTATUS(status);
159
return
r;
160
}
161
162
/**
163
* Runs a command with the parent's stdout and stderr, e.g. so a `docker pull`
164
* streams its progress. There is no deadline: a pull is long by nature and the
165
* user can see it working.
166
*
167
* @param argv the command and its arguments
168
* @return the exit code, or -1 if the command could not run
169
*/
170
inline
int
run_inherit
(
const
std::vector<std::string>& argv) {
171
if
(argv.empty())
return
-1;
172
const
pid_t pid = ::fork();
173
if
(pid < 0)
return
-1;
174
if
(pid == 0) detail::exec_never_returns(argv);
175
int
status = 0;
176
if
(::waitpid(pid, &status, 0) == pid && WIFEXITED(status))
return
WEXITSTATUS(status);
177
return
-1;
178
}
179
180
/** Trims ASCII whitespace from both ends, as Java's String.trim() does. */
181
inline
std::string
trim
(
const
std::string& s) {
182
const
std::size_t b = s.find_first_not_of(
" \t\r\n"
);
183
if
(b == std::string::npos)
return
std::string();
184
const
std::size_t e = s.find_last_not_of(
" \t\r\n"
);
185
return
s.substr(b, e - b + 1);
186
}
187
188
}
// namespace util
189
}
// namespace line
190
191
#endif
// LINE_UTIL_SUBPROCESS_H
line::util
Definition
line_console.h:45
line::util::run_inherit
int run_inherit(const std::vector< std::string > &argv)
Runs a command with the parent's stdout and stderr, e.g.
Definition
subprocess.h:170
line::util::capture
ProcResult capture(const std::vector< std::string > &argv, int timeoutSeconds, bool mergeStderr=false)
Runs a command, capturing stdout and discarding stderr.
Definition
subprocess.h:82
line::util::trim
std::string trim(const std::string &s)
Trims ASCII whitespace from both ends, as Java's String.trim() does.
Definition
subprocess.h:181
line
Definition
aoi_dist2ph.h:52
line::util::ProcResult
Outcome of a captured command.
Definition
subprocess.h:42
line::util::ProcResult::exitCode
int exitCode
Exit status, or -1 when the command could not run.
Definition
subprocess.h:43
line::util::ProcResult::timedOut
bool timedOut
True when the deadline expired and the child was killed.
Definition
subprocess.h:45
line::util::ProcResult::out
std::string out
Everything the command wrote to stdout.
Definition
subprocess.h:44
include
line
util
subprocess.h
Generated by
1.18.0