fix: #eval: redirect stdout/stderr

This commit is contained in:
Sebastian Ullrich 2020-06-04 16:31:39 +02:00
parent f4c28fbe5f
commit cfd1900625
3 changed files with 67 additions and 8 deletions

View file

@ -7,6 +7,12 @@ Author: Leonardo de Moura
#include <algorithm>
#include <string>
#include <vector>
#ifndef LEAN_WINDOWS
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#endif
#include <unistd.h>
#include <lean/sstream.h>
#include <lean/compact.h>
#include "util/timeit.h"
@ -383,8 +389,60 @@ static environment eval_cmd(parser & p) {
auto out = p.mk_message(p.cmd_pos(), p.pos(), INFORMATION);
out.set_caption("eval result");
scope_traces_as_messages scope_traces(p.get_stream_name(), p.cmd_pos());
std::streambuf * saved_cout = std::cout.rdbuf(out.get_text_stream().get_stream().rdbuf());
std::streambuf * saved_cerr = std::cerr.rdbuf(out.get_text_stream().get_stream().rdbuf());
// Now for the fun part: we want to temporarily close stdin and capture stdout/stderr of the evaluated program
// so it doesn't interfere with the server I/O. We could do this on the Lean API level (i.e. `IO.getLine/putStr`),
// but that wouldn't affect direct access to `IO.stdin/...` nor FFI-called code. Instead, we directly work on file
// descriptors.
// Create a fresh file descriptor we can point stdout/stderr to
#if defined(__linux__)
// On Linux, we can simply open an anonymous file in memory
int buf_fd = memfd_create("lean-eval", 0);
#elif 0
// On macOS, we can open exclusive shared memory object, guessing a hopefully unique name
// ...or at least we should be able to, but it doesn't work for some reason.
// NOTE: what doesn't work: `funopen` returns a `FILE *` stream without a file descriptor
std::string shm_name = (sstream() << "lean-eval-" << getpid()).str();
int buf_fd = shm_open(shm_name.c_str(), O_RDWR | O_CREAT | O_EXCL, S_IRWXU);
lean_always_assert(shm_unlink(shm_name.c_str()) == 0);
#else
// On Windows we can open an actual file I guess
FILE * buf_f = tmpfile(); lean_always_assert(buf_f != nullptr);
int buf_fd = fileno(buf_f);
#endif
lean_always_assert(buf_fd >= 0);
// NOTE: what doesn't work: `pipe` creates file descriptors, but we would need a separate consumer thread so
// the evaluated program doesn't block on a full pipe
// make sure to drain user-level buffers
fflush(stdout); fflush(stderr);
// copy stdout/stderr, then set them to `buf_fd`
#ifdef __linux__
// On Linux, we also redirect stdin so it appears as empty. This doesn't seem to work on other platforms.
// NOTE: Since we can't flush stdin, this only really works if we are on a line ending (assuming stdin is line buffered).
// This should be the case for the server, which is line-based.
int old_stdin = dup(STDIN_FILENO); lean_always_assert(old_stdin >= 0); lean_always_assert(dup2(buf_fd, STDIN_FILENO) >= 0);
#endif
int old_stdout = dup(STDOUT_FILENO); lean_always_assert(old_stdout >= 0); lean_always_assert(dup2(buf_fd, STDOUT_FILENO) >= 0);
int old_stderr = dup(STDERR_FILENO); lean_always_assert(old_stderr >= 0); lean_always_assert(dup2(buf_fd, STDERR_FILENO) >= 0);
std::function<void()> report_buf = [&]() {
fflush(stdout); fflush(stderr);
// restore old streams
#ifdef __linux__
lean_always_assert(dup2(old_stdin, STDIN_FILENO) >= 0); lean_always_assert(close(old_stdin) == 0);
#endif
lean_always_assert(dup2(old_stdout, STDOUT_FILENO) >= 0); lean_always_assert(close(old_stdout) == 0);
lean_always_assert(dup2(old_stderr, STDERR_FILENO) >= 0); lean_always_assert(close(old_stderr) == 0);
// write `buf_fd` contents to `out`
off_t buf_sz = lseek(buf_fd, 0, SEEK_CUR);
lseek(buf_fd, 0, SEEK_SET);
std::string buf_s(buf_sz, '\0');
lean_always_assert(read(buf_fd, static_cast<void *>(&buf_s[0]), buf_sz) == buf_sz);
lean_always_assert(close(buf_fd) == 0);
out << buf_s;
out.report();
};
object_ref r;
@ -393,15 +451,12 @@ static environment eval_cmd(parser & p) {
message_builder(environment(), get_global_ios(), "foo", pos_info(), message_severity::INFORMATION));
r = object_ref(ir::run_boxed(new_env, fn_name, args.size(), &args[0]));
} catch (exception & ex) {
std::cout.rdbuf(saved_cout);
std::cerr.rdbuf(saved_cerr);
out.report();
report_buf();
throw ex;
}
std::cout.rdbuf(saved_cout);
std::cerr.rdbuf(saved_cerr);
out.report();
report_buf();
if (io_result_is_error(r.raw())) {
message_builder msg = p.mk_message(p.cmd_pos(), p.pos(), ERROR);
object * err = io_result_get_error(r.raw());

View file

@ -0,0 +1,3 @@
#eval show IO _ from do
h ← IO.stdin;
h.getLine

View file

@ -0,0 +1 @@
""