Make LocalStore::optimisePath_() non-recursive#16025
Conversation
|
Hm, this change by itself is probably good. Though this does make me wonder whether we should increase the stack size - dumpPath by itself is also recursive. The depth should already be bounded though, so maybe it's just a matter of a constant factor change? |
|
How much memory per stack frame are we using? Surely not more than a single page? |
|
This is probably exacerbated by drainFD using 64Kb for a buffer on the stack, which leaves very little room otherwise. We definitely should increase the coroutine stack size. As for the stack frame size: Which is just 1272 bytes. And for Which is also just 376 bytes. So, 64 Kb buffer on the stack in Sounds like bumping to something like 512KiB would leave us in the clear. |
|
Yeah, I also created a DetSys PR to make |
I encountered this failure during a test: $ nix-instantiate --store /tmp/nix-gc-test '<nixpkgs>' -A hello --add-root x --auto-optimise-store error: stack overflow (possible infinite recursion) This turned out to be due to the recursion in optimisePath_() when called from a coroutine (which has a small stack): #0 0x00007ffff7e3e45d in nix::drainFD (fd=0, sink=..., opts=...) at ../src/libutil/file-descriptor.cc:131 #1 0x00007ffff7ee6369 in nix::PosixSourceAccessor::readFile (this=0x55555617e2d0, path=..., sink=..., sizeCallback=...) at ../src/libutil/posix-source-accessor.cc:66 #2 0x00007ffff7d38ad5 in nix::SourceAccessor::dumpPath(nix::CanonPath const&, nix::Sink&, nix::fun<bool (std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)>&)::$_0::operator()(nix::CanonPath const&) const (this=0x7fffa7d64978, path=...) at ../src/libutil/archive.cc:48 #3 0x00007ffff7d36efc in nix::SourceAccessor::dumpPath(nix::CanonPath const&, nix::Sink&, nix::fun<bool (std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)>&)::$_1::operator()<$_1>(this $_1 const&, nix::CanonPath const&, unsigned long) (dump=..., path=..., depth=0) at ../src/libutil/archive.cc:72 #4 0x00007ffff7d36bec in nix::SourceAccessor::dumpPath (this=0x55555617e310, path=..., sink=..., filter=...) at ../src/libutil/archive.cc:58 #5 0x00007ffff7f1e3fa in nix::SourcePath::dumpPath (this=0x7fffa7d65f90, sink=..., filter=...) at ../src/libutil/source-path.cc:49 #6 0x00007ffff7e3cb85 in nix::dumpPath (path=..., sink=..., method=nix::FileSerialisationMethod::NixArchive, filter=...) at ../src/libutil/file-content-address.cc:73 #7 0x00007ffff7e3ccbd in nix::hashPath (path=..., method=nix::FileSerialisationMethod::NixArchive, ha=nix::HashAlgorithm::SHA256, filter=...) at ../src/libutil/file-content-address.cc:93 #8 0x00007ffff763f02f in nix::LocalStore::optimisePath_ (this=0x555555eaeb00, act=0x0, stats=..., path=Python Exception <class 'gdb.error'>: No type named std::filesystem::__cxx11::path::_Cmpt. filesystem::path "/tmp/nix-gc-test/nix/store/9figz32vbiblaw5x3li1hc8q9qwlwf27-source/pkgs/desktops/pantheon/libraries/granite/7/default.nix", inodeHash=..., repair=nix::NoRepair) at ../src/libstore/optimise-store.cc:162 #9 0x00007ffff763e70b in nix::LocalStore::optimisePath_ (this=0x555555eaeb00, act=0x0, stats=..., path=Python Exception <class 'gdb.error'>: No type named std::filesystem::__cxx11::path::_Cmpt. filesystem::path "/tmp/nix-gc-test/nix/store/9figz32vbiblaw5x3li1hc8q9qwlwf27-source/pkgs/desktops/pantheon/libraries/granite/7", inodeHash=..., repair=nix::NoRepair) at ../src/libstore/optimise-store.cc:125 ... #25 0x00007ffff7f058ad in nix::sourceToSink(nix::fun<void (nix::Source&)>)::SourceToSink::operator()(std::basic_string_view<char, std::char_traits<char> >)::{lambda(boost::coroutines2::detail::pull_coroutine<bool>&)#1}::operator()(boost::coroutines2::detail::pull_coroutine<bool>&) const (this=0x7fffa7d73c98, yield=...) at ../src/libutil/serialise.cc:361 ... #32 0x00007ffff3af918f in make_fcontext () from /nix/store/5xkyx9gi1s8mqp1ki1hsx8hwpr4dncxk-boost-1.87.0/lib/libboost_context.so.1.87.0 So let's make optimisePath_() non-recursive to ensure constant stack space. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
560377d to
d44773c
Compare
This was using too much stack space in coroutines.
|
@xokdvium Thanks, added a commit to make |
|
Why not just increase the coroutine stack size? That seems like the least error-prone solution - recursive algorithms are easier to read to maintain. And
I feel like there has been a bit of a jo-jo changing from heap/stack/VLA and back historically. We probably really don't want to allocate a fresh vector for each 64KiB for each file copy. |
Motivation
Note: this is best reviewed by ignoring whitespace changes.
I encountered this failure during a test:
This turned out to be due to the recursion in
optimisePath_()when called from a coroutine (which has a small stack):So let's make
optimisePath_()non-recursive to ensure constant stack space.Context
Taken from DeterminateSystems#508.
Add 👍 to pull requests you find important.
The Nix maintainer team uses a GitHub project board to schedule and track reviews.