1
0
Fork 0
mirror of https://github.com/NixOS/nix synced 2025-06-27 12:41:15 +02:00

withFramedSink(): Don't use a thread to monitor the other side

Since withFramedSink() is now used a lot more than in the past (for
every addToStore() variant), we were creating a lot of threads, e.g.

  nix flake show --no-eval-cache --all-systems github:NixOS/nix/afdd12be5e19c0001ff3297dea544301108d298

would create 46418 threads. While threads on Linux are cheap, this is
still substantial overhead.

So instead, just poll from FramedSink before every write whether there
are pending messages from the daemon. This could slightly increase the
latency on log messages from the daemon, but not on exceptions (which
were only synchronously checked from FramedSink anyway).

This speeds up the command above from 19.2s to 17.5s on my machine (a
9% speedup).

(cherry picked from commit 39daa4a0d3)
This commit is contained in:
Eelco Dolstra 2024-08-19 17:49:26 +02:00 committed by Mergify
parent e0c8b0fc4f
commit 36f3fb72e9
6 changed files with 61 additions and 52 deletions

View file

@ -49,7 +49,7 @@ struct RemoteStore::ConnectionHandle
RemoteStore::Connection & operator * () { return *handle; } RemoteStore::Connection & operator * () { return *handle; }
RemoteStore::Connection * operator -> () { return &*handle; } RemoteStore::Connection * operator -> () { return &*handle; }
void processStderr(Sink * sink = 0, Source * source = 0, bool flush = true); void processStderr(Sink * sink = 0, Source * source = 0, bool flush = true, bool block = true);
void withFramedSink(std::function<void(Sink & sink)> fun); void withFramedSink(std::function<void(Sink & sink)> fun);
}; };

View file

@ -153,9 +153,9 @@ RemoteStore::ConnectionHandle::~ConnectionHandle()
} }
} }
void RemoteStore::ConnectionHandle::processStderr(Sink * sink, Source * source, bool flush) void RemoteStore::ConnectionHandle::processStderr(Sink * sink, Source * source, bool flush, bool block)
{ {
handle->processStderr(&daemonException, sink, source, flush); handle->processStderr(&daemonException, sink, source, flush, block);
} }
@ -926,43 +926,17 @@ void RemoteStore::ConnectionHandle::withFramedSink(std::function<void(Sink & sin
{ {
(*this)->to.flush(); (*this)->to.flush();
std::exception_ptr ex;
/* Handle log messages / exceptions from the remote on a separate
thread. */
std::thread stderrThread([&]()
{ {
try { FramedSink sink((*this)->to, [&]() {
ReceiveInterrupts receiveInterrupts; /* Periodically process stderr messages and exceptions
processStderr(nullptr, nullptr, false); from the daemon. */
} catch (...) { processStderr(nullptr, nullptr, false, false);
ex = std::current_exception();
}
}); });
Finally joinStderrThread([&]()
{
if (stderrThread.joinable()) {
stderrThread.join();
if (ex) {
try {
std::rethrow_exception(ex);
} catch (...) {
ignoreException();
}
}
}
});
{
FramedSink sink((*this)->to, ex);
fun(sink); fun(sink);
sink.flush(); sink.flush();
} }
stderrThread.join(); processStderr(nullptr, nullptr, false);
if (ex)
std::rethrow_exception(ex);
} }
} }

View file

@ -32,7 +32,8 @@ static Logger::Fields readFields(Source & from)
return fields; return fields;
} }
std::exception_ptr WorkerProto::BasicClientConnection::processStderrReturn(Sink * sink, Source * source, bool flush) std::exception_ptr
WorkerProto::BasicClientConnection::processStderrReturn(Sink * sink, Source * source, bool flush, bool block)
{ {
if (flush) if (flush)
to.flush(); to.flush();
@ -41,6 +42,9 @@ std::exception_ptr WorkerProto::BasicClientConnection::processStderrReturn(Sink
while (true) { while (true) {
if (!block && !from.hasData())
break;
auto msg = readNum<uint64_t>(from); auto msg = readNum<uint64_t>(from);
if (msg == STDERR_WRITE) { if (msg == STDERR_WRITE) {
@ -95,8 +99,10 @@ std::exception_ptr WorkerProto::BasicClientConnection::processStderrReturn(Sink
logger->result(act, type, fields); logger->result(act, type, fields);
} }
else if (msg == STDERR_LAST) else if (msg == STDERR_LAST) {
assert(block);
break; break;
}
else else
throw Error("got unknown message type %x from Nix daemon", msg); throw Error("got unknown message type %x from Nix daemon", msg);
@ -130,9 +136,10 @@ std::exception_ptr WorkerProto::BasicClientConnection::processStderrReturn(Sink
} }
} }
void WorkerProto::BasicClientConnection::processStderr(bool * daemonException, Sink * sink, Source * source, bool flush) void WorkerProto::BasicClientConnection::processStderr(
bool * daemonException, Sink * sink, Source * source, bool flush, bool block)
{ {
auto ex = processStderrReturn(sink, source, flush); auto ex = processStderrReturn(sink, source, flush, block);
if (ex) { if (ex) {
*daemonException = true; *daemonException = true;
std::rethrow_exception(ex); std::rethrow_exception(ex);

View file

@ -70,9 +70,10 @@ struct WorkerProto::BasicClientConnection : WorkerProto::BasicConnection
virtual void closeWrite() = 0; virtual void closeWrite() = 0;
std::exception_ptr processStderrReturn(Sink * sink = 0, Source * source = 0, bool flush = true); std::exception_ptr processStderrReturn(Sink * sink = 0, Source * source = 0, bool flush = true, bool block = true);
void processStderr(bool * daemonException, Sink * sink = 0, Source * source = 0, bool flush = true); void
processStderr(bool * daemonException, Sink * sink = 0, Source * source = 0, bool flush = true, bool block = true);
/** /**
* Establishes connection, negotiating version. * Establishes connection, negotiating version.

View file

@ -10,6 +10,8 @@
#ifdef _WIN32 #ifdef _WIN32
# include <fileapi.h> # include <fileapi.h>
# include "windows-error.hh" # include "windows-error.hh"
#else
# include <poll.h>
#endif #endif
@ -158,6 +160,24 @@ bool FdSource::good()
} }
bool FdSource::hasData()
{
if (BufferedSource::hasData()) return true;
while (true) {
struct pollfd fds[1];
fds[0].fd = fd;
fds[0].events = POLLIN;
auto n = poll(fds, 1, 0);
if (n < 0) {
if (errno == EINTR) continue;
throw SysError("polling file descriptor");
}
return n == 1 && (fds[0].events & POLLIN);
}
}
size_t StringSource::read(char * data, size_t len) size_t StringSource::read(char * data, size_t len)
{ {
if (pos == s.size()) throw EndOfFile("end of string reached"); if (pos == s.size()) throw EndOfFile("end of string reached");

View file

@ -104,6 +104,9 @@ struct BufferedSource : Source
size_t read(char * data, size_t len) override; size_t read(char * data, size_t len) override;
/**
* Return true if the buffer is not empty.
*/
bool hasData(); bool hasData();
protected: protected:
@ -162,6 +165,13 @@ struct FdSource : BufferedSource
FdSource & operator=(FdSource && s) = default; FdSource & operator=(FdSource && s) = default;
bool good() override; bool good() override;
/**
* Return true if the buffer is not empty after a non-blocking
* read.
*/
bool hasData();
protected: protected:
size_t readUnbuffered(char * data, size_t len) override; size_t readUnbuffered(char * data, size_t len) override;
private: private:
@ -522,15 +532,16 @@ struct FramedSource : Source
/** /**
* Write as chunks in the format expected by FramedSource. * Write as chunks in the format expected by FramedSource.
* *
* The exception_ptr reference can be used to terminate the stream when you * The `checkError` function can be used to terminate the stream when you
* detect that an error has occurred on the remote end. * detect that an error has occurred.
*/ */
struct FramedSink : nix::BufferedSink struct FramedSink : nix::BufferedSink
{ {
BufferedSink & to; BufferedSink & to;
std::exception_ptr & ex; std::function<void()> checkError;
FramedSink(BufferedSink & to, std::exception_ptr & ex) : to(to), ex(ex) FramedSink(BufferedSink & to, std::function<void()> && checkError)
: to(to), checkError(checkError)
{ } { }
~FramedSink() ~FramedSink()
@ -545,13 +556,9 @@ struct FramedSink : nix::BufferedSink
void writeUnbuffered(std::string_view data) override void writeUnbuffered(std::string_view data) override
{ {
/* Don't send more data if the remote has /* Don't send more data if an error has occured. */
encountered an error. */ checkError();
if (ex) {
auto ex2 = ex;
ex = nullptr;
std::rethrow_exception(ex2);
}
to << data.size(); to << data.size();
to(data); to(data);
}; };