Skip to content

Commit 8a6c4b3

Browse files
authored
Merge pull request #39681 from ahoppen/pr/request-tracker
[SourceKit] Add a request tracker that manages cancellation
2 parents f213272 + c43c051 commit 8a6c4b3

File tree

21 files changed

+175
-73
lines changed

21 files changed

+175
-73
lines changed

test/SourceKit/Misc/cancellation.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
// Check that we can cancel requests.
2-
// We need to wait a little bit after request scheduling and cancellation to make sure we are not cancelling the request before it got scheduled.
32

4-
// RUN: not %sourcekitd-test -req=cursor -id=slow -async -pos=9:5 -simulate-long-request=5000 %s -- %s == \
5-
// RUN: -shell -- sleep 1 == \
3+
// RUN: not %sourcekitd-test -req=cursor -id=slow -async -pos=7:5 -simulate-long-request=5000 %s -- %s == \
64
// RUN: -cancel=slow 2>&1 \
75
// RUN: | %FileCheck %s
86

tools/SourceKit/bindings/python/sourcekitd/capi.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,8 @@ def __repr__(self):
315315
functionList = [
316316
("sourcekitd_cancel_request",
317317
[c_void_p]),
318+
("sourcekitd_request_handle_dispose",
319+
[c_void_p]),
318320

319321
("sourcekitd_initialize",
320322
None),

tools/SourceKit/include/SourceKit/Core/Context.h

Lines changed: 102 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -54,43 +54,118 @@ class GlobalConfig {
5454
Settings::CompletionOptions getCompletionOpts() const;
5555
};
5656

57-
class SlowRequestSimulator {
58-
std::map<SourceKitCancellationToken, std::shared_ptr<Semaphore>>
59-
InProgressRequests;
57+
/// Keeps track of all requests that are currently in progress and coordinates
58+
/// cancellation. All operations are no-ops if \c nullptr is used as a
59+
/// cancellation token.
60+
///
61+
/// Tracked requests will be removed from this object when the request returns
62+
/// a response (\c sourcekitd::handleRequest disposes the cancellation token).
63+
/// We leak memory if a request is cancelled after it has returned a result
64+
/// because we add an \c IsCancelled entry in that case but \c
65+
/// sourcekitd::handleRequest won't have a chance to dispose of the token.
66+
class RequestTracker {
67+
struct TrackedRequest {
68+
/// Whether the request has already been cancelled.
69+
bool IsCancelled = false;
70+
/// A handler that will be called as the request gets cancelled.
71+
std::function<void(void)> CancellationHandler;
72+
};
6073

61-
/// Mutex guarding \c InProgressRequests.
62-
llvm::sys::Mutex InProgressRequestsMutex;
74+
/// Once we have information about a request (either a cancellation handler
75+
/// or information that it has been cancelled), it is added to this list.
76+
std::map<SourceKitCancellationToken, TrackedRequest> Requests;
77+
78+
/// Guards the \c Requests variable.
79+
llvm::sys::Mutex RequestsMtx;
80+
81+
/// Must only be called if \c RequestsMtx has been claimed.
82+
/// Returns \c true if the request has already been cancelled. Requests that
83+
/// are not tracked are not assumed to be cancelled.
84+
bool isCancelledImpl(SourceKitCancellationToken CancellationToken) {
85+
if (Requests.count(CancellationToken) == 0) {
86+
return false;
87+
} else {
88+
return Requests[CancellationToken].IsCancelled;
89+
}
90+
}
6391

6492
public:
93+
/// Returns \c true if the request with the given \p CancellationToken has
94+
/// already been cancelled.
95+
bool isCancelled(SourceKitCancellationToken CancellationToken) {
96+
if (!CancellationToken) {
97+
return false;
98+
}
99+
llvm::sys::ScopedLock L(RequestsMtx);
100+
return isCancelledImpl(CancellationToken);
101+
}
102+
103+
/// Adds a \p CancellationHandler that will be called when the request
104+
/// associated with the \p CancellationToken is cancelled.
105+
/// If that request has already been cancelled when this method is called,
106+
/// \p CancellationHandler will be called synchronously.
107+
void setCancellationHandler(SourceKitCancellationToken CancellationToken,
108+
std::function<void(void)> CancellationHandler) {
109+
if (!CancellationToken) {
110+
return;
111+
}
112+
llvm::sys::ScopedLock L(RequestsMtx);
113+
if (isCancelledImpl(CancellationToken)) {
114+
if (CancellationHandler) {
115+
CancellationHandler();
116+
}
117+
} else {
118+
Requests[CancellationToken].CancellationHandler = CancellationHandler;
119+
}
120+
}
121+
122+
/// Cancel the request with the given \p CancellationToken. If a cancellation
123+
/// handler is associated with it, call it.
124+
void cancel(SourceKitCancellationToken CancellationToken) {
125+
if (!CancellationToken) {
126+
return;
127+
}
128+
llvm::sys::ScopedLock L(RequestsMtx);
129+
Requests[CancellationToken].IsCancelled = true;
130+
if (auto CancellationHandler =
131+
Requests[CancellationToken].CancellationHandler) {
132+
CancellationHandler();
133+
}
134+
}
135+
136+
/// Stop tracking the request with the given \p CancellationToken, freeing up
137+
/// any memory needed for the tracking.
138+
void stopTracking(SourceKitCancellationToken CancellationToken) {
139+
if (!CancellationToken) {
140+
return;
141+
}
142+
llvm::sys::ScopedLock L(RequestsMtx);
143+
Requests.erase(CancellationToken);
144+
}
145+
};
146+
147+
class SlowRequestSimulator {
148+
std::shared_ptr<RequestTracker> ReqTracker;
149+
150+
public:
151+
SlowRequestSimulator(std::shared_ptr<RequestTracker> ReqTracker)
152+
: ReqTracker(ReqTracker) {}
153+
65154
/// Simulate that a request takes \p DurationMs to execute. While waiting that
66155
/// duration, the request can be cancelled using the \p CancellationToken.
67156
/// Returns \c true if the request waited the required duration and \c false
68157
/// if it was cancelled.
69158
bool simulateLongRequest(int64_t DurationMs,
70159
SourceKitCancellationToken CancellationToken) {
71-
auto Sema = std::make_shared<Semaphore>(0);
72-
{
73-
llvm::sys::ScopedLock L(InProgressRequestsMutex);
74-
InProgressRequests[CancellationToken] = Sema;
75-
}
76-
bool DidTimeOut = Sema->wait(DurationMs);
77-
{
78-
llvm::sys::ScopedLock L(InProgressRequestsMutex);
79-
InProgressRequests[CancellationToken] = nullptr;
80-
}
160+
auto Sema = Semaphore(0);
161+
ReqTracker->setCancellationHandler(CancellationToken,
162+
[&] { Sema.signal(); });
163+
bool DidTimeOut = Sema.wait(DurationMs);
164+
ReqTracker->setCancellationHandler(CancellationToken, nullptr);
81165
// If we timed out, we waited the required duration. If we didn't time out,
82166
// the semaphore was cancelled.
83167
return DidTimeOut;
84168
}
85-
86-
/// Cancel a simulated long request. If the required wait duration already
87-
/// elapsed, this is a no-op.
88-
void cancel(SourceKitCancellationToken CancellationToken) {
89-
llvm::sys::ScopedLock L(InProgressRequestsMutex);
90-
if (auto InProgressSema = InProgressRequests[CancellationToken]) {
91-
InProgressSema->signal();
92-
}
93-
}
94169
};
95170

96171
class Context {
@@ -99,6 +174,7 @@ class Context {
99174
std::unique_ptr<LangSupport> SwiftLang;
100175
std::shared_ptr<NotificationCenter> NotificationCtr;
101176
std::shared_ptr<GlobalConfig> Config;
177+
std::shared_ptr<RequestTracker> ReqTracker;
102178
std::shared_ptr<SlowRequestSimulator> SlowRequestSim;
103179

104180
public:
@@ -122,6 +198,8 @@ class Context {
122198
std::shared_ptr<SlowRequestSimulator> getSlowRequestSimulator() {
123199
return SlowRequestSim;
124200
}
201+
202+
std::shared_ptr<RequestTracker> getRequestTracker() { return ReqTracker; }
125203
};
126204

127205
} // namespace SourceKit

tools/SourceKit/include/SourceKit/Core/LangSupport.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -745,8 +745,6 @@ class LangSupport {
745745

746746
virtual void dependencyUpdated() {}
747747

748-
virtual void cancelRequest(SourceKitCancellationToken CancellationToken) = 0;
749-
750748
virtual void indexSource(StringRef Filename,
751749
IndexingConsumer &Consumer,
752750
ArrayRef<const char *> Args) = 0;

tools/SourceKit/lib/Core/Context.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ SourceKit::Context::Context(
4444
DiagnosticDocumentationPath(DiagnosticDocumentationPath),
4545
NotificationCtr(
4646
new NotificationCenter(shouldDispatchNotificationsOnMain)),
47-
Config(new GlobalConfig()), SlowRequestSim(new SlowRequestSimulator()) {
47+
Config(new GlobalConfig()), ReqTracker(new RequestTracker()),
48+
SlowRequestSim(new SlowRequestSimulator(ReqTracker)) {
4849
// Should be called last after everything is initialized.
4950
SwiftLang = LangSupportFactoryFn(*this);
5051
}

tools/SourceKit/lib/SwiftLang/SwiftASTManager.cpp

Lines changed: 17 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -556,17 +556,19 @@ struct SwiftASTManager::Implementation {
556556
explicit Implementation(
557557
std::shared_ptr<SwiftEditorDocumentFileMap> EditorDocs,
558558
std::shared_ptr<GlobalConfig> Config,
559-
std::shared_ptr<SwiftStatistics> Stats, StringRef RuntimeResourcePath,
559+
std::shared_ptr<SwiftStatistics> Stats,
560+
std::shared_ptr<RequestTracker> ReqTracker, StringRef RuntimeResourcePath,
560561
StringRef DiagnosticDocumentationPath)
561562
: EditorDocs(EditorDocs), Config(Config), Stats(Stats),
562-
RuntimeResourcePath(RuntimeResourcePath),
563+
ReqTracker(ReqTracker), RuntimeResourcePath(RuntimeResourcePath),
563564
DiagnosticDocumentationPath(DiagnosticDocumentationPath),
564565
SessionTimestamp(llvm::sys::toTimeT(std::chrono::system_clock::now())) {
565566
}
566567

567568
std::shared_ptr<SwiftEditorDocumentFileMap> EditorDocs;
568569
std::shared_ptr<GlobalConfig> Config;
569570
std::shared_ptr<SwiftStatistics> Stats;
571+
std::shared_ptr<RequestTracker> ReqTracker;
570572
std::string RuntimeResourcePath;
571573
std::string DiagnosticDocumentationPath;
572574
SourceManager SourceMgr;
@@ -590,9 +592,11 @@ struct SwiftASTManager::Implementation {
590592
struct ScheduledConsumer {
591593
SwiftASTConsumerWeakRef Consumer;
592594
const void *OncePerASTToken;
593-
SourceKitCancellationToken CancellationToken;
594595
};
595596

597+
/// FIXME: Once we no longer support implicit cancellation using
598+
/// OncePerASTToken, we can stop keeping track of ScheduledConsumers and
599+
/// completely rely on RequestTracker for cancellation.
596600
llvm::sys::Mutex ScheduledConsumersMtx;
597601
std::vector<ScheduledConsumer> ScheduledConsumers;
598602

@@ -632,9 +636,11 @@ struct SwiftASTManager::Implementation {
632636
SwiftASTManager::SwiftASTManager(
633637
std::shared_ptr<SwiftEditorDocumentFileMap> EditorDocs,
634638
std::shared_ptr<GlobalConfig> Config,
635-
std::shared_ptr<SwiftStatistics> Stats, StringRef RuntimeResourcePath,
639+
std::shared_ptr<SwiftStatistics> Stats,
640+
std::shared_ptr<RequestTracker> ReqTracker, StringRef RuntimeResourcePath,
636641
StringRef DiagnosticDocumentationPath)
637-
: Impl(*new Implementation(EditorDocs, Config, Stats, RuntimeResourcePath,
642+
: Impl(*new Implementation(EditorDocs, Config, Stats, ReqTracker,
643+
RuntimeResourcePath,
638644
DiagnosticDocumentationPath)) {}
639645

640646
SwiftASTManager::~SwiftASTManager() {
@@ -766,30 +772,18 @@ void SwiftASTManager::processASTAsync(
766772
}
767773
}
768774
}
769-
Impl.ScheduledConsumers.push_back(
770-
{ASTConsumer, OncePerASTToken, CancellationToken});
775+
Impl.ScheduledConsumers.push_back({ASTConsumer, OncePerASTToken});
771776
}
772777

773778
Producer->enqueueConsumer(ASTConsumer, fileSystem, Snapshots,
774779
shared_from_this());
775-
}
776780

777-
void SwiftASTManager::cancelASTConsumer(
778-
SourceKitCancellationToken CancellationToken) {
779-
if (!CancellationToken) {
780-
return;
781-
}
782-
Impl.cleanDeletedConsumers();
783-
llvm::sys::ScopedLock L(Impl.ScheduledConsumersMtx);
784-
for (auto ScheduledConsumer : Impl.ScheduledConsumers) {
785-
if (ScheduledConsumer.CancellationToken == CancellationToken) {
786-
if (auto Consumer = ScheduledConsumer.Consumer.lock()) {
787-
Consumer->requestCancellation();
788-
// Multiple consumers might share the same cancellation token (see
789-
// documentation on ScheduledConsumer), so we can't break here.
790-
}
781+
auto WeakConsumer = SwiftASTConsumerWeakRef(ASTConsumer);
782+
Impl.ReqTracker->setCancellationHandler(CancellationToken, [WeakConsumer] {
783+
if (auto Consumer = WeakConsumer.lock()) {
784+
Consumer->requestCancellation();
791785
}
792-
}
786+
});
793787
}
794788

795789
void SwiftASTManager::removeCachedAST(SwiftInvocationRef Invok) {

tools/SourceKit/lib/SwiftLang/SwiftASTManager.h

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
#ifndef LLVM_SOURCEKIT_LIB_SWIFTLANG_SWIFTASTMANAGER_H
7575
#define LLVM_SOURCEKIT_LIB_SWIFTLANG_SWIFTASTMANAGER_H
7676

77+
#include "SourceKit/Core/Context.h"
7778
#include "SourceKit/Core/LLVM.h"
7879
#include "SourceKit/Support/CancellationToken.h"
7980
#include "SwiftInvocation.h"
@@ -223,6 +224,7 @@ class SwiftASTManager : public std::enable_shared_from_this<SwiftASTManager> {
223224
explicit SwiftASTManager(std::shared_ptr<SwiftEditorDocumentFileMap>,
224225
std::shared_ptr<GlobalConfig> Config,
225226
std::shared_ptr<SwiftStatistics> Stats,
227+
std::shared_ptr<RequestTracker> ReqTracker,
226228
StringRef RuntimeResourcePath,
227229
StringRef DiagnosticDocumentationPath);
228230
~SwiftASTManager();
@@ -250,12 +252,6 @@ class SwiftASTManager : public std::enable_shared_from_this<SwiftASTManager> {
250252
ArrayRef<ImmutableTextSnapshotRef> Snapshots =
251253
ArrayRef<ImmutableTextSnapshotRef>());
252254

253-
/// Request the \c SwiftASTConsumer with the given \p CancellationToken to be
254-
/// cancelled. If \p CancellationToken is \c nullptr or no consumer with the
255-
/// given cancellation token exists (e.g. because the consumer already
256-
/// finished), this is a no-op.
257-
void cancelASTConsumer(SourceKitCancellationToken CancellationToken);
258-
259255
std::unique_ptr<llvm::MemoryBuffer> getMemoryBuffer(StringRef Filename,
260256
std::string &Error);
261257

tools/SourceKit/lib/SwiftLang/SwiftLangSupport.cpp

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ configureCompletionInstance(std::shared_ptr<CompletionInstance> CompletionInst,
273273

274274
SwiftLangSupport::SwiftLangSupport(SourceKit::Context &SKCtx)
275275
: NotificationCtr(SKCtx.getNotificationCenter()),
276-
CCCache(new SwiftCompletionCache) {
276+
ReqTracker(SKCtx.getRequestTracker()), CCCache(new SwiftCompletionCache) {
277277
llvm::SmallString<128> LibPath(SKCtx.getRuntimeLibPath());
278278
llvm::sys::path::append(LibPath, "swift");
279279
RuntimeResourcePath = std::string(LibPath.str());
@@ -282,7 +282,7 @@ SwiftLangSupport::SwiftLangSupport(SourceKit::Context &SKCtx)
282282
Stats = std::make_shared<SwiftStatistics>();
283283
EditorDocuments = std::make_shared<SwiftEditorDocumentFileMap>();
284284
ASTMgr = std::make_shared<SwiftASTManager>(
285-
EditorDocuments, SKCtx.getGlobalConfiguration(), Stats,
285+
EditorDocuments, SKCtx.getGlobalConfiguration(), Stats, ReqTracker,
286286
RuntimeResourcePath, DiagnosticDocumentationPath);
287287

288288
CompletionInst = std::make_shared<CompletionInstance>();
@@ -307,11 +307,6 @@ void SwiftLangSupport::dependencyUpdated() {
307307
CompletionInst->markCachedCompilerInstanceShouldBeInvalidated();
308308
}
309309

310-
void SwiftLangSupport::cancelRequest(
311-
SourceKitCancellationToken CancellationToken) {
312-
getASTManager()->cancelASTConsumer(CancellationToken);
313-
}
314-
315310
UIdent SwiftLangSupport::getUIDForDeclLanguage(const swift::Decl *D) {
316311
if (D->hasClangNode())
317312
return KindObjC;

tools/SourceKit/lib/SwiftLang/SwiftLangSupport.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#define LLVM_SOURCEKIT_LIB_SWIFTLANG_SWIFTLANGSUPPORT_H
1515

1616
#include "CodeCompletion.h"
17+
#include "SourceKit/Core/Context.h"
1718
#include "SourceKit/Core/LangSupport.h"
1819
#include "SourceKit/Support/Concurrency.h"
1920
#include "SourceKit/Support/Statistic.h"
@@ -304,6 +305,7 @@ class SwiftLangSupport : public LangSupport {
304305
std::string DiagnosticDocumentationPath;
305306
std::shared_ptr<SwiftASTManager> ASTMgr;
306307
std::shared_ptr<SwiftEditorDocumentFileMap> EditorDocuments;
308+
std::shared_ptr<RequestTracker> ReqTracker;
307309
SwiftInterfaceGenMap IFaceGenContexts;
308310
ThreadSafeRefCntPtr<SwiftCompletionCache> CCCache;
309311
ThreadSafeRefCntPtr<SwiftPopularAPI> PopularAPI;
@@ -487,8 +489,6 @@ class SwiftLangSupport : public LangSupport {
487489

488490
void dependencyUpdated() override;
489491

490-
void cancelRequest(SourceKitCancellationToken CancellationToken) override;
491-
492492
void indexSource(StringRef Filename, IndexingConsumer &Consumer,
493493
ArrayRef<const char *> Args) override;
494494

tools/SourceKit/tools/sourcekitd-test/sourcekitd-test.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1228,6 +1228,8 @@ static int handleTestInvocation(TestOptions Opts, TestOptions &InitOpts) {
12281228
^(sourcekitd_response_t resp) {
12291229
auto &info = asyncResponses[respIndex];
12301230
info.response = resp;
1231+
sourcekitd_request_handle_dispose(
1232+
info.requestHandle);
12311233
info.semaphore.signal(); // Ready to be handled!
12321234
});
12331235

tools/SourceKit/tools/sourcekitd/bin/InProc/sourcekitdInProc-darwin.exports

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
sourcekitd_cancel_request
2+
sourcekitd_request_handle_dispose
23
sourcekitd_initialize
34
sourcekitd_request_array_create
45
sourcekitd_request_array_set_int64

tools/SourceKit/tools/sourcekitd/bin/InProc/sourcekitdInProc.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,10 @@ void sourcekitd_cancel_request(sourcekitd_request_handle_t handle) {
162162
sourcekitd::cancelRequest(/*CancellationToken=*/handle);
163163
}
164164

165+
void sourcekitd_request_handle_dispose(sourcekitd_request_handle_t handle) {
166+
sourcekitd::disposeCancellationToken(/*CancellationToken=*/handle);
167+
}
168+
165169
void
166170
sourcekitd_set_interrupted_connection_handler(
167171
sourcekitd_interrupted_connection_handler_t handler) {

tools/SourceKit/tools/sourcekitd/bin/InProc/sourcekitdInProc.exports

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
sourcekitd_cancel_request
2+
sourcekitd_request_handle_dispose
23
sourcekitd_initialize
34
sourcekitd_request_array_create
45
sourcekitd_request_array_set_int64

0 commit comments

Comments
 (0)