From a2f4129d332dd8eb63519f1b9d95e7aa63b67bb5 Mon Sep 17 00:00:00 2001 From: Argyrios Kyrtzidis Date: Thu, 4 Apr 2024 11:32:09 -0700 Subject: [PATCH] [clang][cas] Implement cancellation functionality for the asynchronous CAS query APIs (cherry picked from commit b88f023ce56614aa06d524cd00ada4c376b2a021) --- clang/test/CAS/libclang-replay-job.c | 17 +- clang/tools/c-index-test/core_main.cpp | 226 +++++++++++++----- clang/tools/libclang/CCAS.cpp | 127 +++++++--- llvm/include/llvm-c/CAS/PluginAPI_functions.h | 33 ++- llvm/include/llvm-c/CAS/PluginAPI_types.h | 1 + llvm/include/llvm/CAS/ActionCache.h | 24 +- llvm/include/llvm/CAS/CASID.h | 8 + llvm/include/llvm/CAS/ObjectStore.h | 11 +- llvm/lib/CAS/ActionCache.cpp | 6 +- llvm/lib/CAS/ObjectStore.cpp | 20 +- llvm/lib/CAS/PluginAPI.h | 13 +- llvm/lib/CAS/PluginAPI_functions.def | 2 + llvm/lib/CAS/PluginCAS.cpp | 77 ++++-- .../libCASPluginTest/libCASPluginTest.cpp | 87 ++++++- .../libCASPluginTest/libCASPluginTest.exports | 2 + 15 files changed, 500 insertions(+), 154 deletions(-) diff --git a/clang/test/CAS/libclang-replay-job.c b/clang/test/CAS/libclang-replay-job.c index 0bb859477e376..fc79b88bed2da 100644 --- a/clang/test/CAS/libclang-replay-job.c +++ b/clang/test/CAS/libclang-replay-job.c @@ -7,7 +7,7 @@ // RUN: clang-scan-deps -compilation-database %t/cdb.json \ // RUN: -format experimental-include-tree-full \ // RUN: -cas-path %t/cas -fcas-plugin-path %llvmshlibdir/libCASPluginTest%pluginext \ -// RUN: -fcas-plugin-option upstream-path=%t/cas-upstream -fcas-plugin-option no-logging \ +// RUN: -fcas-plugin-option no-logging \ // RUN: > %t/deps.json // RUN: %deps-to-rsp %t/deps.json --tu-index 0 > %t/cc1.rsp @@ -24,6 +24,12 @@ // RUN: -e "s/^.*hit for '//" \ // RUN: -e "s/' .*$//" > %t/cache-key +// RUN: c-index-test core -upload-cached-job -cas-path %t/cas @%t/cache-key -test-cas-cancellation \ +// RUN: -fcas-plugin-path %llvmshlibdir/libCASPluginTest%pluginext \ +// RUN: -fcas-plugin-option upstream-path=%t/cas-upstream \ +// RUN: 2>&1 | FileCheck %s --check-prefix=UPLOAD-CANCEL +// UPLOAD-CANCEL: actioncache_put_for_digest_async cancelled + // Delete the "local" cache and use the "upstream" one to re-materialize the outputs locally. // RUN: rm -rf %t/cas @@ -31,14 +37,17 @@ // RUN: clang-scan-deps -compilation-database %t/cdb.json \ // RUN: -format experimental-include-tree-full \ // RUN: -cas-path %t/cas -fcas-plugin-path %llvmshlibdir/libCASPluginTest%pluginext \ -// RUN: -fcas-plugin-option upstream-path=%t/cas-upstream -fcas-plugin-option no-logging \ +// RUN: -fcas-plugin-option no-logging \ // RUN: > %t/deps2.json // RUN: diff -u %t/deps.json %t/deps2.json -// RUN: c-index-test core -materialize-cached-job -cas-path %t/cas @%t/cache-key \ +// RUN: c-index-test core -materialize-cached-job -cas-path %t/cas @%t/cache-key -test-cas-cancellation \ // RUN: -fcas-plugin-path %llvmshlibdir/libCASPluginTest%pluginext \ -// RUN: -fcas-plugin-option upstream-path=%t/cas-upstream -fcas-plugin-option no-logging +// RUN: -fcas-plugin-option upstream-path=%t/cas-upstream \ +// RUN: 2>&1 | FileCheck %s --check-prefix=MATERIALIZE-CANCEL +// MATERIALIZE-CANCEL: actioncache_get_for_digest_async cancelled +// MATERIALIZE-CANCEL: load_object_async cancelled // RUN: c-index-test core -replay-cached-job -cas-path %t/cas @%t/cache-key \ // RUN: -fcas-plugin-path %llvmshlibdir/libCASPluginTest%pluginext \ diff --git a/clang/tools/c-index-test/core_main.cpp b/clang/tools/c-index-test/core_main.cpp index ca4bb25f67a5d..3da08314899cd 100644 --- a/clang/tools/c-index-test/core_main.cpp +++ b/clang/tools/c-index-test/core_main.cpp @@ -58,6 +58,7 @@ enum class ActionType { AggregateAsJSON, ScanDeps, ScanDepsByModuleName, + UploadCachedJob, MaterializeCachedJob, ReplayCachedJob, PruneCAS, @@ -85,6 +86,8 @@ Action(cl::desc("Action:"), cl::init(ActionType::None), "Get file dependencies"), clEnumValN(ActionType::ScanDepsByModuleName, "scan-deps-by-mod-name", "Get file dependencies by module name alone"), + clEnumValN(ActionType::UploadCachedJob, "upload-cached-job", + "Upload cached compilation data to upstream CAS"), clEnumValN(ActionType::MaterializeCachedJob, "materialize-cached-job", "Materialize cached compilation data from upstream CAS"), clEnumValN(ActionType::ReplayCachedJob, "replay-cached-job", @@ -154,6 +157,10 @@ static cl::list CASPluginOpts("fcas-plugin-option", cl::desc("Plugin CAS Options")); static llvm::cl::opt WorkingDir("working-dir", llvm::cl::desc("Path for working directory")); +static cl::opt TestCASCancellation( + "test-cas-cancellation", + cl::desc( + "perform extra CAS API invocation and cancel it for testing purposes")); } } // anonymous namespace @@ -865,41 +872,117 @@ static int scanDeps(ArrayRef Args, std::string WorkingDirectory, return 1; } -static int materializeCachedJob(std::string CacheKey, CXCASDatabases DBs) { - struct CompResult { - CXCASCachedCompilation Comp = nullptr; - CXError Err = nullptr; +static int uploadCachedJob(std::string CacheKey, CXCASDatabases DBs) { + CXError Err = nullptr; + CXCASCachedCompilation CComp = clang_experimental_cas_getCachedCompilation( + DBs, CacheKey.c_str(), /*Globally*/ false, &Err); + auto CleanupCachedComp = llvm::make_scope_exit( + [&] { clang_experimental_cas_CachedCompilation_dispose(CComp); }); + if (!CComp) { + if (Err) { + llvm::errs() << clang_Error_getDescription(Err) << "\n"; + clang_Error_dispose(Err); + } else { + llvm::errs() << "cache key was not found\n"; + } + return 1; + } + + /// \returns true of an error occurred. + auto invokeMakeGlobal = [&](bool Cancel) -> bool { + CXCASCancellationToken CancelToken = nullptr; + auto CleanupCancelTok = llvm::make_scope_exit([&] { + if (Cancel) + clang_experimental_cas_CancellationToken_dispose(CancelToken); + }); + + std::promise CallPromise; + clang_experimental_cas_CachedCompilation_makeGlobal( + CComp, &CallPromise, + [](void *Ctx, CXError Err) { + static_cast *>(Ctx)->set_value(Err); + }, + Cancel ? &CancelToken : nullptr); + if (Cancel) { + clang_experimental_cas_CancellationToken_cancel(CancelToken); + } + CXError CallRes = CallPromise.get_future().get(); + if (CallRes) { + llvm::errs() << clang_Error_getDescription(CallRes) << "\n"; + return true; + } + return false; }; - std::promise CompPromise; - auto CompFuture = CompPromise.get_future(); - struct CompCall { - std::promise Promise; + + if (options::TestCASCancellation) { + // Cancel an invocation for testing purposes. + if (invokeMakeGlobal(/*Cancel=*/true)) + return 1; + } + if (invokeMakeGlobal(/*Cancel=*/false)) + return 1; + + return 0; +} + +static int materializeCachedJob(std::string CacheKey, CXCASDatabases DBs) { + /// \returns true of an error occurred. + auto invokeGetCachedCompilation = + [&](bool Cancel, CXCASCachedCompilation &OutComp) -> bool { + OutComp = nullptr; + CXCASCancellationToken CancelToken = nullptr; + auto CleanupCancelTok = llvm::make_scope_exit([&] { + if (Cancel) + clang_experimental_cas_CancellationToken_dispose(CancelToken); + }); + + struct CompResult { + CXCASCachedCompilation Comp = nullptr; + CXError Err = nullptr; + }; + std::promise CompPromise; + auto CompFuture = CompPromise.get_future(); + struct CompCall { + std::promise Promise; + }; + CompCall *CallCtx = new CompCall{std::move(CompPromise)}; + clang_experimental_cas_getCachedCompilation_async( + DBs, CacheKey.c_str(), /*Globally*/ true, CallCtx, + [](void *Ctx, CXCASCachedCompilation Comp, CXError Err) { + std::unique_ptr CallCtx(static_cast(Ctx)); + CallCtx->Promise.set_value(CompResult{Comp, Err}); + }, + Cancel ? &CancelToken : nullptr); + if (Cancel) { + clang_experimental_cas_CancellationToken_cancel(CancelToken); + } + CompResult Res = CompFuture.get(); + OutComp = Res.Comp; + if (!OutComp && !Cancel) { + if (Res.Err) { + llvm::errs() << clang_Error_getDescription(Res.Err) << "\n"; + clang_Error_dispose(Res.Err); + } else { + llvm::errs() << "cache key was not found\n"; + } + return true; + } + return false; }; - CompCall *CallCtx = new CompCall{std::move(CompPromise)}; - clang_experimental_cas_getCachedCompilation_async( - DBs, CacheKey.c_str(), /*Globally*/ true, CallCtx, - [](void *Ctx, CXCASCachedCompilation Comp, CXError Err) { - std::unique_ptr CallCtx(static_cast(Ctx)); - CallCtx->Promise.set_value(CompResult{Comp, Err}); - }, - /*cancelToken*/ nullptr); - CompResult Res = CompFuture.get(); - CXCASCachedCompilation CComp = Res.Comp; + CXCASCachedCompilation CComp = nullptr; auto CleanupCachedComp = llvm::make_scope_exit([&] { if (CComp) clang_experimental_cas_CachedCompilation_dispose(CComp); - if (Res.Err) - clang_Error_dispose(Res.Err); }); - if (!CComp) { - if (Res.Err) { - llvm::errs() << clang_Error_getDescription(Res.Err) << "\n"; - } else { - llvm::errs() << "cache key was not found\n"; - } - return 1; + + if (options::TestCASCancellation) { + // Cancel an invocation for testing purposes. + if (invokeGetCachedCompilation(/*Cancel=*/true, CComp)) + return 1; } + if (invokeGetCachedCompilation(/*Cancel=*/false, CComp)) + return 1; for (unsigned I = 0, @@ -912,42 +995,63 @@ static int materializeCachedJob(std::string CacheKey, CXCASDatabases DBs) { auto CleanupOutputID = llvm::make_scope_exit([&] { clang_disposeString(OutputID); }); - struct LoadResult { - CXCASObject Obj = nullptr; - CXError Err = nullptr; - }; - std::promise LoadPromise; - auto LoadFuture = LoadPromise.get_future(); - struct LoadCall { - std::promise Promise; - }; - LoadCall *CallCtx = new LoadCall{std::move(LoadPromise)}; - clang_experimental_cas_loadObjectByString_async( - DBs, clang_getCString(OutputID), CallCtx, - [](void *Ctx, CXCASObject Obj, CXError Err) { - std::unique_ptr CallCtx(static_cast(Ctx)); - CallCtx->Promise.set_value(LoadResult{Obj, Err}); - }, - /*cancelToken*/ nullptr); + /// \returns true of an error occurred. + auto invokeLoadObject = [&](bool Cancel, CXCASObject &OutObj) -> bool { + OutObj = nullptr; + CXCASCancellationToken CancelToken = nullptr; + auto CleanupCancelTok = llvm::make_scope_exit([&] { + if (Cancel) + clang_experimental_cas_CancellationToken_dispose(CancelToken); + }); - LoadResult Res = LoadFuture.get(); - CXCASObject CASObj = Res.Obj; + struct LoadResult { + CXCASObject Obj = nullptr; + CXError Err = nullptr; + }; + std::promise LoadPromise; + auto LoadFuture = LoadPromise.get_future(); + struct LoadCall { + std::promise Promise; + }; + LoadCall *CallCtx = new LoadCall{std::move(LoadPromise)}; + clang_experimental_cas_loadObjectByString_async( + DBs, clang_getCString(OutputID), CallCtx, + [](void *Ctx, CXCASObject Obj, CXError Err) { + std::unique_ptr CallCtx(static_cast(Ctx)); + CallCtx->Promise.set_value(LoadResult{Obj, Err}); + }, + Cancel ? &CancelToken : nullptr); + if (Cancel) { + clang_experimental_cas_CancellationToken_cancel(CancelToken); + } + LoadResult Res = LoadFuture.get(); + OutObj = Res.Obj; + if (!OutObj && !Cancel) { + if (Res.Err) { + llvm::errs() << clang_Error_getDescription(Res.Err) << "\n"; + clang_Error_dispose(Res.Err); + } else { + llvm::errs() << "cache key was not found\n"; + } + return true; + } + return false; + }; + CXCASObject CASObj = nullptr; auto CleanupLoadObj = llvm::make_scope_exit([&] { if (CASObj) clang_experimental_cas_CASObject_dispose(CASObj); - if (Res.Err) - clang_Error_dispose(Res.Err); }); - if (!CASObj) { - if (Res.Err) { - llvm::errs() << clang_Error_getDescription(Res.Err) << "\n"; - } else { - llvm::errs() << "compilation output ID was not found\n"; - } - return 1; + if (options::TestCASCancellation) { + // Cancel an invocation for testing purposes. + if (invokeLoadObject(/*Cancel=*/true, CASObj)) + return 1; } + if (invokeLoadObject(/*Cancel=*/false, CASObj)) + return 1; + if (!clang_experimental_cas_CachedCompilation_isOutputMaterialized(CComp, I)) report_fatal_error("output was not materialized?"); @@ -1393,6 +1497,18 @@ int indextest_core_main(int argc, const char **argv) { options::OutputDir, DBs, options::ModuleName); } + if (options::Action == ActionType::UploadCachedJob) { + if (options::InputFiles.empty()) { + errs() << "error: missing cache key\n"; + return 1; + } + if (!DBs) { + errs() << "error: CAS was not configured\n"; + return 1; + } + return uploadCachedJob(options::InputFiles[0], DBs); + } + if (options::Action == ActionType::MaterializeCachedJob) { if (options::InputFiles.empty()) { errs() << "error: missing cache key\n"; diff --git a/clang/tools/libclang/CCAS.cpp b/clang/tools/libclang/CCAS.cpp index 763261791dbc2..ccb3f206a45e1 100644 --- a/clang/tools/libclang/CCAS.cpp +++ b/clang/tools/libclang/CCAS.cpp @@ -51,10 +51,16 @@ struct WrappedReplayResult { SmallString<256> DiagText; }; +struct WrappedCancellationToken { + std::unique_ptr CancelTok; +}; + DEFINE_SIMPLE_CONVERSION_FUNCTIONS(WrappedCASObject, CXCASObject) DEFINE_SIMPLE_CONVERSION_FUNCTIONS(WrappedCachedCompilation, CXCASCachedCompilation) DEFINE_SIMPLE_CONVERSION_FUNCTIONS(WrappedReplayResult, CXCASReplayResult) +DEFINE_SIMPLE_CONVERSION_FUNCTIONS(WrappedCancellationToken, + CXCASCancellationToken) } // anonymous namespace @@ -265,8 +271,9 @@ void clang_experimental_cas_loadObjectByString_async( /// Asynchronously visits the graph of the object node to ensure it's fully /// materialized. - class AsyncObjectLoader - : public std::enable_shared_from_this { + class AsyncObjectLoader final + : public llvm::cas::Cancellable, + public std::enable_shared_from_this { void *Ctx; void (*Callback)(void *Ctx, CXCASObject, CXError); std::shared_ptr CAS; @@ -277,29 +284,40 @@ void clang_experimental_cas_loadObjectByString_async( std::atomic MissingNode{false}; /// The first error that occurred. std::optional ErrOccurred; + + const bool MayCancel; + bool Cancelled = false; + llvm::SmallDenseMap> + PendingCancellables; + std::mutex Mutex; public: AsyncObjectLoader(void *Ctx, void (*Callback)(void *Ctx, CXCASObject, CXError), - std::shared_ptr CAS) - : Ctx(Ctx), Callback(Callback), CAS(std::move(CAS)) {} + std::shared_ptr CAS, bool MayCancel) + : Ctx(Ctx), Callback(Callback), CAS(std::move(CAS)), + MayCancel(MayCancel) {} void visit(ObjectRef Ref, bool IsRootNode) { - bool Inserted; { std::lock_guard Guard(Mutex); - Inserted = ObjectsSeen.insert(Ref).second; - if (Inserted) - ++NumPending; - } - if (!Inserted) { - finishedNode(); - return; + if (Cancelled) + return; + bool Inserted = ObjectsSeen.insert(Ref).second; + if (!Inserted) + return; + ++NumPending; } auto This = shared_from_this(); + std::unique_ptr CancelObj; CAS->getProxyAsync( - Ref, [This, IsRootNode](Expected> Obj) { + Ref, + [This, IsRootNode, Ref](Expected> Obj) { + if (This->MayCancel) { + std::lock_guard Guard(This->Mutex); + This->PendingCancellables.erase(Ref); + } auto _1 = llvm::make_scope_exit([&]() { This->finishedNode(); }); if (!Obj) { This->encounteredError(Obj.takeError()); @@ -315,7 +333,12 @@ void clang_experimental_cas_loadObjectByString_async( This->visit(Sub, /*IsRootNode*/ false); return Error::success(); })); - }); + }, + MayCancel ? &CancelObj : nullptr); + if (CancelObj) { + std::lock_guard Guard(This->Mutex); + PendingCancellables[Ref] = std::move(CancelObj); + } } void finishedNode() { @@ -347,9 +370,30 @@ void clang_experimental_cas_loadObjectByString_async( } ErrOccurred = std::move(E); } + + void cancel() override { + std::lock_guard Guard(Mutex); + Cancelled = true; + for (const auto &I : PendingCancellables) + I.second->cancel(); + PendingCancellables.clear(); + } }; - auto WL = std::make_shared(Ctx, Callback, DBs.CAS); + auto WL = std::make_shared( + Ctx, Callback, DBs.CAS, /*MayCancel=*/OutToken != nullptr); + if (OutToken) { + // Using a wrapper since \c WrappedCancellationToken expects a + // \c std::unique_ptr. + struct ObjectLoaderWrapper : public llvm::cas::Cancellable { + std::shared_ptr ObjLoader; + ObjectLoaderWrapper(std::shared_ptr ObjLoader) + : ObjLoader(std::move(ObjLoader)) {} + void cancel() override { ObjLoader->cancel(); } + }; + *OutToken = wrap(new WrappedCancellationToken{ + std::make_unique(WL)}); + } WL->visit(*Ref, /*IsRootNode*/ true); } @@ -391,16 +435,21 @@ void clang_experimental_cas_getCachedCompilation_async( if (!KeyID) return Callback(Ctx, nullptr, cxerror::create(KeyID.takeError())); - DBs.Cache->getAsync(*KeyID, Globally, - [KeyID = *KeyID, CAS = DBs.CAS, AC = DBs.Cache, Ctx, - Callback](Expected> ResultID) { - CXError Err = nullptr; - CXCASCachedCompilation CComp = - WrappedCachedCompilation::fromResultID( - std::move(ResultID), std::move(KeyID), - std::move(CAS), std::move(AC), &Err); - Callback(Ctx, CComp, Err); - }); + std::unique_ptr CancelObj; + DBs.Cache->getAsync( + *KeyID, Globally, + [KeyID = *KeyID, CAS = DBs.CAS, AC = DBs.Cache, Ctx, + Callback](Expected> ResultID) { + CXError Err = nullptr; + CXCASCachedCompilation CComp = WrappedCachedCompilation::fromResultID( + std::move(ResultID), std::move(KeyID), std::move(CAS), + std::move(AC), &Err); + Callback(Ctx, CComp, Err); + }, + OutToken != nullptr ? &CancelObj : nullptr); + if (OutToken && CancelObj) { + *OutToken = wrap(new WrappedCancellationToken{std::move(CancelObj)}); + } } void clang_experimental_cas_CachedCompilation_dispose( @@ -450,10 +499,16 @@ void clang_experimental_cas_CachedCompilation_makeGlobal( *OutToken = nullptr; WrappedCachedCompilation &WComp = *unwrap(CComp); CompileJobCacheResult &CacheResult = WComp.CachedResult; - WComp.AC->putAsync(WComp.CacheKey, CacheResult.getID(), /*Globally=*/true, - [Ctx, Callback](Error E) { - Callback(Ctx, cxerror::create(std::move(E))); - }); + std::unique_ptr CancelObj; + WComp.AC->putAsync( + WComp.CacheKey, CacheResult.getID(), /*Globally=*/true, + [Ctx, Callback](Error E) { + Callback(Ctx, cxerror::create(std::move(E))); + }, + OutToken != nullptr ? &CancelObj : nullptr); + if (OutToken && CancelObj) { + *OutToken = wrap(new WrappedCancellationToken{std::move(CancelObj)}); + } } CXCASReplayResult clang_experimental_cas_replayCompilation( @@ -516,12 +571,18 @@ CXString clang_experimental_cas_ReplayResult_getStderr(CXCASReplayResult CRR) { return cxstring::createDup(unwrap(CRR)->DiagText); } -void clang_experimental_cas_CancellationToken_cancel(CXCASCancellationToken) { - // FIXME: Implement. +void clang_experimental_cas_CancellationToken_cancel( + CXCASCancellationToken CCT) { + if (!CCT) + return; + unwrap(CCT)->CancelTok->cancel(); } -void clang_experimental_cas_CancellationToken_dispose(CXCASCancellationToken) { - // FIXME: Implement. +void clang_experimental_cas_CancellationToken_dispose( + CXCASCancellationToken CCT) { + if (!CCT) + return; + delete unwrap(CCT); } void clang_experimental_cas_ObjectStore_dispose(CXCASObjectStore CAS) { diff --git a/llvm/include/llvm-c/CAS/PluginAPI_functions.h b/llvm/include/llvm-c/CAS/PluginAPI_functions.h index f6ce65dd44470..a8d1ff7f3370f 100644 --- a/llvm/include/llvm-c/CAS/PluginAPI_functions.h +++ b/llvm/include/llvm-c/CAS/PluginAPI_functions.h @@ -41,6 +41,16 @@ LLCAS_PUBLIC void llcas_get_plugin_version(unsigned *major, unsigned *minor); */ LLCAS_PUBLIC void llcas_string_dispose(char *); +/** + * Cancels the asynchronous query associated with the \c llcas_cancellable_t. + */ +LLCAS_PUBLIC void llcas_cancellable_cancel(llcas_cancellable_t); + +/** + * Releases memory associated with given \c llcas_cancellable_t. + */ +LLCAS_PUBLIC void llcas_cancellable_dispose(llcas_cancellable_t); + /** * Options object to configure creation of \c llcas_cas_t. After passing to * \c llcas_cas_create, its memory can be released via @@ -227,10 +237,13 @@ LLCAS_PUBLIC llcas_lookup_result_t llcas_cas_load_object( * Whether the call is asynchronous or not depends on the implementation. * * \param ctx_cb pointer to pass to the callback function. + * + * \param[out] cancel_tok optional pointer to receive a \c llcas_cancellable_t. */ LLCAS_PUBLIC void llcas_cas_load_object_async(llcas_cas_t, llcas_objectid_t, void *ctx_cb, - llcas_cas_load_object_cb); + llcas_cas_load_object_cb, + llcas_cancellable_t *cancel_tok); /** * Stores the object with the provided data buffer and \c llcas_objectid_t @@ -300,11 +313,12 @@ LLCAS_PUBLIC llcas_lookup_result_t llcas_actioncache_get_for_digest( * implementation. * * \param ctx_cb pointer to pass to the callback function. + * + * \param[out] cancel_tok optional pointer to receive a \c llcas_cancellable_t. */ -LLCAS_PUBLIC void -llcas_actioncache_get_for_digest_async(llcas_cas_t, llcas_digest_t key, - bool globally, void *ctx_cb, - llcas_actioncache_get_cb); +LLCAS_PUBLIC void llcas_actioncache_get_for_digest_async( + llcas_cas_t, llcas_digest_t key, bool globally, void *ctx_cb, + llcas_actioncache_get_cb, llcas_cancellable_t *cancel_tok); /** * Associates a \c llcas_objectid_t \p value with a \p key. It is invalid to set @@ -329,11 +343,12 @@ LLCAS_PUBLIC bool llcas_actioncache_put_for_digest(llcas_cas_t, * implementation. * * \param ctx_cb pointer to pass to the callback function. + * + * \param[out] cancel_tok optional pointer to receive a \c llcas_cancellable_t. */ -LLCAS_PUBLIC void -llcas_actioncache_put_for_digest_async(llcas_cas_t, llcas_digest_t key, - llcas_objectid_t value, bool globally, - void *ctx_cb, llcas_actioncache_put_cb); +LLCAS_PUBLIC void llcas_actioncache_put_for_digest_async( + llcas_cas_t, llcas_digest_t key, llcas_objectid_t value, bool globally, + void *ctx_cb, llcas_actioncache_put_cb, llcas_cancellable_t *cancel_tok); LLVM_C_EXTERN_C_END diff --git a/llvm/include/llvm-c/CAS/PluginAPI_types.h b/llvm/include/llvm-c/CAS/PluginAPI_types.h index fdade74fcebcc..f006cca97bc28 100644 --- a/llvm/include/llvm-c/CAS/PluginAPI_types.h +++ b/llvm/include/llvm-c/CAS/PluginAPI_types.h @@ -24,6 +24,7 @@ typedef struct llcas_cas_options_s *llcas_cas_options_t; typedef struct llcas_cas_s *llcas_cas_t; +typedef struct llcas_cancellable_s *llcas_cancellable_t; /** * Digest hash bytes. diff --git a/llvm/include/llvm/CAS/ActionCache.h b/llvm/include/llvm/CAS/ActionCache.h index 134c586fa0a9a..f548fb0522480 100644 --- a/llvm/include/llvm/CAS/ActionCache.h +++ b/llvm/include/llvm/CAS/ActionCache.h @@ -77,11 +77,11 @@ class ActionCache { bool Globally = false) const; /// Asynchronous version of \c get. - void getAsync( - const CacheKey &ActionKey, bool Globally, - unique_function>)> Callback) const { + void getAsync(const CacheKey &ActionKey, bool Globally, + unique_function>)> Callback, + std::unique_ptr *CancelObj = nullptr) const { return getImplAsync(arrayRefFromStringRef(ActionKey.getKey()), Globally, - std::move(Callback)); + std::move(Callback), CancelObj); } /// Cache \p Result for the \p ActionKey computation. @@ -103,13 +103,15 @@ class ActionCache { bool Globally = false); /// Asynchronous version of \c put. + /// \param[out] CancelObj Optional pointer to receive a cancellation object. void putAsync(const CacheKey &ActionKey, const CASID &Result, bool Globally, - unique_function Callback) { + unique_function Callback, + std::unique_ptr *CancelObj = nullptr) { assert(Result.getContext().getHashSchemaIdentifier() == getContext().getHashSchemaIdentifier() && "Hash schema mismatch"); return putImplAsync(arrayRefFromStringRef(ActionKey.getKey()), Result, - Globally, std::move(Callback)); + Globally, std::move(Callback), CancelObj); } virtual ~ActionCache() = default; @@ -117,15 +119,17 @@ class ActionCache { protected: virtual Expected> getImpl(ArrayRef ResolvedKey, bool Globally) const = 0; - virtual void getImplAsync( - ArrayRef ResolvedKey, bool Globally, - unique_function>)> Callback) const; + virtual void + getImplAsync(ArrayRef ResolvedKey, bool Globally, + unique_function>)> Callback, + std::unique_ptr *CancelObj) const; virtual Error putImpl(ArrayRef ResolvedKey, const CASID &Result, bool Globally) = 0; virtual void putImplAsync(ArrayRef ResolvedKey, const CASID &Result, bool Globally, - unique_function Callback); + unique_function Callback, + std::unique_ptr *CancelObj); ActionCache(const CASContext &Context) : Context(Context) {} diff --git a/llvm/include/llvm/CAS/CASID.h b/llvm/include/llvm/CAS/CASID.h index 96aff766fb15c..fcac19aae9911 100644 --- a/llvm/include/llvm/CAS/CASID.h +++ b/llvm/include/llvm/CAS/CASID.h @@ -137,6 +137,14 @@ template struct AsyncValue { Expected> Value; }; +class Cancellable { + virtual void anchor(); + +public: + virtual ~Cancellable() {} + virtual void cancel() = 0; +}; + } // namespace cas template <> struct DenseMapInfo { diff --git a/llvm/include/llvm/CAS/ObjectStore.h b/llvm/include/llvm/CAS/ObjectStore.h index 70372692eb829..f30bdf98f9786 100644 --- a/llvm/include/llvm/CAS/ObjectStore.h +++ b/llvm/include/llvm/CAS/ObjectStore.h @@ -166,9 +166,11 @@ class ObjectStore { virtual Expected> loadIfExists(ObjectRef Ref) = 0; /// Asynchronous version of \c loadIfExists. + /// \param[out] CancelObj Optional pointer to receive a cancellation object. virtual void loadIfExistsAsync( ObjectRef Ref, - unique_function>)> Callback); + unique_function>)> Callback, + std::unique_ptr *CancelObj); /// Like \c loadIfExists but returns an error if the object is missing. Expected load(ObjectRef Ref); @@ -258,13 +260,16 @@ class ObjectStore { std::future getProxyFuture(ObjectRef Ref); /// Asynchronous version of \c getProxyIfExists using a callback. + /// \param[out] CancelObj Optional pointer to receive a cancellation object. void getProxyAsync( const CASID &ID, - unique_function>)> Callback); + unique_function>)> Callback, + std::unique_ptr *CancelObj = nullptr); /// Asynchronous version of \c getProxyIfExists using a callback. void getProxyAsync( ObjectRef Ref, - unique_function>)> Callback); + unique_function>)> Callback, + std::unique_ptr *CancelObj = nullptr); /// Read the data from \p Data into \p OS. uint64_t readData(ObjectHandle Node, raw_ostream &OS, uint64_t Offset = 0, diff --git a/llvm/lib/CAS/ActionCache.cpp b/llvm/lib/CAS/ActionCache.cpp index ded1fc4879fc0..ddddef04a27cc 100644 --- a/llvm/lib/CAS/ActionCache.cpp +++ b/llvm/lib/CAS/ActionCache.cpp @@ -47,14 +47,16 @@ std::future ActionCache::putFuture(const CacheKey &ActionKey, void ActionCache::getImplAsync( ArrayRef ResolvedKey, bool Globally, - unique_function>)> Callback) const { + unique_function>)> Callback, + std::unique_ptr *) const { // The default implementation is synchronous. return Callback(getImpl(ResolvedKey, Globally)); } void ActionCache::putImplAsync(ArrayRef ResolvedKey, const CASID &Result, bool Globally, - unique_function Callback) { + unique_function Callback, + std::unique_ptr *) { // The default implementation is synchronous. return Callback(putImpl(ResolvedKey, Result, Globally)); } diff --git a/llvm/lib/CAS/ObjectStore.cpp b/llvm/lib/CAS/ObjectStore.cpp index 27cd837d22c9f..4d48f331230fa 100644 --- a/llvm/lib/CAS/ObjectStore.cpp +++ b/llvm/lib/CAS/ObjectStore.cpp @@ -23,6 +23,7 @@ using namespace llvm::cas; void CASContext::anchor() {} void ObjectStore::anchor() {} +void Cancellable::anchor() {} LLVM_DUMP_METHOD void CASID::dump() const { print(dbgs()); } LLVM_DUMP_METHOD void ObjectStore::dump() const { print(dbgs()); } @@ -60,7 +61,8 @@ void ReferenceBase::print(raw_ostream &OS, const ObjectRef &This) const { void ObjectStore::loadIfExistsAsync( ObjectRef Ref, - unique_function>)> Callback) { + unique_function>)> Callback, + std::unique_ptr *CancelObj) { // The default implementation is synchronous. Callback(loadIfExists(Ref)); } @@ -129,30 +131,34 @@ std::future ObjectStore::getProxyFuture(ObjectRef Ref) { void ObjectStore::getProxyAsync( const CASID &ID, - unique_function>)> Callback) { + unique_function>)> Callback, + std::unique_ptr *CancelObj) { std::optional Ref = getReference(ID); if (!Ref) return Callback(createUnknownObjectError(ID)); - return getProxyAsync(*Ref, std::move(Callback)); + return getProxyAsync(*Ref, std::move(Callback), CancelObj); } void ObjectStore::getProxyAsync( ObjectRef Ref, - unique_function>)> Callback) { + unique_function>)> Callback, + std::unique_ptr *CancelObj) { // FIXME: there is potential for use-after-free for the 'this' pointer. // Either we should always allocate shared pointers for \c ObjectStore objects // and pass \c shared_from_this() or expect that the caller will not release // the \c ObjectStore before the callback returns. return loadIfExistsAsync( - Ref, [this, Ref, Callback = std::move(Callback)]( - Expected> H) mutable { + Ref, + [this, Ref, Callback = std::move(Callback)]( + Expected> H) mutable { if (!H) Callback(H.takeError()); else if (!*H) Callback(std::nullopt); else Callback(ObjectProxy::load(*this, Ref, **H)); - }); + }, + CancelObj); } Error ObjectStore::createUnknownObjectError(const CASID &ID) { diff --git a/llvm/lib/CAS/PluginAPI.h b/llvm/lib/CAS/PluginAPI.h index d691ece6b1e95..2d2c5cdf0468c 100644 --- a/llvm/lib/CAS/PluginAPI.h +++ b/llvm/lib/CAS/PluginAPI.h @@ -18,6 +18,10 @@ struct llcas_functions_t { void (*string_dispose)(char *); + void (*cancellable_cancel)(llcas_cancellable_t); + + void (*cancellable_dispose)(llcas_cancellable_t); + llcas_cas_options_t (*cas_options_create)(void); void (*cas_options_dispose)(llcas_cas_options_t); @@ -61,7 +65,8 @@ struct llcas_functions_t { llcas_loaded_object_t *, char **error); void (*cas_load_object_async)(llcas_cas_t, llcas_objectid_t, void *ctx_cb, - llcas_cas_load_object_cb); + llcas_cas_load_object_cb, + llcas_cancellable_t *); bool (*cas_store_object)(llcas_cas_t, llcas_data_t, const llcas_objectid_t *refs, size_t refs_count, @@ -89,7 +94,8 @@ struct llcas_functions_t { void (*actioncache_get_for_digest_async)(llcas_cas_t, llcas_digest_t key, bool globally, void *ctx_cb, - llcas_actioncache_get_cb); + llcas_actioncache_get_cb, + llcas_cancellable_t *); bool (*actioncache_put_for_digest)(llcas_cas_t, llcas_digest_t key, llcas_objectid_t value, bool globally, @@ -98,7 +104,8 @@ struct llcas_functions_t { void (*actioncache_put_for_digest_async)(llcas_cas_t, llcas_digest_t key, llcas_objectid_t value, bool globally, void *ctx_cb, - llcas_actioncache_put_cb); + llcas_actioncache_put_cb, + llcas_cancellable_t *); }; #endif // LLVM_LIB_CAS_PLUGINAPI_H diff --git a/llvm/lib/CAS/PluginAPI_functions.def b/llvm/lib/CAS/PluginAPI_functions.def index 6da6f7473bb6a..60cd25b4c47d9 100644 --- a/llvm/lib/CAS/PluginAPI_functions.def +++ b/llvm/lib/CAS/PluginAPI_functions.def @@ -7,6 +7,8 @@ CASPLUGINAPI_FUNCTION(actioncache_get_for_digest, true) CASPLUGINAPI_FUNCTION(actioncache_get_for_digest_async, true) CASPLUGINAPI_FUNCTION(actioncache_put_for_digest, true) CASPLUGINAPI_FUNCTION(actioncache_put_for_digest_async, true) +CASPLUGINAPI_FUNCTION(cancellable_cancel, false) +CASPLUGINAPI_FUNCTION(cancellable_dispose, false) CASPLUGINAPI_FUNCTION(cas_contains_object, true) CASPLUGINAPI_FUNCTION(cas_create, true) CASPLUGINAPI_FUNCTION(cas_dispose, true) diff --git a/llvm/lib/CAS/PluginCAS.cpp b/llvm/lib/CAS/PluginCAS.cpp index fe90bddb917a4..477b50a219f2a 100644 --- a/llvm/lib/CAS/PluginCAS.cpp +++ b/llvm/lib/CAS/PluginCAS.cpp @@ -132,10 +132,10 @@ class PluginObjectStore std::optional getReference(const CASID &ID) const final; Expected isMaterialized(ObjectRef Ref) const final; Expected> loadIfExists(ObjectRef Ref) final; - void - loadIfExistsAsync(ObjectRef Ref, - unique_function>)> - Callback) final; + void loadIfExistsAsync( + ObjectRef Ref, + unique_function>)> Callback, + std::unique_ptr *CancelObj) final; uint64_t getDataSize(ObjectHandle Node) const final; Error forEachRef(ObjectHandle Node, function_ref Callback) const final; @@ -157,6 +157,19 @@ class PluginObjectStore std::shared_ptr Ctx; }; +class PluginCancellable final : public Cancellable { + std::shared_ptr Ctx; + llcas_cancellable_t cancel_tok; + +public: + PluginCancellable(std::shared_ptr Ctx, + llcas_cancellable_t cancel_tok) + : Ctx(std::move(Ctx)), cancel_tok(cancel_tok) {} + + ~PluginCancellable() { Ctx->Functions.cancellable_dispose(cancel_tok); } + void cancel() override { Ctx->Functions.cancellable_cancel(cancel_tok); } +}; + } // anonymous namespace Expected PluginObjectStore::parseID(StringRef ID) { @@ -265,7 +278,8 @@ PluginObjectStore::loadIfExists(ObjectRef Ref) { void PluginObjectStore::loadIfExistsAsync( ObjectRef Ref, - unique_function>)> Callback) { + unique_function>)> Callback, + std::unique_ptr *CancelObj) { llcas_objectid_t c_id{Ref.getInternalRef(*this)}; struct LoadObjCtx { @@ -298,7 +312,15 @@ void PluginObjectStore::loadIfExistsAsync( }; LoadObjCtx *CallCtx = new LoadObjCtx(shared_from_this(), std::move(Callback)); - Ctx->Functions.cas_load_object_async(Ctx->c_cas, c_id, CallCtx, LoadObjCB); + if (CancelObj && Ctx->Functions.cancellable_cancel) { + llcas_cancellable_t cancel_tok = nullptr; + Ctx->Functions.cas_load_object_async(Ctx->c_cas, c_id, CallCtx, LoadObjCB, + &cancel_tok); + *CancelObj = std::make_unique(Ctx, cancel_tok); + } else { + Ctx->Functions.cas_load_object_async(Ctx->c_cas, c_id, CallCtx, LoadObjCB, + nullptr); + } } namespace { @@ -413,14 +435,16 @@ class PluginActionCache : public ActionCache { public: Expected> getImpl(ArrayRef ResolvedKey, bool Globally) const final; - void getImplAsync(ArrayRef ResolvedKey, bool Globally, - unique_function>)> - Callback) const final; + void + getImplAsync(ArrayRef ResolvedKey, bool Globally, + unique_function>)> Callback, + std::unique_ptr *CancelObj) const final; Error putImpl(ArrayRef ResolvedKey, const CASID &Result, bool Globally) final; void putImplAsync(ArrayRef ResolvedKey, const CASID &Result, - bool Globally, unique_function Callback) final; + bool Globally, unique_function Callback, + std::unique_ptr *CancelObj) final; PluginActionCache(std::shared_ptr); @@ -452,7 +476,8 @@ PluginActionCache::getImpl(ArrayRef ResolvedKey, bool Globally) const { void PluginActionCache::getImplAsync( ArrayRef ResolvedKey, bool Globally, - unique_function>)> Callback) const { + unique_function>)> Callback, + std::unique_ptr *CancelObj) const { struct CacheGetCtx { std::shared_ptr CASCtx; @@ -482,9 +507,16 @@ void PluginActionCache::getImplAsync( }; CacheGetCtx *CallCtx = new CacheGetCtx{this->Ctx, std::move(Callback)}; - Ctx->Functions.actioncache_get_for_digest_async( - Ctx->c_cas, llcas_digest_t{ResolvedKey.data(), ResolvedKey.size()}, - Globally, CallCtx, CacheGetCB); + llcas_digest_t c_digest{ResolvedKey.data(), ResolvedKey.size()}; + if (CancelObj && Ctx->Functions.cancellable_cancel) { + llcas_cancellable_t cancel_tok = nullptr; + Ctx->Functions.actioncache_get_for_digest_async( + Ctx->c_cas, c_digest, Globally, CallCtx, CacheGetCB, &cancel_tok); + *CancelObj = std::make_unique(Ctx, cancel_tok); + } else { + Ctx->Functions.actioncache_get_for_digest_async( + Ctx->c_cas, c_digest, Globally, CallCtx, CacheGetCB, nullptr); + } } Error PluginActionCache::putImpl(ArrayRef ResolvedKey, @@ -507,7 +539,8 @@ Error PluginActionCache::putImpl(ArrayRef ResolvedKey, void PluginActionCache::putImplAsync(ArrayRef ResolvedKey, const CASID &Result, bool Globally, - unique_function Callback) { + unique_function Callback, + std::unique_ptr *CancelObj) { ArrayRef Hash = Result.getHash(); llcas_objectid_t c_value; char *c_err = nullptr; @@ -534,9 +567,17 @@ void PluginActionCache::putImplAsync(ArrayRef ResolvedKey, }; CachePutCtx *CallCtx = new CachePutCtx{this->Ctx, std::move(Callback)}; - Ctx->Functions.actioncache_put_for_digest_async( - Ctx->c_cas, llcas_digest_t{ResolvedKey.data(), ResolvedKey.size()}, - c_value, Globally, CallCtx, CachePutCB); + llcas_digest_t c_digest{ResolvedKey.data(), ResolvedKey.size()}; + if (CancelObj && Ctx->Functions.cancellable_cancel) { + llcas_cancellable_t cancel_tok = nullptr; + Ctx->Functions.actioncache_put_for_digest_async(Ctx->c_cas, c_digest, + c_value, Globally, CallCtx, + CachePutCB, &cancel_tok); + *CancelObj = std::make_unique(Ctx, cancel_tok); + } else { + Ctx->Functions.actioncache_put_for_digest_async( + Ctx->c_cas, c_digest, c_value, Globally, CallCtx, CachePutCB, nullptr); + } } PluginActionCache::PluginActionCache(std::shared_ptr CASCtx) diff --git a/llvm/tools/libCASPluginTest/libCASPluginTest.cpp b/llvm/tools/libCASPluginTest/libCASPluginTest.cpp index a53b287e6b39b..df50c1ae88f5f 100644 --- a/llvm/tools/libCASPluginTest/libCASPluginTest.cpp +++ b/llvm/tools/libCASPluginTest/libCASPluginTest.cpp @@ -46,6 +46,28 @@ void llcas_string_dispose(char *str) { free(str); } namespace { +struct CancellableState { + std::atomic Cancelled{false}; +}; + +struct CancellableWrap { + std::shared_ptr State; +}; + +DEFINE_SIMPLE_CONVERSION_FUNCTIONS(CancellableWrap, llcas_cancellable_t) + +} // namespace + +void llcas_cancellable_cancel(llcas_cancellable_t c_cancellable) { + unwrap(c_cancellable)->State->Cancelled = true; +} + +void llcas_cancellable_dispose(llcas_cancellable_t c_cancellable) { + delete unwrap(c_cancellable); +} + +namespace { + struct CASPluginOptions { std::string OnDiskPath; std::string UpstreamPath; @@ -405,7 +427,13 @@ llcas_lookup_result_t llcas_cas_load_object(llcas_cas_t c_cas, } void llcas_cas_load_object_async(llcas_cas_t c_cas, llcas_objectid_t c_id, - void *ctx_cb, llcas_cas_load_object_cb cb) { + void *ctx_cb, llcas_cas_load_object_cb cb, + llcas_cancellable_t *c_cancellable) { + auto CancelState = std::make_shared(); + if (c_cancellable) { + *c_cancellable = wrap(new CancellableWrap{CancelState}); + } + std::string PrintedDigest; { llcas_digest_t c_digest = llcas_objectid_get_digest(c_cas, c_id); @@ -453,6 +481,12 @@ void llcas_cas_load_object_async(llcas_cas_t c_cas, llcas_objectid_t c_id, // Wait a bit for the caller to proceed. std::this_thread::sleep_for(std::chrono::milliseconds(100)); auto &Wrap = *unwrap(c_cas); + if (CancelState->Cancelled) { + Wrap.syncErrs([&](raw_ostream &OS) { + OS << "load_object_async cancelled: " << PrintedDigest << '\n'; + }); + return passObject(std::nullopt); + } Wrap.syncErrs([&](raw_ostream &OS) { OS << "load_object_async downstream end: " << PrintedDigest << '\n'; }); @@ -541,14 +575,31 @@ llcas_actioncache_get_for_digest(llcas_cas_t c_cas, llcas_digest_t c_key, return LLCAS_LOOKUP_RESULT_SUCCESS; } -void llcas_actioncache_get_for_digest_async(llcas_cas_t c_cas, - llcas_digest_t c_key, bool globally, - void *ctx_cb, - llcas_actioncache_get_cb cb) { +void llcas_actioncache_get_for_digest_async( + llcas_cas_t c_cas, llcas_digest_t c_key, bool globally, void *ctx_cb, + llcas_actioncache_get_cb cb, llcas_cancellable_t *c_cancellable) { + auto CancelState = std::make_shared(); + if (c_cancellable) { + *c_cancellable = wrap(new CancellableWrap{CancelState}); + } + bool IsCancellable = c_cancellable != nullptr; + ArrayRef Key(c_key.data, c_key.size); SmallVector KeyBuf(Key); unwrap(c_cas)->Pool.async([=] { + if (IsCancellable) { + // Wait a bit for the caller to have a chance to cancel. + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } + auto &Wrap = *unwrap(c_cas); + if (CancelState->Cancelled) { + Wrap.syncErrs([&](raw_ostream &OS) { + OS << "actioncache_get_for_digest_async cancelled\n"; + }); + return cb(ctx_cb, LLCAS_LOOKUP_RESULT_NOTFOUND, llcas_objectid_t(), + nullptr); + } llcas_objectid_t c_value; char *c_err; llcas_lookup_result_t result = llcas_actioncache_get_for_digest( @@ -581,15 +632,31 @@ bool llcas_actioncache_put_for_digest(llcas_cas_t c_cas, llcas_digest_t c_key, return false; } -void llcas_actioncache_put_for_digest_async(llcas_cas_t c_cas, - llcas_digest_t c_key, - llcas_objectid_t c_value, - bool globally, void *ctx_cb, - llcas_actioncache_put_cb cb) { +void llcas_actioncache_put_for_digest_async( + llcas_cas_t c_cas, llcas_digest_t c_key, llcas_objectid_t c_value, + bool globally, void *ctx_cb, llcas_actioncache_put_cb cb, + llcas_cancellable_t *c_cancellable) { + auto CancelState = std::make_shared(); + if (c_cancellable) { + *c_cancellable = wrap(new CancellableWrap{CancelState}); + } + bool IsCancellable = c_cancellable != nullptr; + ArrayRef Key(c_key.data, c_key.size); SmallVector KeyBuf(Key); unwrap(c_cas)->Pool.async([=] { + if (IsCancellable) { + // Wait a bit for the caller to have a chance to cancel. + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } + auto &Wrap = *unwrap(c_cas); + if (CancelState->Cancelled) { + Wrap.syncErrs([&](raw_ostream &OS) { + OS << "actioncache_put_for_digest_async cancelled\n"; + }); + return cb(ctx_cb, false, nullptr); + } char *c_err; bool failed = llcas_actioncache_put_for_digest( c_cas, llcas_digest_t{KeyBuf.data(), KeyBuf.size()}, c_value, globally, diff --git a/llvm/tools/libCASPluginTest/libCASPluginTest.exports b/llvm/tools/libCASPluginTest/libCASPluginTest.exports index 07478da1e1e88..ad8bed6c6a689 100644 --- a/llvm/tools/libCASPluginTest/libCASPluginTest.exports +++ b/llvm/tools/libCASPluginTest/libCASPluginTest.exports @@ -2,6 +2,8 @@ llcas_actioncache_get_for_digest llcas_actioncache_get_for_digest_async llcas_actioncache_put_for_digest llcas_actioncache_put_for_digest_async +llcas_cancellable_cancel +llcas_cancellable_dispose llcas_cas_contains_object llcas_cas_create llcas_cas_dispose