mirror of
https://github.com/NixOS/nix.git
synced 2025-11-15 15:02:42 +01:00
libstore: Make writable dummy store thread-safe
Tested by building with b_sanitize=thread and running: nix flake prefetch-inputs --store "dummy://?read-only=false" It might make sense to move this utility class out of dummy-store.cc, but it seems fine for now.
This commit is contained in:
parent
c71f80b6eb
commit
c4c92c4c61
1 changed files with 125 additions and 40 deletions
|
|
@ -4,6 +4,8 @@
|
||||||
#include "nix/util/memory-source-accessor.hh"
|
#include "nix/util/memory-source-accessor.hh"
|
||||||
#include "nix/store/dummy-store.hh"
|
#include "nix/store/dummy-store.hh"
|
||||||
|
|
||||||
|
#include <boost/unordered/concurrent_flat_map.hpp>
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
std::string DummyStoreConfig::doc()
|
std::string DummyStoreConfig::doc()
|
||||||
|
|
@ -13,6 +15,99 @@ std::string DummyStoreConfig::doc()
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class WholeStoreViewAccessor : public SourceAccessor
|
||||||
|
{
|
||||||
|
using BaseName = std::string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map from store path basenames to corresponding accessors.
|
||||||
|
*/
|
||||||
|
boost::concurrent_flat_map<BaseName, ref<MemorySourceAccessor>> subdirs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper accessor for accessing just the CanonPath::root.
|
||||||
|
*/
|
||||||
|
MemorySourceAccessor rootPathAccessor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper empty accessor.
|
||||||
|
*/
|
||||||
|
MemorySourceAccessor emptyAccessor;
|
||||||
|
|
||||||
|
auto
|
||||||
|
callWithAccessorForPath(CanonPath path, std::invocable<MemorySourceAccessor &, const CanonPath &> auto callback)
|
||||||
|
{
|
||||||
|
if (path.isRoot())
|
||||||
|
return callback(rootPathAccessor, path);
|
||||||
|
|
||||||
|
BaseName baseName(*path.begin());
|
||||||
|
MemorySourceAccessor * res = nullptr;
|
||||||
|
|
||||||
|
subdirs.cvisit(baseName, [&](const auto & kv) {
|
||||||
|
path = path.removePrefix(CanonPath{baseName});
|
||||||
|
res = &*kv.second;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res)
|
||||||
|
res = &emptyAccessor;
|
||||||
|
|
||||||
|
return callback(*res, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
WholeStoreViewAccessor()
|
||||||
|
{
|
||||||
|
MemorySink sink{rootPathAccessor};
|
||||||
|
sink.createDirectory(CanonPath::root);
|
||||||
|
}
|
||||||
|
|
||||||
|
void addObject(std::string_view baseName, ref<MemorySourceAccessor> accessor)
|
||||||
|
{
|
||||||
|
subdirs.emplace(baseName, std::move(accessor));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string readFile(const CanonPath & path) override
|
||||||
|
{
|
||||||
|
return callWithAccessorForPath(
|
||||||
|
path, [](SourceAccessor & accessor, const CanonPath & path) { return accessor.readFile(path); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void readFile(const CanonPath & path, Sink & sink, std::function<void(uint64_t)> sizeCallback) override
|
||||||
|
{
|
||||||
|
return callWithAccessorForPath(path, [&](SourceAccessor & accessor, const CanonPath & path) {
|
||||||
|
return accessor.readFile(path, sink, sizeCallback);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool pathExists(const CanonPath & path) override
|
||||||
|
{
|
||||||
|
return callWithAccessorForPath(
|
||||||
|
path, [](SourceAccessor & accessor, const CanonPath & path) { return accessor.pathExists(path); });
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<Stat> maybeLstat(const CanonPath & path) override
|
||||||
|
{
|
||||||
|
return callWithAccessorForPath(
|
||||||
|
path, [](SourceAccessor & accessor, const CanonPath & path) { return accessor.maybeLstat(path); });
|
||||||
|
}
|
||||||
|
|
||||||
|
DirEntries readDirectory(const CanonPath & path) override
|
||||||
|
{
|
||||||
|
return callWithAccessorForPath(
|
||||||
|
path, [](SourceAccessor & accessor, const CanonPath & path) { return accessor.readDirectory(path); });
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string readLink(const CanonPath & path) override
|
||||||
|
{
|
||||||
|
return callWithAccessorForPath(
|
||||||
|
path, [](SourceAccessor & accessor, const CanonPath & path) { return accessor.readLink(path); });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
struct DummyStore : virtual Store
|
struct DummyStore : virtual Store
|
||||||
{
|
{
|
||||||
using Config = DummyStoreConfig;
|
using Config = DummyStoreConfig;
|
||||||
|
|
@ -29,7 +124,7 @@ struct DummyStore : virtual Store
|
||||||
* This is map conceptually owns the file system objects for each
|
* This is map conceptually owns the file system objects for each
|
||||||
* store object.
|
* store object.
|
||||||
*/
|
*/
|
||||||
std::map<StorePath, PathInfoAndContents> contents;
|
boost::concurrent_flat_map<StorePath, PathInfoAndContents> contents;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This view conceptually just borrows the file systems objects of
|
* This view conceptually just borrows the file systems objects of
|
||||||
|
|
@ -38,23 +133,23 @@ struct DummyStore : virtual Store
|
||||||
*
|
*
|
||||||
* This is needed just in order to implement `Store::getFSAccessor`.
|
* This is needed just in order to implement `Store::getFSAccessor`.
|
||||||
*/
|
*/
|
||||||
ref<MemorySourceAccessor> wholeStoreView = make_ref<MemorySourceAccessor>();
|
ref<WholeStoreViewAccessor> wholeStoreView = make_ref<WholeStoreViewAccessor>();
|
||||||
|
|
||||||
DummyStore(ref<const Config> config)
|
DummyStore(ref<const Config> config)
|
||||||
: Store{*config}
|
: Store{*config}
|
||||||
, config(config)
|
, config(config)
|
||||||
{
|
{
|
||||||
wholeStoreView->setPathDisplay(config->storeDir);
|
wholeStoreView->setPathDisplay(config->storeDir);
|
||||||
MemorySink sink{*wholeStoreView};
|
|
||||||
sink.createDirectory(CanonPath::root);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void queryPathInfoUncached(
|
void queryPathInfoUncached(
|
||||||
const StorePath & path, Callback<std::shared_ptr<const ValidPathInfo>> callback) noexcept override
|
const StorePath & path, Callback<std::shared_ptr<const ValidPathInfo>> callback) noexcept override
|
||||||
{
|
{
|
||||||
if (auto it = contents.find(path); it != contents.end())
|
bool visited = contents.cvisit(path, [&](const auto & kv) {
|
||||||
callback(std::make_shared<ValidPathInfo>(StorePath{path}, it->second.info));
|
callback(std::make_shared<ValidPathInfo>(StorePath{kv.first}, kv.second.info));
|
||||||
else
|
});
|
||||||
|
|
||||||
|
if (!visited)
|
||||||
callback(nullptr);
|
callback(nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -87,19 +182,14 @@ struct DummyStore : virtual Store
|
||||||
parseDump(tempSink, source);
|
parseDump(tempSink, source);
|
||||||
auto path = info.path;
|
auto path = info.path;
|
||||||
|
|
||||||
auto [it, _] = contents.insert({
|
auto accessor = make_ref<MemorySourceAccessor>(std::move(*temp));
|
||||||
path,
|
contents.insert(
|
||||||
{
|
{path,
|
||||||
std::move(info),
|
PathInfoAndContents{
|
||||||
make_ref<MemorySourceAccessor>(std::move(*temp)),
|
std::move(info),
|
||||||
},
|
accessor,
|
||||||
});
|
}});
|
||||||
|
wholeStoreView->addObject(path.to_string(), accessor);
|
||||||
auto & pathAndContents = it->second;
|
|
||||||
|
|
||||||
bool inserted = wholeStoreView->open(CanonPath(path.to_string()), pathAndContents.contents->root);
|
|
||||||
if (!inserted)
|
|
||||||
unreachable();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StorePath addToStoreFromDump(
|
StorePath addToStoreFromDump(
|
||||||
|
|
@ -156,33 +246,28 @@ struct DummyStore : virtual Store
|
||||||
info.narSize = narHash.second.value();
|
info.narSize = narHash.second.value();
|
||||||
|
|
||||||
auto path = info.path;
|
auto path = info.path;
|
||||||
|
auto accessor = make_ref<MemorySourceAccessor>(std::move(*temp));
|
||||||
auto [it, _] = contents.insert({
|
contents.insert(
|
||||||
path,
|
{path,
|
||||||
{
|
PathInfoAndContents{
|
||||||
std::move(info),
|
std::move(info),
|
||||||
make_ref<MemorySourceAccessor>(std::move(*temp)),
|
accessor,
|
||||||
},
|
}});
|
||||||
});
|
wholeStoreView->addObject(path.to_string(), accessor);
|
||||||
|
|
||||||
auto & pathAndContents = it->second;
|
|
||||||
|
|
||||||
bool inserted = wholeStoreView->open(CanonPath(path.to_string()), pathAndContents.contents->root);
|
|
||||||
if (!inserted)
|
|
||||||
unreachable();
|
|
||||||
|
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
void narFromPath(const StorePath & path, Sink & sink) override
|
void narFromPath(const StorePath & path, Sink & sink) override
|
||||||
{
|
{
|
||||||
auto object = contents.find(path);
|
bool visited = contents.cvisit(path, [&](const auto & kv) {
|
||||||
if (object == contents.end())
|
const auto & [info, accessor] = kv.second;
|
||||||
throw Error("path '%s' is not valid", printStorePath(path));
|
SourcePath sourcePath(accessor);
|
||||||
|
dumpPath(sourcePath, sink, FileSerialisationMethod::NixArchive);
|
||||||
|
});
|
||||||
|
|
||||||
const auto & [info, accessor] = object->second;
|
if (!visited)
|
||||||
SourcePath sourcePath(accessor);
|
throw Error("path '%s' is not valid", printStorePath(path));
|
||||||
dumpPath(sourcePath, sink, FileSerialisationMethod::NixArchive);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue