Skip to content

Commit 5292b0e

Browse files
authored
Merge pull request #14038 from NixOS/thread-safe-dummy
libstore: Make writable dummy store thread-safe
2 parents 01357d0 + 5915fe3 commit 5292b0e

File tree

5 files changed

+149
-63
lines changed

5 files changed

+149
-63
lines changed

src/libstore/dummy-store.cc

Lines changed: 125 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
#include "nix/util/memory-source-accessor.hh"
55
#include "nix/store/dummy-store.hh"
66

7+
#include <boost/unordered/concurrent_flat_map.hpp>
8+
79
namespace nix {
810

911
std::string DummyStoreConfig::doc()
@@ -13,6 +15,99 @@ std::string DummyStoreConfig::doc()
1315
;
1416
}
1517

18+
namespace {
19+
20+
class WholeStoreViewAccessor : public SourceAccessor
21+
{
22+
using BaseName = std::string;
23+
24+
/**
25+
* Map from store path basenames to corresponding accessors.
26+
*/
27+
boost::concurrent_flat_map<BaseName, ref<MemorySourceAccessor>> subdirs;
28+
29+
/**
30+
* Helper accessor for accessing just the CanonPath::root.
31+
*/
32+
MemorySourceAccessor rootPathAccessor;
33+
34+
/**
35+
* Helper empty accessor.
36+
*/
37+
MemorySourceAccessor emptyAccessor;
38+
39+
auto
40+
callWithAccessorForPath(CanonPath path, std::invocable<MemorySourceAccessor &, const CanonPath &> auto callback)
41+
{
42+
if (path.isRoot())
43+
return callback(rootPathAccessor, path);
44+
45+
BaseName baseName(*path.begin());
46+
MemorySourceAccessor * res = nullptr;
47+
48+
subdirs.cvisit(baseName, [&](const auto & kv) {
49+
path = path.removePrefix(CanonPath{baseName});
50+
res = &*kv.second;
51+
});
52+
53+
if (!res)
54+
res = &emptyAccessor;
55+
56+
return callback(*res, path);
57+
}
58+
59+
public:
60+
WholeStoreViewAccessor()
61+
{
62+
MemorySink sink{rootPathAccessor};
63+
sink.createDirectory(CanonPath::root);
64+
}
65+
66+
void addObject(std::string_view baseName, ref<MemorySourceAccessor> accessor)
67+
{
68+
subdirs.emplace(baseName, std::move(accessor));
69+
}
70+
71+
std::string readFile(const CanonPath & path) override
72+
{
73+
return callWithAccessorForPath(
74+
path, [](SourceAccessor & accessor, const CanonPath & path) { return accessor.readFile(path); });
75+
}
76+
77+
void readFile(const CanonPath & path, Sink & sink, std::function<void(uint64_t)> sizeCallback) override
78+
{
79+
return callWithAccessorForPath(path, [&](SourceAccessor & accessor, const CanonPath & path) {
80+
return accessor.readFile(path, sink, sizeCallback);
81+
});
82+
}
83+
84+
bool pathExists(const CanonPath & path) override
85+
{
86+
return callWithAccessorForPath(
87+
path, [](SourceAccessor & accessor, const CanonPath & path) { return accessor.pathExists(path); });
88+
}
89+
90+
std::optional<Stat> maybeLstat(const CanonPath & path) override
91+
{
92+
return callWithAccessorForPath(
93+
path, [](SourceAccessor & accessor, const CanonPath & path) { return accessor.maybeLstat(path); });
94+
}
95+
96+
DirEntries readDirectory(const CanonPath & path) override
97+
{
98+
return callWithAccessorForPath(
99+
path, [](SourceAccessor & accessor, const CanonPath & path) { return accessor.readDirectory(path); });
100+
}
101+
102+
std::string readLink(const CanonPath & path) override
103+
{
104+
return callWithAccessorForPath(
105+
path, [](SourceAccessor & accessor, const CanonPath & path) { return accessor.readLink(path); });
106+
}
107+
};
108+
109+
} // namespace
110+
16111
struct DummyStore : virtual Store
17112
{
18113
using Config = DummyStoreConfig;
@@ -29,7 +124,7 @@ struct DummyStore : virtual Store
29124
* This is map conceptually owns the file system objects for each
30125
* store object.
31126
*/
32-
std::map<StorePath, PathInfoAndContents> contents;
127+
boost::concurrent_flat_map<StorePath, PathInfoAndContents> contents;
33128

34129
/**
35130
* This view conceptually just borrows the file systems objects of
@@ -38,23 +133,23 @@ struct DummyStore : virtual Store
38133
*
39134
* This is needed just in order to implement `Store::getFSAccessor`.
40135
*/
41-
ref<MemorySourceAccessor> wholeStoreView = make_ref<MemorySourceAccessor>();
136+
ref<WholeStoreViewAccessor> wholeStoreView = make_ref<WholeStoreViewAccessor>();
42137

43138
DummyStore(ref<const Config> config)
44139
: Store{*config}
45140
, config(config)
46141
{
47142
wholeStoreView->setPathDisplay(config->storeDir);
48-
MemorySink sink{*wholeStoreView};
49-
sink.createDirectory(CanonPath::root);
50143
}
51144

52145
void queryPathInfoUncached(
53146
const StorePath & path, Callback<std::shared_ptr<const ValidPathInfo>> callback) noexcept override
54147
{
55-
if (auto it = contents.find(path); it != contents.end())
56-
callback(std::make_shared<ValidPathInfo>(StorePath{path}, it->second.info));
57-
else
148+
bool visited = contents.cvisit(path, [&](const auto & kv) {
149+
callback(std::make_shared<ValidPathInfo>(StorePath{kv.first}, kv.second.info));
150+
});
151+
152+
if (!visited)
58153
callback(nullptr);
59154
}
60155

@@ -87,19 +182,14 @@ struct DummyStore : virtual Store
87182
parseDump(tempSink, source);
88183
auto path = info.path;
89184

90-
auto [it, _] = contents.insert({
91-
path,
92-
{
93-
std::move(info),
94-
make_ref<MemorySourceAccessor>(std::move(*temp)),
95-
},
96-
});
97-
98-
auto & pathAndContents = it->second;
99-
100-
bool inserted = wholeStoreView->open(CanonPath(path.to_string()), pathAndContents.contents->root);
101-
if (!inserted)
102-
unreachable();
185+
auto accessor = make_ref<MemorySourceAccessor>(std::move(*temp));
186+
contents.insert(
187+
{path,
188+
PathInfoAndContents{
189+
std::move(info),
190+
accessor,
191+
}});
192+
wholeStoreView->addObject(path.to_string(), accessor);
103193
}
104194

105195
StorePath addToStoreFromDump(
@@ -156,33 +246,28 @@ struct DummyStore : virtual Store
156246
info.narSize = narHash.second.value();
157247

158248
auto path = info.path;
159-
160-
auto [it, _] = contents.insert({
161-
path,
162-
{
163-
std::move(info),
164-
make_ref<MemorySourceAccessor>(std::move(*temp)),
165-
},
166-
});
167-
168-
auto & pathAndContents = it->second;
169-
170-
bool inserted = wholeStoreView->open(CanonPath(path.to_string()), pathAndContents.contents->root);
171-
if (!inserted)
172-
unreachable();
249+
auto accessor = make_ref<MemorySourceAccessor>(std::move(*temp));
250+
contents.insert(
251+
{path,
252+
PathInfoAndContents{
253+
std::move(info),
254+
accessor,
255+
}});
256+
wholeStoreView->addObject(path.to_string(), accessor);
173257

174258
return path;
175259
}
176260

177261
void narFromPath(const StorePath & path, Sink & sink) override
178262
{
179-
auto object = contents.find(path);
180-
if (object == contents.end())
181-
throw Error("path '%s' is not valid", printStorePath(path));
263+
bool visited = contents.cvisit(path, [&](const auto & kv) {
264+
const auto & [info, accessor] = kv.second;
265+
SourcePath sourcePath(accessor);
266+
dumpPath(sourcePath, sink, FileSerialisationMethod::NixArchive);
267+
});
182268

183-
const auto & [info, accessor] = object->second;
184-
SourcePath sourcePath(accessor);
185-
dumpPath(sourcePath, sink, FileSerialisationMethod::NixArchive);
269+
if (!visited)
270+
throw Error("path '%s' is not valid", printStorePath(path));
186271
}
187272

188273
void

src/libstore/include/nix/store/path.hh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,4 +108,13 @@ struct hash<nix::StorePath>
108108

109109
} // namespace std
110110

111+
namespace nix {
112+
113+
inline std::size_t hash_value(const StorePath & path)
114+
{
115+
return std::hash<StorePath>{}(path);
116+
}
117+
118+
} // namespace nix
119+
111120
JSON_IMPL(nix::StorePath)

src/libutil-tests/git.cc

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -233,30 +233,30 @@ TEST_F(GitTest, both_roundrip)
233233
.contents{
234234
{
235235
"foo",
236-
make_ref<File>(File::Regular{
236+
File::Regular{
237237
.contents = "hello\n\0\n\tworld!",
238-
}),
238+
},
239239
},
240240
{
241241
"bar",
242-
make_ref<File>(File::Directory{
242+
File::Directory{
243243
.contents =
244244
{
245245
{
246246
"baz",
247-
make_ref<File>(File::Regular{
247+
File::Regular{
248248
.executable = true,
249249
.contents = "good day,\n\0\n\tworld!",
250-
}),
250+
},
251251
},
252252
{
253253
"quux",
254-
make_ref<File>(File::Symlink{
254+
File::Symlink{
255255
.target = "/over/there",
256-
}),
256+
},
257257
},
258258
},
259-
}),
259+
},
260260
},
261261
},
262262
};

src/libutil/include/nix/util/memory-source-accessor.hh

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ struct MemorySourceAccessor : virtual SourceAccessor
3535
{
3636
using Name = std::string;
3737

38-
std::map<Name, ref<File>, std::less<>> contents;
38+
std::map<Name, File, std::less<>> contents;
3939

4040
bool operator==(const Directory &) const noexcept;
4141
// TODO libc++ 16 (used by darwin) missing `std::map::operator <=>`, can't do yet.
@@ -89,21 +89,13 @@ struct MemorySourceAccessor : virtual SourceAccessor
8989
SourcePath addFile(CanonPath path, std::string && contents);
9090
};
9191

92-
inline bool
93-
MemorySourceAccessor::File::Directory::operator==(const MemorySourceAccessor::File::Directory & other) const noexcept
94-
{
95-
return std::ranges::equal(contents, other.contents, [](const auto & lhs, const auto & rhs) -> bool {
96-
return lhs.first == rhs.first && *lhs.second == *rhs.second;
97-
});
98-
};
92+
inline bool MemorySourceAccessor::File::Directory::operator==(
93+
const MemorySourceAccessor::File::Directory &) const noexcept = default;
9994

10095
inline bool
10196
MemorySourceAccessor::File::Directory::operator<(const MemorySourceAccessor::File::Directory & other) const noexcept
10297
{
103-
return std::ranges::lexicographical_compare(
104-
contents, other.contents, [](const auto & lhs, const auto & rhs) -> bool {
105-
return lhs.first < rhs.first && *lhs.second < *rhs.second;
106-
});
98+
return contents < other.contents;
10799
}
108100

109101
inline bool MemorySourceAccessor::File::operator==(const MemorySourceAccessor::File &) const noexcept = default;

src/libutil/memory-source-accessor.cc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,11 @@ MemorySourceAccessor::File * MemorySourceAccessor::open(const CanonPath & path,
3939
i,
4040
{
4141
std::string{name},
42-
make_ref<File>(File::Directory{}),
42+
File::Directory{},
4343
});
4444
}
4545
}
46-
cur = &*i->second;
46+
cur = &i->second;
4747
}
4848

4949
if (newF && create)
@@ -107,7 +107,7 @@ MemorySourceAccessor::DirEntries MemorySourceAccessor::readDirectory(const Canon
107107
if (auto * d = std::get_if<File::Directory>(&f->raw)) {
108108
DirEntries res;
109109
for (auto & [name, file] : d->contents)
110-
res.insert_or_assign(name, file->lstat().type);
110+
res.insert_or_assign(name, file.lstat().type);
111111
return res;
112112
} else
113113
throw Error("file '%s' is not a directory", path);

0 commit comments

Comments
 (0)