libutil: add makeNonBlocking, resetNonBlocking

these are used often enough that deduplicating them is worth it. we do
lose some error fidelity, but valid fds will never cause an error here

Change-Id: I2b91b4848f546a894a2a6c2d36c32a892fb73c9f
This commit is contained in:
eldritch horrors
2025-07-01 17:06:26 +02:00
parent 6e7c0812c7
commit ce6eba531e
8 changed files with 66 additions and 47 deletions
+2 -7
View File
@@ -1,5 +1,6 @@
#include "lix/libstore/build/derivation-goal.hh"
#include "lix/libutil/async.hh"
#include "lix/libutil/file-descriptor.hh"
#include "lix/libutil/file-system.hh"
#include "lix/libstore/build/hook-instance.hh"
#include "lix/libstore/build/worker.hh"
@@ -1204,13 +1205,7 @@ struct DerivationGoal::InputStream final : private kj::AsyncObject
: fd(fd)
, observer(ep, fd, kj::UnixEventPort::FdObserver::OBSERVE_READ)
{
int flags = fcntl(fd, F_GETFL);
if (flags < 0) {
throw SysError("fcntl(F_GETFL) failed on fd %i", fd);
}
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
throw SysError("fcntl(F_SETFL) failed on fd %i", fd);
}
makeNonBlocking(fd);
}
kj::Promise<std::string_view> read(kj::ArrayPtr<char> buffer)
+6 -4
View File
@@ -3,6 +3,7 @@
#include "lix/libstore/pathlocks.hh"
#include "lix/libutil/async.hh"
#include "lix/libutil/error.hh"
#include "lix/libutil/file-descriptor.hh"
#include "lix/libutil/processes.hh"
#include "lix/libutil/result.hh"
#include "lix/libutil/signals.hh"
@@ -456,9 +457,7 @@ class GCOperation {
createDirs(dirOf(socketPath));
fdServer = createUnixDomainSocket(socketPath, 0666);
if (fcntl(fdServer.get(), F_SETFL, fcntl(fdServer.get(), F_GETFL) | O_NONBLOCK) == -1) {
throw SysError("making socket '%1%' non-blocking", socketPath);
}
makeNonBlocking(fdServer.get());
serverThread = std::thread([this]() {
setCurrentThreadName("gc server");
@@ -547,8 +546,11 @@ void GCOperation::runServerThread()
/* On macOS, accepted sockets inherit the
non-blocking flag from the server socket, so
explicitly make it blocking. */
if (fcntl(fdClient.get(), F_SETFL, fcntl(fdClient.get(), F_GETFL) & ~O_NONBLOCK) == -1)
try {
makeBlocking(fdClient.get());
} catch (...) {
abort();
}
while (true) {
try {
+3 -9
View File
@@ -3,6 +3,7 @@
#include "lix/libutil/async.hh"
#include "lix/libutil/box_ptr.hh"
#include "lix/libutil/error.hh"
#include "lix/libutil/file-descriptor.hh"
#include "lix/libutil/result.hh"
#include "lix/libutil/serialise.hh"
#include "lix/libutil/signals.hh"
@@ -784,15 +785,8 @@ try {
// SAFETY NOTE: while we're running we own the executor, and thus the stream.
// setting these flags is unsafe if the stream is shared with another thread.
const int oldFlags = fcntl(getFD(), F_GETFL);
if (oldFlags == -1 || fcntl(getFD(), F_SETFL, oldFlags & ~O_NONBLOCK) < 0) {
throw SysError("making connection blocking");
}
KJ_DEFER({
if (fcntl(getFD(), F_SETFL, oldFlags) < 0) {
throw SysError("restoring connection flags");
}
});
const auto oldState = makeBlocking(getFD());
KJ_DEFER(resetBlockingState(getFD(), oldState));
FdSource from{getFD(), fromBuf};
from.specialEndOfFileError = "Nix daemon disconnected while waiting for a response";
+5 -10
View File
@@ -148,22 +148,17 @@ AsyncFdIoStream::AsyncFdIoStream(AutoCloseFD fd) : AsyncFdIoStream(shared_fd{},
AsyncFdIoStream::AsyncFdIoStream(shared_fd, int fd)
: fd(fd)
, oldState(makeNonBlocking(fd))
, observer(AIO().unixEventPort, fd, kj::UnixEventPort::FdObserver::OBSERVE_READ_WRITE)
{
oldFlags = fcntl(fd, F_GETFL, 0);
if (oldFlags == -1 || fcntl(fd, F_SETFL, oldFlags | O_NONBLOCK)) {
throw SysError("making file descriptor non-blocking");
}
}
AsyncFdIoStream::~AsyncFdIoStream() noexcept(false)
{
if (fcntl(fd, F_SETFL, oldFlags)) {
try {
throw SysError("restoring file descriptor flags");
} catch (...) {
ignoreExceptionInDestructor();
}
try {
resetBlockingState(fd, oldState);
} catch (...) {
ignoreExceptionInDestructor();
}
}
+2 -1
View File
@@ -161,7 +161,8 @@ class AsyncStream : public AsyncInputStream, public AsyncOutputStream
class AsyncFdIoStream : public AsyncStream
{
int fd, oldFlags;
int fd;
FdBlockingState oldState;
AutoCloseFD ownedFd; // only for closing automatically, must equal fd if set
kj::UnixEventPort::FdObserver observer;
+2 -10
View File
@@ -298,16 +298,8 @@ struct DecompressionStream : DecompressorPipes, AsyncInputStream
DecompressionStream(const std::string & method, box_ptr<AsyncInputStream> inner)
: inner(std::move(inner))
{
if (auto flags = fcntl(compressed.writeSide.get(), F_GETFL);
flags == -1 || fcntl(compressed.writeSide.get(), F_SETFL, flags | O_NONBLOCK))
{
throw SysError("setting up decompression stream");
}
if (auto flags = fcntl(uncompressed.readSide.get(), F_GETFL);
flags == -1 || fcntl(uncompressed.readSide.get(), F_SETFL, flags | O_NONBLOCK))
{
throw SysError("setting up decompression stream");
}
makeNonBlocking(compressed.writeSide.get());
makeNonBlocking(uncompressed.readSide.get());
source = std::make_unique<FdSource>(compressed.readSide.get());
sink = std::make_unique<FdSink>(uncompressed.writeSide.get());
+29 -6
View File
@@ -1,3 +1,4 @@
#include "file-descriptor.hh"
#include "lix/libutil/charptr-cast.hh"
#include "lix/libutil/file-system.hh"
#include "lix/libutil/finally.hh"
@@ -89,18 +90,15 @@ std::string drainFD(int fd, bool block, const size_t reserveSize)
Generator<Bytes> drainFDSource(int fd, bool block)
{
// silence GCC maybe-uninitialized warning in finally
int saved = 0;
FdBlockingState saved{};
if (!block) {
saved = fcntl(fd, F_GETFL);
if (fcntl(fd, F_SETFL, saved | O_NONBLOCK) == -1)
throw SysError("making file descriptor non-blocking");
saved = makeNonBlocking(fd);
}
Finally finally([&]() {
if (!block) {
if (fcntl(fd, F_SETFL, saved) == -1)
throw SysError("making file descriptor blocking");
resetBlockingState(fd, saved);
}
});
@@ -294,4 +292,29 @@ void closeOnExec(int fd)
throw SysError("setting close-on-exec flag");
}
FdBlockingState makeNonBlocking(int fd)
{
const auto oldFlags = fcntl(fd, F_GETFL);
if (oldFlags < 0 || fcntl(fd, F_SETFL, oldFlags | O_NONBLOCK) == -1) {
throw SysError("makeNonBlocking");
}
return FdBlockingState(oldFlags & O_NONBLOCK);
}
FdBlockingState makeBlocking(int fd)
{
const auto oldFlags = fcntl(fd, F_GETFL);
if (fcntl(fd, F_SETFL, oldFlags & ~O_NONBLOCK) == -1) {
throw SysError("makeBlocking");
}
return FdBlockingState(oldFlags & O_NONBLOCK);
}
void resetBlockingState(int fd, FdBlockingState prevState)
{
const auto oldFlags = fcntl(fd, F_GETFL);
if (oldFlags < 0 || fcntl(fd, F_SETFL, (oldFlags & ~O_NONBLOCK) | int(prevState)) == -1) {
throw SysError("resetBlockingState");
}
}
}
+17
View File
@@ -94,6 +94,23 @@ void closeExtraFDs();
*/
void closeOnExec(int fd);
enum class FdBlockingState : int {};
/**
* Make the given file descriptor non-blocking. Returns the old flag set; this
* can be passed to resetBlockingState to return the fd to its original state.
*/
FdBlockingState makeNonBlocking(int fd);
/**
* Make the given file descriptor blocking. Returns the old flag set; it can
* be passed to `resetBlockingState` to return the fd to its original state.
*/
FdBlockingState makeBlocking(int fd);
/**
* Undo a `makeNonBlocking` or `makeBlocking` call.
*/
void resetBlockingState(int fd, FdBlockingState prevState);
MakeError(EndOfFile, Error);
}