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&) {
141inline void sleep_millis(
long millis) {
143 ts.tv_sec = millis / 1000;
144 ts.tv_nsec = (millis % 1000) * 1000000L;
145 ::nanosleep(&ts,
nullptr);
152 detail::SymState& st = detail::sym_state();
153 std::lock_guard<std::mutex> guard(st.mutex);
154 if (st.container.empty())
return;
155 util::capture({
"docker",
"stop",
"-t",
"1", st.container}, 30);
156 st.container.clear();
165 for (std::size_t i = 0; i < candidates.size(); ++i)
167 return std::string();
177inline std::string pull_image(
const std::string& target) {
179 std::cerr <<
"[LINE] Skipping docker pull of " << target
180 <<
": insufficient free space at the Docker storage location; "
181 <<
"keeping the native symbolic backend." << std::endl;
182 return std::string();
184 std::cout <<
"[LINE] Pulling Docker image " << target <<
" (this may take a while)..."
187 return std::string();
191inline std::shared_ptr<SageRestEngine> start_container(
const std::string& image) {
192 const int port = free_port();
193 const std::string name =
"line-sage-rest-" + std::to_string(port);
194 const util::ProcResult run =
195 util::capture({
"docker",
"run",
"-d",
"--rm",
"--name", name,
"-p",
196 std::to_string(port) +
":8080", image},
198 if (run.exitCode != 0 ||
util::trim(run.out).empty())
201 SymState& st = sym_state();
203 std::lock_guard<std::mutex> guard(st.mutex);
205 if (!st.atexitRegistered) {
207 st.atexitRegistered =
true;
211 std::shared_ptr<SageRestEngine> engine =
212 std::make_shared<SageRestEngine>(
"http://localhost:" + std::to_string(port));
214 if (engine->isAvailable()) {
215 if (engine->isUsable()) {
216 std::lock_guard<std::mutex> guard(st.mutex);
225 " answers but cannot evaluate on this CPU");
230 throw SymEngineError(
"SymEngines: container " + name +
" did not become healthy within " +
243inline std::shared_ptr<SymEngine>
sym_resolve(
const std::string& requested =
"auto") {
244 const std::string req =
util::trim(requested);
245 const std::string reqLower = detail::lower(req);
246 if (reqLower ==
"none" || reqLower ==
"off")
return std::shared_ptr<SymEngine>();
248 if (req.compare(0, 8,
"https://") == 0)
250 "SymEngines: this port's HTTP client has no TLS, so the symbolic service must be "
251 "reached over http://; terminate TLS in front of it or use a local container");
252 if (req.compare(0, 7,
"http://") == 0) {
253 std::shared_ptr<SageRestEngine> engine = std::make_shared<SageRestEngine>(req);
254 return engine->isAvailable() && engine->isUsable() ? engine : std::shared_ptr<SymEngine>();
260 if (url.compare(0, 8,
"https://") == 0) {
262 <<
": this port's HTTP client has no TLS." << std::endl;
264 std::shared_ptr<SageRestEngine> engine = std::make_shared<SageRestEngine>(url);
265 if (engine->isAvailable() && engine->isUsable())
return engine;
270 detail::SymState& st = detail::sym_state();
271 std::shared_ptr<SageRestEngine> cached;
273 std::lock_guard<std::mutex> guard(st.mutex);
276 if (cached && cached->isAvailable() && cached->isUsable())
return cached;
280 for (std::size_t i = 0; i < ports.size(); ++i) {
281 std::shared_ptr<SageRestEngine> engine =
282 std::make_shared<SageRestEngine>(
"http://localhost:" + std::to_string(ports[i]));
283 if (detail::is_sage_service(*engine) && engine->isUsable())
return engine;
287 req.empty() || reqLower ==
"auto" || reqLower ==
"true" || reqLower ==
"sage";
289 if (image.empty() && reqLower ==
"sage")
292 image = detail::pull_image(image);
293 if (image.empty())
return std::shared_ptr<SymEngine>();
296 return detail::start_container(image);
297 }
catch (
const Error&) {
298 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.
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.