[SourceKit] Allow explicit cancellation of requests with a cancellation token

The key changes here are
- To keep track of cancellation tokens for all `ScheduledConsumer`s in `SwiftASTManager`
- Generate unique request handles for all incoming requests (`create_request_handle `), use these request handles as cancellation tokens and return them from the `sourcekitd_send_request` methods
- Implement cancellation with `sourcekitd_cancel_request` as the entry point and `SwiftASTManager::cancelASTConsumer` as the termination point

Everything else is just plumbing the cancellation token through the various abstraction layers.

rdar://83391505
This commit is contained in:
Alex Hoppen
2021-09-23 19:14:47 +02:00
parent 4521c99d56
commit 7d5ee83a61
18 changed files with 546 additions and 292 deletions

View File

@@ -27,6 +27,12 @@ static StringRef getRuntimeLibPath() {
return sys::path::parent_path(SWIFTLIB_DIR);
}
static void *createCancallationToken() {
static std::atomic<size_t> handle(1000);
return reinterpret_cast<void *>(
handle.fetch_add(1, std::memory_order_relaxed));
}
namespace {
class NullEditorConsumer : public EditorConsumer {
@@ -145,9 +151,10 @@ public:
getLang().editorReplaceText(DocName, Buf.get(), Offset, Length, Consumer);
}
TestCursorInfo getCursor(const char *DocName, unsigned Offset,
ArrayRef<const char *> CArgs,
bool CancelOnSubsequentRequest = false) {
TestCursorInfo
getCursor(const char *DocName, unsigned Offset, ArrayRef<const char *> CArgs,
SourceKitCancellationToken CancellationToken = nullptr,
bool CancelOnSubsequentRequest = false) {
auto Args = makeArgs(DocName, CArgs);
Semaphore sema(0);
@@ -155,7 +162,8 @@ public:
getLang().getCursorInfo(
DocName, Offset, /*Length=*/0, /*Actionables=*/false,
/*SymbolGraph=*/false, CancelOnSubsequentRequest, Args,
/*vfsOptions=*/None, [&](const RequestResult<CursorInfoData> &Result) {
/*vfsOptions=*/None, CancellationToken,
[&](const RequestResult<CursorInfoData> &Result) {
assert(!Result.isCancelled());
if (Result.isError()) {
TestInfo.Error = Result.getError().str();
@@ -439,12 +447,14 @@ TEST_F(CursorInfoTest, CursorInfoCancelsPreviousRequest) {
getLang().getCursorInfo(
SlowDocName, SlowOffset, /*Length=*/0, /*Actionables=*/false,
/*SymbolGraph=*/false, /*CancelOnSubsequentRequest=*/true, ArgsForSlow,
/*vfsOptions=*/None, [&](const RequestResult<CursorInfoData> &Result) {
/*vfsOptions=*/None, /*CancellationToken=*/nullptr,
[&](const RequestResult<CursorInfoData> &Result) {
EXPECT_TRUE(Result.isCancelled());
FirstCursorInfoSema.signal();
});
auto Info = getCursor(FastDocName, FastOffset, Args,
/*CancellationToken=*/nullptr,
/*CancelOnSubsequentRequest=*/true);
EXPECT_STREQ("foo", Info.Name.c_str());
EXPECT_STREQ("Int", Info.Typename.c_str());
@@ -455,3 +465,40 @@ TEST_F(CursorInfoTest, CursorInfoCancelsPreviousRequest) {
if (expired)
llvm::report_fatal_error("Did not receive a resonse for the first request");
}
TEST_F(CursorInfoTest, CursorInfoCancellation) {
// TODO: This test case relies on the following snippet being slow to type
// check so that the first cursor info request takes longer to execute than it
// takes time to schedule the second request. If that is fixed, we need to
// find a new way to cause slow type checking. rdar://80582770
const char *SlowDocName = "slow.swift";
const char *SlowContents = "func foo(x: Invalid1, y: Invalid2) {\n"
" x / y / x / y / x / y / x / y\n"
"}\n";
auto SlowOffset = findOffset("x", SlowContents);
const char *Args[] = {"-parse-as-library"};
std::vector<const char *> ArgsForSlow = llvm::makeArrayRef(Args).vec();
ArgsForSlow.push_back(SlowDocName);
open(SlowDocName, SlowContents, llvm::makeArrayRef(Args));
SourceKitCancellationToken CancellationToken = createCancallationToken();
// Schedule a cursor info request that takes long to execute. This should be
// cancelled as the next cursor info (which is faster) gets requested.
Semaphore CursorInfoSema(0);
getLang().getCursorInfo(
SlowDocName, SlowOffset, /*Length=*/0, /*Actionables=*/false,
/*SymbolGraph=*/false, /*CancelOnSubsequentRequest=*/false, ArgsForSlow,
/*vfsOptions=*/None, /*CancellationToken=*/CancellationToken,
[&](const RequestResult<CursorInfoData> &Result) {
EXPECT_TRUE(Result.isCancelled());
CursorInfoSema.signal();
});
getLang().cancelRequest(CancellationToken);
bool expired = CursorInfoSema.wait(30 * 1000);
if (expired)
llvm::report_fatal_error("Did not receive a resonse for the first request");
}