LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
cli_run.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_CLI_CLI_RUN_H
6#define LINE_CLI_CLI_RUN_H
7
8/**
9 * @file
10 * @ingroup line_public
11 * The CLI's argument vector, reachable in-process.
12 *
13 * `line-cli` is the only place in this port that serves the WHOLE vocabulary:
14 * every solver, every `-a` analysis, every input format and the `--api`
15 * registry. The in-process facade (`line/solvers/solver.h`) is deliberately a
16 * facade -- `double` only, `avg_table()` and a handful of siblings -- so a host
17 * that wants a response-time CDF, a layered solve or a state probability has
18 * had no route but to spawn the binary. This header is that route.
19 *
20 * THE VOCABULARY IS argv, AND THAT IS THE POINT. `parse_args` accepts about a
21 * hundred and twenty flags and gains more with every solver; a struct mirroring
22 * them would be a second parser to keep in step, and every host binding would
23 * have to learn it. Passing the argument vector verbatim means a host that can
24 * build a command line can reach everything the command line reaches, on the
25 * day the flag lands rather than on the day someone widens a struct.
26 *
27 * WHAT THIS ADDS OVER `system("line-cli ...")` is the three things a subprocess
28 * cannot give: the JSON documents arrive as separate strings rather than as
29 * text to be scanned for a brace, stdout and stderr are captured instead of
30 * escaping into the host's console, and an error arrives with the stable
31 * identifier of `line/io/marshal.h` rather than as prose on fd 2.
32 *
33 * REENTRANCY IS THE CALLER'S TO SERIALIZE. `line_cli.cpp` holds file-scope
34 * state (the output format, the two input-format flags, the verbosity and the
35 * buffered stdin text) and the capture redirects process-global file
36 * descriptors, so two concurrent `run()` calls would interfere. `run()` resets
37 * every one of those before it dispatches, so SEQUENTIAL calls are independent;
38 * concurrent ones need a lock the caller holds. The C ABI in `r/capi/` takes
39 * one, and that is the intended arrangement rather than a workaround.
40 */
41
42#include <string>
43#include <vector>
44
45namespace line {
46namespace cli {
47
48/** One invocation: the argument vector, plus what a pipe would have carried. */
49struct Request {
50 /**
51 * The argument vector WITHOUT argv[0], e.g. {"-f", "m.json", "-s", "mva"}.
52 * A leading program name is not expected and would be parsed as a stray
53 * positional argument.
54 */
55 std::vector<std::string> argv;
56
57 /**
58 * The model document, as if piped to the binary's stdin.
59 *
60 * `has_stdin` rather than an empty check, because an empty document is a
61 * legitimate thing to hand the reader and must produce the reader's own
62 * refusal rather than a read from the host's real stdin.
63 */
64 std::string stdin_text;
65 bool has_stdin = false;
66
67 /** Collect the JSON documents as they are emitted. */
68 bool collect_json = true;
69 /** Redirect fd 1 for the duration of the call into Response::text. */
70 bool capture_stdout = true;
71 /** Redirect fd 2 for the duration of the call into Response::diagnostics. */
72 bool capture_stderr = true;
73 /**
74 * Permit `-p`, which serves until its request budget is exhausted.
75 *
76 * OFF BY DEFAULT because a host that passes a user's argument string
77 * through would otherwise hand that user a way to block the calling thread
78 * indefinitely and open a listening socket. It is a capability, not a
79 * safety check: a caller who means it says so.
80 */
81 bool allow_server = false;
82
83 /**
84 * Polled between analyses and at each document emission; non-zero aborts.
85 *
86 * A HOST CANNOT SIMPLY LONGJMP OUT OF HERE. R's `R_CheckUserInterrupt`
87 * does exactly that, and doing it from a frame with C++ destructors above
88 * it skips every one of them, leaking the capture's file descriptors and
89 * its temporary files. So the host polls its own flag inside this callback
90 * and says so by return value, and the abort unwinds normally.
91 *
92 * WHERE IT IS POLLED IS DELIBERATELY NARROW, and saying so is the point: a
93 * half-working interrupt that is advertised as working is worse than none.
94 * It fires between the analyses of a comma-separated `-a` list and at each
95 * JSON emission. It does NOT reach inside one long solve, because the
96 * solver loops take no callback and threading one through them is an
97 * engine-wide change this seam has no business making.
98 */
99 int (*interrupt)(void* user) = nullptr;
100 void* interrupt_user = nullptr;
101};
102
103/** What one invocation produced. */
104struct Response {
105 /** The CLI's own exit status: 0 ok, 2 `line::Error`, 3 other. */
106 int exit_code = 0;
107
108 /**
109 * Each JSON document the run emitted, in emission order, AS EMITTED.
110 *
111 * STRINGS AND NOT PARSED VALUES, deliberately. The eleven emission sites
112 * use three different indents and the numbers are printed at full
113 * precision so a cross-codebase diff is not capped
114 * (`line_cli.cpp`, `emit_avg_table_named`). Parsing here and re-dumping in
115 * the host would reformat both, and the reformatting would be invisible
116 * until someone diffed a golden. A host that wants a value parses this
117 * string itself, once, at the boundary where it builds its own objects.
118 *
119 * PARTIAL ON FAILURE BY DESIGN: `-a avg,sens` whose `sens` arm throws has
120 * already produced the `avg` envelope, and the binary printed it. Dropping
121 * it here would lose an answer the equivalent command line gives. Read
122 * `exit_code` for whether the run as a whole succeeded.
123 */
124 std::vector<std::string> documents;
125
126 /** Everything written to fd 1, with the collected documents still in it. */
127 std::string text;
128 /** Everything written to fd 2: warnings, and the failure message. */
129 std::string diagnostics;
130
131 /** Empty on success; the `what()` of the exception otherwise. */
132 std::string error;
133 /**
134 * `line::io::error_id`'s stable identifier for `error`.
135 *
136 * One of `line:input`, `line:numeric`, `line:unsupported`, `line:error`,
137 * or `line:internal` for a `std::exception` that is not a `line::Error`
138 * and which `error_id` therefore cannot classify.
139 */
140 std::string error_id;
141};
142
143/**
144 * Run one invocation and return what it produced.
145 *
146 * Throws nothing: every failure the binary would report on fd 2 and an exit
147 * status arrives here as `error`, `error_id` and `exit_code`.
148 */
149Response run(const Request& req);
150
151/** The binary's `main`, so `line_cli_main.cpp` stays ten lines. */
152int main_body(int argc, char** argv);
153
154} // namespace cli
155} // namespace line
156
157#endif // LINE_CLI_CLI_RUN_H
int main_body(int argc, char **argv)
The binary's main, so line_cli_main.cpp stays ten lines.
Response run(const Request &req)
Run one invocation and return what it produced.
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
One invocation: the argument vector, plus what a pipe would have carried.
Definition cli_run.h:49
bool collect_json
Collect the JSON documents as they are emitted.
Definition cli_run.h:68
void * interrupt_user
Definition cli_run.h:100
int(*) interrupt(void *user)
Polled between analyses and at each document emission; non-zero aborts.
Definition cli_run.h:99
std::vector< std::string > argv
The argument vector WITHOUT argv[0], e.g.
Definition cli_run.h:55
std::string stdin_text
The model document, as if piped to the binary's stdin.
Definition cli_run.h:64
bool capture_stderr
Redirect fd 2 for the duration of the call into Response::diagnostics.
Definition cli_run.h:72
bool capture_stdout
Redirect fd 1 for the duration of the call into Response::text.
Definition cli_run.h:70
bool allow_server
Permit -p, which serves until its request budget is exhausted.
Definition cli_run.h:81
What one invocation produced.
Definition cli_run.h:104
int exit_code
The CLI's own exit status: 0 ok, 2 line::Error, 3 other.
Definition cli_run.h:106
std::string error_id
line::io::error_id's stable identifier for error.
Definition cli_run.h:140
std::vector< std::string > documents
Each JSON document the run emitted, in emission order, AS EMITTED.
Definition cli_run.h:124
std::string error
Empty on success; the what() of the exception otherwise.
Definition cli_run.h:132
std::string text
Everything written to fd 1, with the collected documents still in it.
Definition cli_run.h:127
std::string diagnostics
Everything written to fd 2: warnings, and the failure message.
Definition cli_run.h:129