From c914b9fc53e1838b6cabb3d73c188f5b2fe9cf45 Mon Sep 17 00:00:00 2001 From: eldritch horrors Date: Thu, 5 Mar 2026 22:23:01 +0100 Subject: [PATCH] nix/daemon: wrap kj stdio forwarding errors nicely not doing this has lix crash if stdio forwarding fails for odd reasons, such as ssh failing to set up a connection due to transient dns errors. cf #1148 Change-Id: I543b7444236e69d3c13b47fb6c6eb37c65b96763 --- lix/nix/daemon.cc | 17 +++++++++++++---- tests/functional2/cli/test_daemon.py | 26 ++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 tests/functional2/cli/test_daemon.py diff --git a/lix/nix/daemon.cc b/lix/nix/daemon.cc index 65de46a49..757921f9e 100644 --- a/lix/nix/daemon.cc +++ b/lix/nix/daemon.cc @@ -34,7 +34,9 @@ #include #include +#include #include +#include #include #include #include @@ -536,13 +538,20 @@ static void forwardStdioConnection(AsyncIoRoot & aio, RemoteStore & store) aio.blockOn(connSocket->pumpTo(*asyncStdout) .then([](auto) -> Result { - return { - std::make_exception_ptr(EndOfFile("unexpected EOF from daemon socket")) - }; + return {std::make_exception_ptr(EndOfFile("unexpected EOF from daemon socket"))}; }) .exclusiveJoin(asyncStdin->pumpTo(*connSocket).then([](auto) -> Result { return result::success(); - }))); + })) + .catch_([](kj::Exception && e) -> Result { + if (e.getType() == kj::Exception::Type::DISCONNECTED) { + return std::make_exception_ptr(Error("peer disconnected unexpectedly")); + } else { + return std::make_exception_ptr( + Error("forwarding daemon connection: %s", e.getDescription().cStr()) + ); + } + })); } /** diff --git a/tests/functional2/cli/test_daemon.py b/tests/functional2/cli/test_daemon.py new file mode 100644 index 000000000..d9bbedd75 --- /dev/null +++ b/tests/functional2/cli/test_daemon.py @@ -0,0 +1,26 @@ +from pathlib import Path +from testlib.fixtures.nix import Nix +from testlib.fixtures.file_helper import with_files +from testlib.utils import get_global_asset_pack + + +@with_files(get_global_asset_pack("simple-drv")) +def test_stdio_forward_failure(nix: Nix, files: Path): + result = ( + nix.nix_build( + [ + f"{files}/simple.nix", + "--builders", + # build-remote handles the first ssh-ng in-process, but we want to test how nix-daemon + # behaves in error cases. we need to explicitly set remote-program for the main remote + # connection since we may choose the wrong binary otherwise. the inner ssh-ng also has + # a remote-program set to cause predictable failures (rather than using ssh impurely). + f"ssh-ng://localhost?remote-program={nix.env.dirs.nix_bin_dir}/nix-daemon&remote-store=ssh-ng://localhost?remote-program=false", + "-j0", + ] + ) + .run() + .expect(1) + ) + assert "stream ended unexpectedly" in result.stderr_plain + assert "Lix crashed" not in result.stderr_plain