diff --git a/nix/libstore/gc.cc b/nix/libstore/gc.cc index 72eff52..db58603 100644 --- a/nix/libstore/gc.cc +++ b/nix/libstore/gc.cc @@ -545,6 +545,9 @@ void LocalStore::tryToDelete(GCState & state, const Path & path) } +/* Like 'dirent', but with just what we need. */ +typedef std::pair MiniDirEntry; + /* Unlink all files in /nix/store/.links that have a link count of 1, which indicates that there are no other links and so they can be safely deleted. FIXME: race condition with optimisePath(): we @@ -555,32 +558,57 @@ void LocalStore::removeUnusedLinks(const GCState & state) AutoCloseDir dir = opendir(linksDir.c_str()); if (!dir) throw SysError(format("opening directory `%1%'") % linksDir); + /* Maximum number of entries stored in memory; each 'MiniDirEntry' takes + ~80 bytes. */ + const size_t maxEntries = 100000; + long long actualSize = 0, unsharedSize = 0; - struct dirent * dirent; - while (errno = 0, dirent = readdir(dir)) { - checkInterrupt(); - string name = dirent->d_name; - if (name == "." || name == "..") continue; - Path path = linksDir + "/" + name; - - struct stat st; - if (lstat(path.c_str(), &st) == -1) - throw SysError(format("statting `%1%'") % path); - - if (st.st_nlink != 1) { - unsigned long long size = st.st_blocks * 512ULL; - actualSize += size; - unsharedSize += (st.st_nlink - 1) * size; - continue; - } - - printMsg(lvlTalkative, format("deleting unused link `%1%'") % path); - - if (unlink(path.c_str()) == -1) - throw SysError(format("deleting `%1%'") % path); - - state.results.bytesFreed += st.st_blocks * 512; + bool endOfDir = false; + + while (!endOfDir) { + /* Read as many entries as possible at once, up to 'maxEntries'. */ + std::list entries; + struct dirent * dirent; + while (errno = 0, + (entries.size() < maxEntries) && (dirent = readdir(dir))) { + checkInterrupt(); + string name = dirent->d_name; + if (name == "." || name == "..") continue; + entries.emplace_back(MiniDirEntry(name, dirent->d_ino)); + } + + endOfDir = (dirent == NULL); + + /* Sort entries by inode number to minimize disk seeks induced by the + following 'lstat' calls. */ + entries.sort([] (const MiniDirEntry& e1, const MiniDirEntry& e2) { + return e1.second < e2.second; + }); + + for (auto && item: entries) { + checkInterrupt(); + + Path path = linksDir + "/" + item.first; + + struct stat st; + if (lstat(path.c_str(), &st) == -1) + throw SysError(format("statting `%1%'") % path); + + if (st.st_nlink != 1) { + unsigned long long size = st.st_blocks * 512ULL; + actualSize += size; + unsharedSize += (st.st_nlink - 1) * size; + continue; + } + x+ printMsg(lvlTalkative, format("deleting unused link `%1%'") % path); + + if (unlink(path.c_str()) == -1) + throw SysError(format("deleting `%1%'") % path); + + state.results.bytesFreed += st.st_blocks * 512; + } } struct stat st;