Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/libstore/daemon.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "nix/store/gc-store.hh"
#include "nix/store/log-store.hh"
#include "nix/store/indirect-root-store.hh"
#include "nix/store/remote-store.hh"
#include "nix/store/path-with-outputs.hh"
#include "nix/util/finally.hh"
#include "nix/util/archive.hh"
Expand Down Expand Up @@ -1026,6 +1027,16 @@ void processConnection(ref<Store> store, FdSource && from, FdSink && to, Trusted
auto monitor = !recursive ? std::make_unique<MonitorFdHup>(from.fd) : nullptr;
(void) monitor; // suppress warning
ReceiveInterrupts receiveInterrupts;

/* When interrupted (e.g., SSH client disconnects), shutdown any downstream
store connections to break circular waits. This fixes deadlocks where the
daemon is waiting for a response from a downstream store while the downstream
is waiting for more data from this daemon. */
auto shutdownStoreOnInterrupt = createInterruptCallback([&store]() {
if (auto remoteStore = dynamic_cast<RemoteStore *>(&*store)) {
remoteStore->shutdownConnections();
}
});
#endif

/* Exchange the greeting. */
Expand Down
16 changes: 16 additions & 0 deletions src/libstore/include/nix/store/remote-store.hh
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@
///@file

#include <limits>
#include <set>
#include <string>

#include "nix/store/store-api.hh"
#include "nix/util/sync.hh"
#include "nix/util/file-descriptor.hh"
#include "nix/store/gc-store.hh"
#include "nix/store/log-store.hh"

Expand Down Expand Up @@ -153,6 +156,13 @@ struct RemoteStore : public virtual Store, public virtual GcStore, public virtua

void flushBadConnections();

/**
* Shutdown all connections (both idle and in-use) to break any blocking I/O.
* This is called on interrupt to allow graceful termination when the client
* disconnects during a long-running operation.
*/
void shutdownConnections();

struct Connection;

ref<Connection> openConnectionWrapper();
Expand Down Expand Up @@ -191,6 +201,12 @@ private:

std::atomic_bool failed{false};

/**
* Track all active connection file descriptors (both idle and in-use).
* Used by shutdownConnections() to break blocking I/O on interrupt.
*/
Sync<std::set<Descriptor>> connectionFds;

void copyDrvsFromEvalStore(const std::vector<DerivedPath> & paths, std::shared_ptr<Store> evalStore);
};

Expand Down
20 changes: 20 additions & 0 deletions src/libstore/remote-store.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
#include "nix/store/filetransfer.hh"
#include "nix/util/signals.hh"

#ifndef _WIN32
# include <sys/socket.h>
#endif

#include <nlohmann/json.hpp>

namespace nix {
Expand All @@ -38,6 +42,8 @@ RemoteStore::RemoteStore(const Config & config)
failed = true;
throw;
}
/* Track the connection FD for shutdownConnections() */
connectionFds.lock()->insert(conn->from.fd);
return conn;
},
[this](const ref<Connection> & r) {
Expand Down Expand Up @@ -797,6 +803,20 @@ void RemoteStore::flushBadConnections()
connections->flushBad();
}

void RemoteStore::shutdownConnections()
{
#ifndef _WIN32
auto fds = connectionFds.lock();
for (auto fd : *fds) {
/* Use shutdown() instead of close() to signal EOF to any blocking
reads/writes without actually closing the FD (which would cause
issues if the connection is still in use). This breaks circular
waits when the client disconnects during long-running operations. */
::shutdown(fromDescriptorReadOnly(fd), SHUT_RDWR);
}
#endif
}

void RemoteStore::narFromPath(const StorePath & path, Sink & sink)
{
auto conn(getConnection());
Expand Down
Loading