mirror of
https://github.com/NixOS/nix.git
synced 2025-11-12 05:26:02 +01:00
We now see exception beeing thrown when remote building in master because of writing to a non-blocking file descriptor from our json logger. > #0 0x00007f2ea97aea9c in __pthread_kill_implementation () from /nix/store/wn7v2vhyyyi6clcyn0s9ixvl7d4d87ic-glibc-2.40-36/lib/libc.so.6 > #1 0x00007f2ea975c576 in raise () from /nix/store/wn7v2vhyyyi6clcyn0s9ixvl7d4d87ic-glibc-2.40-36/lib/libc.so.6 > #2 0x00007f2ea9744935 in abort () from /nix/store/wn7v2vhyyyi6clcyn0s9ixvl7d4d87ic-glibc-2.40-36/lib/libc.so.6 > #3 0x00007f2ea99e8c2b in __gnu_cxx::__verbose_terminate_handler() [clone .cold] () from /nix/store/ybjcla5bhj8g1y84998pn4a2drfxybkv-gcc-13.3.0-lib/lib/libstdc++.so.6 > #4 0x00007f2ea99f820a in __cxxabiv1::__terminate(void (*)()) () from /nix/store/ybjcla5bhj8g1y84998pn4a2drfxybkv-gcc-13.3.0-lib/lib/libstdc++.so.6 > #5 0x00007f2ea99f8275 in std::terminate() () from /nix/store/ybjcla5bhj8g1y84998pn4a2drfxybkv-gcc-13.3.0-lib/lib/libstdc++.so.6 > #6 0x00007f2ea99f84c7 in __cxa_throw () from /nix/store/ybjcla5bhj8g1y84998pn4a2drfxybkv-gcc-13.3.0-lib/lib/libstdc++.so.6 > #7 0x00007f2eaa5035c2 in nix::writeFull (fd=2, s=..., allowInterrupts=true) at ../unix/file-descriptor.cc:43 > #8 0x00007f2eaa5633c4 in nix::JSONLogger::write (this=this@entry=0x249a7d40, json=...) at /nix/store/4krab2h0hd4wvxxmscxrw21pl77j4i7j-gcc-13.3.0/include/c++/13.3.0/bits/char_traits.h:358 > #9 0x00007f2eaa5658d7 in nix::JSONLogger::logEI (this=<optimized out>, ei=...) at ../logging.cc:242 > #10 0x00007f2ea9c5d048 in nix::Logger::logEI (ei=..., lvl=nix::lvlError, this=0x249a7d40) at /nix/store/a7cq5bqh0ryvnkv4m19ffchnvi8l9qx6-nix-util-2.27.0-dev/include/nix/logging.hh:108 > #11 nix::handleExceptions (programName="nix", fun=...) at ../shared.cc:343 > #12 0x0000000000465b1f in main (argc=<optimized out>, argv=<optimized out>) at /nix/store/4krab2h0hd4wvxxmscxrw21pl77j4i7j-gcc-13.3.0/include/c++/13.3.0/bits/allocator.h:163 > (gdb) frame 10 > #10 0x00007f2ea9c5d048 in nix::Logger::logEI (ei=..., lvl=nix::lvlError, this=0x249a7d40) at /nix/store/a7cq5bqh0ryvnkv4m19ffchnvi8l9qx6-nix-util-2.27.0-dev/include/nix/logging.hh:108 > 108 logEI(ei); So far only drainFD sets the non-blocking flag on a "readable" file descriptor, while this is a "writeable" file descriptor. It's not clear to me yet, why we see logs after that point, but it's also not that bad to handle EAGAIN in read/write functions after all.
222 lines
5.5 KiB
C++
222 lines
5.5 KiB
C++
#include "file-system.hh"
|
|
#include "signals.hh"
|
|
#include "finally.hh"
|
|
#include "serialise.hh"
|
|
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <poll.h>
|
|
|
|
namespace nix {
|
|
|
|
namespace {
|
|
|
|
// This function is needed to handle non-blocking reads/writes. This is needed in the buildhook, because
|
|
// somehow the json logger file descriptor ends up beeing non-blocking and breaks remote-building.
|
|
// TODO: get rid of buildhook and remove this function again (https://github.com/NixOS/nix/issues/12688)
|
|
void pollFD(int fd, int events)
|
|
{
|
|
struct pollfd pfd;
|
|
pfd.fd = fd;
|
|
pfd.events = events;
|
|
int ret = poll(&pfd, 1, -1);
|
|
if (ret == -1) {
|
|
throw SysError("poll on file descriptor failed");
|
|
}
|
|
}
|
|
}
|
|
|
|
std::string readFile(int fd)
|
|
{
|
|
struct stat st;
|
|
if (fstat(fd, &st) == -1)
|
|
throw SysError("statting file");
|
|
|
|
return drainFD(fd, true, st.st_size);
|
|
}
|
|
|
|
void readFull(int fd, char * buf, size_t count)
|
|
{
|
|
while (count) {
|
|
checkInterrupt();
|
|
ssize_t res = read(fd, buf, count);
|
|
if (res == -1) {
|
|
switch (errno) {
|
|
case EINTR: continue;
|
|
case EAGAIN:
|
|
pollFD(fd, POLLIN);
|
|
continue;
|
|
}
|
|
throw SysError("reading from file");
|
|
}
|
|
if (res == 0) throw EndOfFile("unexpected end-of-file");
|
|
count -= res;
|
|
buf += res;
|
|
}
|
|
}
|
|
|
|
|
|
void writeFull(int fd, std::string_view s, bool allowInterrupts)
|
|
{
|
|
while (!s.empty()) {
|
|
if (allowInterrupts) checkInterrupt();
|
|
ssize_t res = write(fd, s.data(), s.size());
|
|
if (res == -1) {
|
|
switch (errno) {
|
|
case EINTR: continue;
|
|
case EAGAIN:
|
|
pollFD(fd, POLLOUT);
|
|
continue;
|
|
}
|
|
throw SysError("writing to file");
|
|
}
|
|
if (res > 0)
|
|
s.remove_prefix(res);
|
|
}
|
|
}
|
|
|
|
|
|
std::string readLine(int fd, bool eofOk)
|
|
{
|
|
std::string s;
|
|
while (1) {
|
|
checkInterrupt();
|
|
char ch;
|
|
// FIXME: inefficient
|
|
ssize_t rd = read(fd, &ch, 1);
|
|
if (rd == -1) {
|
|
switch (errno) {
|
|
case EINTR: continue;
|
|
case EAGAIN: {
|
|
pollFD(fd, POLLIN);
|
|
continue;
|
|
}
|
|
default:
|
|
throw SysError("reading a line");
|
|
}
|
|
} else if (rd == 0) {
|
|
if (eofOk)
|
|
return s;
|
|
else
|
|
throw EndOfFile("unexpected EOF reading a line");
|
|
}
|
|
else {
|
|
if (ch == '\n') return s;
|
|
s += ch;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void drainFD(int fd, Sink & sink, bool block)
|
|
{
|
|
// silence GCC maybe-uninitialized warning in finally
|
|
int saved = 0;
|
|
|
|
if (!block) {
|
|
saved = fcntl(fd, F_GETFL);
|
|
if (fcntl(fd, F_SETFL, saved | O_NONBLOCK) == -1)
|
|
throw SysError("making file descriptor non-blocking");
|
|
}
|
|
|
|
Finally finally([&]() {
|
|
if (!block) {
|
|
if (fcntl(fd, F_SETFL, saved) == -1)
|
|
throw SysError("making file descriptor blocking");
|
|
}
|
|
});
|
|
|
|
std::vector<unsigned char> buf(64 * 1024);
|
|
while (1) {
|
|
checkInterrupt();
|
|
ssize_t rd = read(fd, buf.data(), buf.size());
|
|
if (rd == -1) {
|
|
if (!block && (errno == EAGAIN || errno == EWOULDBLOCK))
|
|
break;
|
|
if (errno != EINTR)
|
|
throw SysError("reading from file");
|
|
}
|
|
else if (rd == 0) break;
|
|
else sink({reinterpret_cast<char *>(buf.data()), (size_t) rd});
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
void Pipe::create()
|
|
{
|
|
int fds[2];
|
|
#if HAVE_PIPE2
|
|
if (pipe2(fds, O_CLOEXEC) != 0) throw SysError("creating pipe");
|
|
#else
|
|
if (pipe(fds) != 0) throw SysError("creating pipe");
|
|
unix::closeOnExec(fds[0]);
|
|
unix::closeOnExec(fds[1]);
|
|
#endif
|
|
readSide = fds[0];
|
|
writeSide = fds[1];
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
#if __linux__ || __FreeBSD__
|
|
static int unix_close_range(unsigned int first, unsigned int last, int flags)
|
|
{
|
|
#if !HAVE_CLOSE_RANGE
|
|
return syscall(SYS_close_range, first, last, (unsigned int)flags);
|
|
#else
|
|
return close_range(first, last, flags);
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
void unix::closeExtraFDs()
|
|
{
|
|
constexpr int MAX_KEPT_FD = 2;
|
|
static_assert(std::max({STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO}) == MAX_KEPT_FD);
|
|
|
|
#if __linux__ || __FreeBSD__
|
|
// first try to close_range everything we don't care about. if this
|
|
// returns an error with these parameters we're running on a kernel
|
|
// that does not implement close_range (i.e. pre 5.9) and fall back
|
|
// to the old method. we should remove that though, in some future.
|
|
if (unix_close_range(MAX_KEPT_FD + 1, ~0U, 0) == 0) {
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
#if __linux__
|
|
try {
|
|
for (auto & s : std::filesystem::directory_iterator{"/proc/self/fd"}) {
|
|
checkInterrupt();
|
|
auto fd = std::stoi(s.path().filename());
|
|
if (fd > MAX_KEPT_FD) {
|
|
debug("closing leaked FD %d", fd);
|
|
close(fd);
|
|
}
|
|
}
|
|
return;
|
|
} catch (SysError &) {
|
|
} catch (std::filesystem::filesystem_error &) {
|
|
}
|
|
#endif
|
|
|
|
int maxFD = 0;
|
|
#if HAVE_SYSCONF
|
|
maxFD = sysconf(_SC_OPEN_MAX);
|
|
#endif
|
|
for (int fd = MAX_KEPT_FD + 1; fd < maxFD; ++fd)
|
|
close(fd); /* ignore result */
|
|
}
|
|
|
|
|
|
void unix::closeOnExec(int fd)
|
|
{
|
|
int prev;
|
|
if ((prev = fcntl(fd, F_GETFD, 0)) == -1 ||
|
|
fcntl(fd, F_SETFD, prev | FD_CLOEXEC) == -1)
|
|
throw SysError("setting close-on-exec flag");
|
|
}
|
|
|
|
}
|