5#ifndef LINE_API_SYM_SYM_ENGINES_H
6#define LINE_API_SYM_SYM_ENGINES_H
58#include <netinet/in.h>
59#include <sys/socket.h>
80 return std::vector<std::string>{
"imperialqore/line-sage-rest:latest",
81 "imperialqore/line-sage-rest"};
92 std::shared_ptr<SageRestEngine> started;
93 std::string container;
94 bool atexitRegistered =
false;
97inline SymState& sym_state() {
98 static SymState state;
102inline std::string lower(
const std::string& s) {
104 for (std::size_t i = 0; i < out.size(); ++i)
105 out[i] =
static_cast<char>(std::tolower(out[i]));
110inline int free_port() {
111 const int fd = ::socket(AF_INET, SOCK_STREAM, 0);
112 if (fd < 0)
throw SymEngineError(
"SymEngines: cannot open a socket to pick a free port");
113 struct sockaddr_in addr;
114 std::memset(&addr, 0,
sizeof(addr));
115 addr.sin_family = AF_INET;
116 addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
118 if (::bind(fd,
reinterpret_cast<struct sockaddr*
>(&addr),
sizeof(addr)) != 0) {
122 socklen_t len =
sizeof(addr);
123 if (::getsockname(fd,
reinterpret_cast<struct sockaddr*
>(&addr), &len) != 0) {
127 const int port =
static_cast<int>(ntohs(addr.sin_port));
133inline bool is_sage_service(
const SageRestEngine& engine) {
135 return engine.info().contains(
"sage_version");
136 }
catch (
const Error&) {
151inline bool serves_required_routes(
const SageRestEngine& engine) {
152 std::vector<std::string> missing;
154 missing = engine.missingRoutes();
155 }
catch (
const Error&) {
158 if (missing.empty())
return true;
159 std::cerr <<
"[LINE] Ignoring the line-sage-rest service at " << engine.getBaseUrl()
160 <<
": it is older than this client and does not serve ";
161 for (std::size_t i = 0; i < missing.size(); ++i)
162 std::cerr << (i ?
", " :
"") << missing[i];
163 std::cerr <<
". Refresh it with 'docker pull " <<
SYM_DOCKER_IMAGE <<
"'." << std::endl;
167inline void sleep_millis(
long millis) {
169 ts.tv_sec = millis / 1000;
170 ts.tv_nsec = (millis % 1000) * 1000000L;
171 ::nanosleep(&ts,
nullptr);
178 detail::SymState& st = detail::sym_state();
179 std::lock_guard<std::mutex> guard(st.mutex);
180 if (st.container.empty())
return;
181 util::capture({
"docker",
"stop",
"-t",
"1", st.container}, 30);
182 st.container.clear();
191 for (std::size_t i = 0; i < candidates.size(); ++i)
193 return std::string();
203inline std::string pull_image(
const std::string& target) {
205 std::cerr <<
"[LINE] Skipping docker pull of " << target
206 <<
": insufficient free space at the Docker storage location; "
207 <<
"keeping the native symbolic backend." << std::endl;
208 return std::string();
210 std::cout <<
"[LINE] Pulling Docker image " << target <<
" (this may take a while)..."
213 return std::string();
217inline std::shared_ptr<SageRestEngine> start_container(
const std::string& image) {
218 const int port = free_port();
219 const std::string name =
"line-sage-rest-" + std::to_string(port);
220 const util::ProcResult run =
221 util::capture({
"docker",
"run",
"-d",
"--rm",
"--name", name,
"-p",
222 std::to_string(port) +
":8080", image},
224 if (run.exitCode != 0 ||
util::trim(run.out).empty())
227 SymState& st = sym_state();
229 std::lock_guard<std::mutex> guard(st.mutex);
231 if (!st.atexitRegistered) {
233 st.atexitRegistered =
true;
237 std::shared_ptr<SageRestEngine> engine =
238 std::make_shared<SageRestEngine>(
"http://localhost:" + std::to_string(port));
240 if (engine->isAvailable()) {
241 if (serves_required_routes(*engine) && engine->isUsable()) {
242 std::lock_guard<std::mutex> guard(st.mutex);
251 " answers but cannot evaluate on this CPU");
256 throw SymEngineError(
"SymEngines: container " + name +
" did not become healthy within " +
269inline std::shared_ptr<SymEngine>
sym_resolve(
const std::string& requested =
"auto") {
270 const std::string req =
util::trim(requested);
271 const std::string reqLower = detail::lower(req);
272 if (reqLower ==
"none" || reqLower ==
"off")
return std::shared_ptr<SymEngine>();
274 if (req.compare(0, 8,
"https://") == 0)
276 "SymEngines: this port's HTTP client has no TLS, so the symbolic service must be "
277 "reached over http://; terminate TLS in front of it or use a local container");
278 if (req.compare(0, 7,
"http://") == 0) {
279 std::shared_ptr<SageRestEngine> engine = std::make_shared<SageRestEngine>(req);
280 return engine->isAvailable() && detail::serves_required_routes(*engine) && engine->isUsable()
282 : std::shared_ptr<SymEngine>();
288 if (url.compare(0, 8,
"https://") == 0) {
290 <<
": this port's HTTP client has no TLS." << std::endl;
292 std::shared_ptr<SageRestEngine> engine = std::make_shared<SageRestEngine>(url);
293 if (engine->isAvailable() && detail::serves_required_routes(*engine) &&
300 detail::SymState& st = detail::sym_state();
301 std::shared_ptr<SageRestEngine> cached;
303 std::lock_guard<std::mutex> guard(st.mutex);
306 if (cached && cached->isAvailable() && cached->isUsable())
return cached;
310 for (std::size_t i = 0; i < ports.size(); ++i) {
311 std::shared_ptr<SageRestEngine> engine =
312 std::make_shared<SageRestEngine>(
"http://localhost:" + std::to_string(ports[i]));
313 if (detail::is_sage_service(*engine) && detail::serves_required_routes(*engine) &&
319 req.empty() || reqLower ==
"auto" || reqLower ==
"true" || reqLower ==
"sage";
321 if (image.empty() && reqLower ==
"sage")
324 image = detail::pull_image(image);
325 if (image.empty())
return std::shared_ptr<SymEngine>();
328 return detail::start_container(image);
329 }
catch (
const Error&) {
330 return std::shared_ptr<SymEngine>();
Base error for the multiprecision C++ port.
UnsupportedError(const std::string &what)
SymEngineError(const std::string &what)
Docker primitives for the backends that legitimately ship an image.
The exception types the port throws.
bool docker_has_storage_for(const std::string &image)
bool docker_has_local_image(const std::string &image)
bool docker_pull(const std::string &image)
Pulls an image, streaming Docker's progress to stdout and stderr.
const char *const SYM_URL_ENV
Environment variable naming a service to use.
constexpr int SYM_STARTUP_TIMEOUT_SECONDS
Seconds to wait for a container to report healthy.
std::string sym_find_image()
std::shared_ptr< SymEngine > sym_resolve(const std::string &requested="auto")
Resolves an engine.
std::vector< int > sym_probe_ports()
Ports probed for an already running service, in order.
const char *const SYM_DOCKER_IMAGE
Image serving the symbolic REST API.
std::vector< std::string > sym_docker_image_candidates()
Fallback tags, tried in order after SYM_DOCKER_IMAGE.
void sym_stop_container()
Stops the container started by this process, if any.
ProcResult capture(const std::vector< std::string > &argv, int timeoutSeconds, bool mergeStderr=false)
Runs a command, capturing stdout and discarding stderr.
std::string trim(const std::string &s)
Trims ASCII whitespace from both ends, as Java's String.trim() does.
Conservation laws of a layered queueing network, enumerated from its structure.
SymEngine backed by the line-sage-rest service.
Running an external command and capturing its output, with a deadline.
Computer algebra operations LINE needs, as seen by this port.