Skip to content

Commit 536aa2b

Browse files
committed
[cas/libclang] Add libclang APIs for restricting the size of the local CAS directory
The new APIs allow the client to: * Get the current size * Set a size limit in bytes * Prune data from the directory The client decides how to derive the proper size limit. rdar://121129053
1 parent b1ebed5 commit 536aa2b

File tree

16 files changed

+399
-16
lines changed

16 files changed

+399
-16
lines changed

clang/include/clang-c/CAS.h

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "clang-c/CXString.h"
2525
#include "clang-c/Platform.h"
2626
#include <stdbool.h>
27+
#include <stdint.h>
2728

2829
#ifdef __cplusplus
2930
extern "C" {
@@ -118,6 +119,39 @@ clang_experimental_cas_Databases_create(CXCASOptions Opts, CXString *Error);
118119
*/
119120
CINDEX_LINKAGE void clang_experimental_cas_Databases_dispose(CXCASDatabases);
120121

122+
/**
123+
* Get the local storage size of the CAS/cache data in bytes.
124+
*
125+
* \param[out] OutError The error object to pass back to client (if any).
126+
* If non-null the object must be disposed using \c clang_Error_dispose.
127+
* \returns the local storage size of the CAS/cache data, or -1 if the
128+
* implementation does not support reporting such size, or -2 if an error
129+
* occurred.
130+
*/
131+
CINDEX_LINKAGE int64_t clang_experimental_cas_Databases_get_storage_size(
132+
CXCASDatabases, CXError *OutError);
133+
134+
/**
135+
* Set the size for limiting disk storage growth.
136+
*
137+
* \param size_limit the maximum size limit in bytes. 0 means no limit. Negative
138+
* values are invalid.
139+
* \returns an error object if there was an error, NULL otherwise.
140+
* If non-null the object must be disposed using \c clang_Error_dispose.
141+
*/
142+
CINDEX_LINKAGE CXError clang_experimental_cas_Databases_set_size_limit(
143+
CXCASDatabases, int64_t size_limit);
144+
145+
/**
146+
* Prune local storage to reduce its size according to the desired size limit.
147+
* Pruning can happen concurrently with other operations.
148+
*
149+
* \returns an error object if there was an error, NULL otherwise.
150+
* If non-null the object must be disposed using \c clang_Error_dispose.
151+
*/
152+
CINDEX_LINKAGE
153+
CXError clang_experimental_cas_Databases_prune_ondisk_data(CXCASDatabases);
154+
121155
/**
122156
* Loads an object using its printed \p CASID.
123157
*

clang/test/CAS/libclang-prune-data.c

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// REQUIRES: ondisk_cas
2+
3+
// Tests that the CAS directory storage can be limited via libclang APIs.
4+
// The test depends on internal details of the CAS directory structure.
5+
6+
// RUN: rm -rf %t && mkdir -p %t
7+
8+
// RUN: %clang -cc1depscan -fdepscan=inline -fdepscan-include-tree -o %t/t.rsp -cc1-args \
9+
// RUN: -cc1 -triple x86_64-apple-macos12 -fcas-path %t/cas -emit-obj %s -o %t/output.o
10+
// RUN: %clang @%t/t.rsp
11+
// RUN: ls %t/cas | wc -l | grep 2
12+
// RUN: ls %t/cas | grep v1.1
13+
14+
// Limit too high, no change.
15+
// RUN: c-index-test core -prune-cas -cas-path %t/cas 100000000
16+
// RUN: ls %t/cas | wc -l | grep 2
17+
18+
// Under the limit, starts a chain.
19+
// RUN: c-index-test core -prune-cas -cas-path %t/cas 10
20+
// RUN: ls %t/cas | wc -l | grep 3
21+
// RUN: ls %t/cas | grep v1.2
22+
23+
// Under the limit, starts a chain and abandons oldest dir.
24+
// RUN: c-index-test core -prune-cas -cas-path %t/cas 10
25+
// RUN: ls %t/cas | wc -l | grep 4
26+
// RUN: ls %t/cas | grep v1.3
27+
28+
// Under the limit, removes abandonded dir, starts a chain and abandons oldest dir.
29+
// RUN: c-index-test core -prune-cas -cas-path %t/cas 10
30+
// RUN: ls %t/cas | wc -l | grep 4
31+
// RUN: ls %t/cas | grep v1.4
32+
// RUN: ls %t/cas | grep -v v1.1
33+
34+
// Same test but using the plugin CAS.
35+
36+
// RUN: rm -rf %t/cas
37+
38+
// RUN: %clang -cc1depscan -fdepscan=inline -fdepscan-include-tree -o %t/t.rsp -cc1-args \
39+
// RUN: -cc1 -triple x86_64-apple-macos12 -fcas-path %t/cas -emit-obj %s -o %t/output.o \
40+
// RUN: -fcas-plugin-path %llvmshlibdir/libCASPluginTest%pluginext
41+
// RUN: %clang @%t/t.rsp
42+
// RUN: ls %t/cas | wc -l | grep 2
43+
// RUN: ls %t/cas | grep v1.1
44+
45+
// Limit too high, no change.
46+
// RUN: c-index-test core -prune-cas -cas-path %t/cas 100000000 -fcas-plugin-path %llvmshlibdir/libCASPluginTest%pluginext
47+
// RUN: ls %t/cas | wc -l | grep 2
48+
49+
// Under the limit, starts a chain.
50+
// RUN: c-index-test core -prune-cas -cas-path %t/cas 10 -fcas-plugin-path %llvmshlibdir/libCASPluginTest%pluginext
51+
// RUN: ls %t/cas | wc -l | grep 3
52+
// RUN: ls %t/cas | grep v1.2
53+
54+
// Under the limit, starts a chain and abandons oldest dir.
55+
// RUN: c-index-test core -prune-cas -cas-path %t/cas 10 -fcas-plugin-path %llvmshlibdir/libCASPluginTest%pluginext
56+
// RUN: ls %t/cas | wc -l | grep 4
57+
// RUN: ls %t/cas | grep v1.3
58+
59+
// Under the limit, removes abandonded dir, starts a chain and abandons oldest dir.
60+
// RUN: c-index-test core -prune-cas -cas-path %t/cas 10 -fcas-plugin-path %llvmshlibdir/libCASPluginTest%pluginext
61+
// RUN: ls %t/cas | wc -l | grep 4
62+
// RUN: ls %t/cas | grep v1.4
63+
// RUN: ls %t/cas | grep -v v1.1

clang/tools/c-index-test/core_main.cpp

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ enum class ActionType {
6060
ScanDepsByModuleName,
6161
MaterializeCachedJob,
6262
ReplayCachedJob,
63+
PruneCAS,
6364
WatchDir,
6465
};
6566

@@ -88,6 +89,7 @@ Action(cl::desc("Action:"), cl::init(ActionType::None),
8889
"Materialize cached compilation data from upstream CAS"),
8990
clEnumValN(ActionType::ReplayCachedJob, "replay-cached-job",
9091
"Replay a cached compilation from the CAS"),
92+
clEnumValN(ActionType::PruneCAS, "prune-cas", "Prune CAS data"),
9193
clEnumValN(ActionType::WatchDir,
9294
"watch-dir", "Watch directory for file events")),
9395
cl::cat(IndexTestCoreCategory));
@@ -990,6 +992,43 @@ static int replayCachedJob(ArrayRef<const char *> Args,
990992
return 0;
991993
}
992994

995+
static int pruneCAS(int64_t Limit, CXCASDatabases DBs) {
996+
CXError Err = nullptr;
997+
int64_t Size = clang_experimental_cas_Databases_get_storage_size(DBs, &Err);
998+
if (Size == -2) {
999+
llvm::errs() << "clang_experimental_cas_Databases_get_storage_size: "
1000+
<< clang_Error_getDescription(Err) << "\n";
1001+
clang_Error_dispose(Err);
1002+
return 1;
1003+
}
1004+
if (Size == -1) {
1005+
llvm::errs()
1006+
<< "unsupported clang_experimental_cas_Databases_get_storage_size";
1007+
return 1;
1008+
}
1009+
if (Size == 0) {
1010+
llvm::errs()
1011+
<< "clang_experimental_cas_Databases_get_storage_size returned 0";
1012+
return 1;
1013+
}
1014+
1015+
if (CXError Err =
1016+
clang_experimental_cas_Databases_set_size_limit(DBs, Limit)) {
1017+
llvm::errs() << "clang_experimental_cas_Databases_set_size_limit: "
1018+
<< clang_Error_getDescription(Err) << "\n";
1019+
clang_Error_dispose(Err);
1020+
return 1;
1021+
}
1022+
if (CXError Err = clang_experimental_cas_Databases_prune_ondisk_data(DBs)) {
1023+
llvm::errs() << "clang_experimental_cas_Databases_prune_ondisk_data: "
1024+
<< clang_Error_getDescription(Err) << "\n";
1025+
clang_Error_dispose(Err);
1026+
return 1;
1027+
}
1028+
1029+
return 0;
1030+
}
1031+
9931032
static void printSymbol(const IndexRecordDecl &Rec, raw_ostream &OS) {
9941033
printSymbolInfo(Rec.SymInfo, OS);
9951034
OS << " | ";
@@ -1379,6 +1418,23 @@ int indextest_core_main(int argc, const char **argv) {
13791418
options::InputFiles[0], DBs);
13801419
}
13811420

1421+
if (options::Action == ActionType::PruneCAS) {
1422+
if (options::InputFiles.empty()) {
1423+
errs() << "error: missing size limit\n";
1424+
return 1;
1425+
}
1426+
int64_t Limit;
1427+
if (StringRef(options::InputFiles[0]).getAsInteger(10, Limit)) {
1428+
errs() << "error: size limit not an integer\n";
1429+
return 1;
1430+
}
1431+
if (!DBs) {
1432+
errs() << "error: CAS was not configured\n";
1433+
return 1;
1434+
}
1435+
return pruneCAS(Limit, DBs);
1436+
}
1437+
13821438
if (options::Action == ActionType::WatchDir) {
13831439
if (options::InputFiles.empty()) {
13841440
errs() << "error: missing directory path\n";

clang/tools/libclang/CCAS.cpp

Lines changed: 62 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,20 @@ DEFINE_SIMPLE_CONVERSION_FUNCTIONS(WrappedReplayResult, CXCASReplayResult)
5858

5959
} // anonymous namespace
6060

61+
static void passAsCXError(Error &&E, CXError *OutError) {
62+
if (OutError)
63+
*OutError = cxerror::create(std::move(E));
64+
else
65+
llvm::consumeError(std::move(E));
66+
}
67+
6168
CXCASCachedCompilation WrappedCachedCompilation::fromResultID(
6269
Expected<std::optional<CASID>> ResultID, CASID CacheKey,
6370
const std::shared_ptr<llvm::cas::ObjectStore> &CAS,
6471
const std::shared_ptr<llvm::cas::ActionCache> &AC, CXError *OutError) {
6572

6673
auto failure = [OutError](Error &&E) -> CXCASCachedCompilation {
67-
if (OutError)
68-
*OutError = cxerror::create(std::move(E));
74+
passAsCXError(std::move(E), OutError);
6975
return nullptr;
7076
};
7177

@@ -139,6 +145,57 @@ void clang_experimental_cas_Databases_dispose(CXCASDatabases CDBs) {
139145
delete unwrap(CDBs);
140146
}
141147

148+
int64_t clang_experimental_cas_Databases_get_storage_size(CXCASDatabases CDBs,
149+
CXError *OutError) {
150+
// Commonly used ObjectStore implementations (on-disk and plugin) combine a
151+
// CAS and action-cache into a single directory managing the storage
152+
// holistically for both, so calling the ObjectStore API is sufficient.
153+
// FIXME: For completeness we should figure out how to deal with potential
154+
// implementations that use separate directories for CAS and action-cache.
155+
std::optional<uint64_t> Size;
156+
if (Error E = unwrap(CDBs)->CAS->getStorageSize().moveInto(Size)) {
157+
passAsCXError(std::move(E), OutError);
158+
return -2;
159+
}
160+
if (!Size)
161+
return -1;
162+
return *Size;
163+
}
164+
165+
CXError clang_experimental_cas_Databases_set_size_limit(CXCASDatabases CDBs,
166+
int64_t size_limit) {
167+
// Commonly used ObjectStore implementations (on-disk and plugin) combine a
168+
// CAS and action-cache into a single directory managing the storage
169+
// holistically for both, so calling the ObjectStore API is sufficient.
170+
// FIXME: For completeness we should figure out how to deal with potential
171+
// implementations that use separate directories for CAS and action-cache.
172+
std::optional<uint64_t> SizeLimit;
173+
if (size_limit < 0) {
174+
return cxerror::create(llvm::createStringError(
175+
llvm::inconvertibleErrorCode(),
176+
"invalid size limit passed to "
177+
"clang_experimental_cas_Databases_set_size_limit"));
178+
}
179+
if (size_limit > 0) {
180+
SizeLimit = size_limit;
181+
}
182+
if (Error E = unwrap(CDBs)->CAS->setSizeLimit(SizeLimit))
183+
return cxerror::create(std::move(E));
184+
return nullptr;
185+
}
186+
187+
CXError
188+
clang_experimental_cas_Databases_prune_ondisk_data(CXCASDatabases CDBs) {
189+
// Commonly used ObjectStore implementations (on-disk and plugin) combine a
190+
// CAS and action-cache into a single directory managing the storage
191+
// holistically for both, so calling the ObjectStore API is sufficient.
192+
// FIXME: For completeness we should figure out how to deal with potential
193+
// implementations that use separate directories for CAS and action-cache.
194+
if (Error E = unwrap(CDBs)->CAS->pruneStorageData())
195+
return cxerror::create(std::move(E));
196+
return nullptr;
197+
}
198+
142199
CXCASObject clang_experimental_cas_loadObjectByString(CXCASDatabases CDBs,
143200
const char *PrintedID,
144201
CXError *OutError) {
@@ -149,8 +206,7 @@ CXCASObject clang_experimental_cas_loadObjectByString(CXCASDatabases CDBs,
149206
*OutError = nullptr;
150207

151208
auto failure = [OutError](Error &&E) -> CXCASObject {
152-
if (OutError)
153-
*OutError = cxerror::create(std::move(E));
209+
passAsCXError(std::move(E), OutError);
154210
return nullptr;
155211
};
156212

@@ -311,8 +367,7 @@ clang_experimental_cas_getCachedCompilation(CXCASDatabases CDBs,
311367
*OutError = nullptr;
312368

313369
auto failure = [OutError](Error &&E) -> CXCASCachedCompilation {
314-
if (OutError)
315-
*OutError = cxerror::create(std::move(E));
370+
passAsCXError(std::move(E), OutError);
316371
return nullptr;
317372
};
318373

@@ -441,10 +496,7 @@ CXCASReplayResult clang_experimental_cas_replayCompilation(
441496
std::move(Invok), WorkingDirectory, WComp.CacheKey,
442497
WComp.CachedResult, DiagText)
443498
.moveInto(Ret)) {
444-
if (OutError)
445-
*OutError = cxerror::create(std::move(E));
446-
else
447-
llvm::consumeError(std::move(E));
499+
passAsCXError(std::move(E), OutError);
448500
return nullptr;
449501
}
450502

clang/tools/libclang/libclang.map

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,9 @@ LLVM_16 {
486486
clang_experimental_cas_CASObject_dispose;
487487
clang_experimental_cas_Databases_create;
488488
clang_experimental_cas_Databases_dispose;
489+
clang_experimental_cas_Databases_get_storage_size;
490+
clang_experimental_cas_Databases_prune_ondisk_data;
491+
clang_experimental_cas_Databases_set_size_limit;
489492
clang_experimental_cas_getCachedCompilation;
490493
clang_experimental_cas_getCachedCompilation_async;
491494
clang_experimental_cas_loadObjectByString;

llvm/include/llvm-c/CAS/PluginAPI_functions.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,39 @@ LLCAS_PUBLIC llcas_cas_t llcas_cas_create(llcas_cas_options_t, char **error);
9898
*/
9999
LLCAS_PUBLIC void llcas_cas_dispose(llcas_cas_t);
100100

101+
/**
102+
* Get the local storage size of the CAS/cache data in bytes.
103+
*
104+
* \param error optional pointer to receive an error message if an error
105+
* occurred. If set, the memory it points to needs to be released via
106+
* \c llcas_string_dispose.
107+
* \returns the local storage size of the CAS/cache data, or -1 if the
108+
* implementation does not support reporting such size, or -2 if an error
109+
* occurred.
110+
*/
111+
LLCAS_PUBLIC int64_t llcas_cas_get_ondisk_size(llcas_cas_t, char **error);
112+
113+
/**
114+
* Set the size for limiting disk storage growth.
115+
*
116+
* \param size_limit the maximum size limit in bytes. 0 means no limit. Negative
117+
* values are invalid.
118+
* \param error optional pointer to receive an error message if an error
119+
* occurred. If set, the memory it points to needs to be released via
120+
* \c llcas_string_dispose.
121+
* \returns true if there was an error, false otherwise.
122+
*/
123+
LLCAS_PUBLIC bool
124+
llcas_cas_set_ondisk_size_limit(llcas_cas_t, int64_t size_limit, char **error);
125+
126+
/**
127+
* Prune local storage to reduce its size according to the desired size limit.
128+
* Pruning can happen concurrently with other operations.
129+
*
130+
* \returns true if there was an error, false otherwise.
131+
*/
132+
LLCAS_PUBLIC bool llcas_cas_prune_ondisk_data(llcas_cas_t, char **error);
133+
101134
/**
102135
* \returns the hash schema name that the plugin is using. The string memory it
103136
* points to needs to be released via \c llcas_string_dispose.

llvm/include/llvm/CAS/ObjectStore.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,28 @@ class ObjectStore {
272272
return Data.size();
273273
}
274274

275+
/// Set the size for limiting growth of on-disk storage. This has an effect
276+
/// for when the instance is closed.
277+
///
278+
/// Implementations may be not have this implemented.
279+
virtual Error setSizeLimit(std::optional<uint64_t> SizeLimit) {
280+
return Error::success();
281+
}
282+
283+
/// \returns the storage size of the on-disk CAS data.
284+
///
285+
/// Implementations that don't have an implementation for this should return
286+
/// \p std::nullopt.
287+
virtual Expected<std::optional<uint64_t>> getStorageSize() const {
288+
return std::nullopt;
289+
}
290+
291+
/// Prune local storage to reduce its size according to the desired size
292+
/// limit. Pruning can happen concurrently with other operations.
293+
///
294+
/// Implementations may be not have this implemented.
295+
virtual Error pruneStorageData() { return Error::success(); }
296+
275297
/// Validate the whole node tree.
276298
Error validateTree(ObjectRef Ref);
277299

0 commit comments

Comments
 (0)