Skip to content

Commit 772485d

Browse files
committed
[SourceKit] Add a request tracker that manages cancellaiton
Previously, `SwiftASTManager` and `SlowRequestSimulator` maintained their own list of in-progress cancellation tokens. With code completion cancellation coming up, there would need to be yet another place to track in-progress requests, so let’s centralize it. While at it, also support cancelling requests before they are scheduled, eliminating the need for a `sleep` in a test case. The current implementaiton leaks tiny amounts of memory if a request is cancelled after if finishes. I think this is fine because it is a pretty nieche case and the leaked memory is pretty small (a `std::map` entry pointing to a `std::function` + `bool`). Alternatively, we could require the client to always dispose of the cancellation token manually.
1 parent 86a1bfd commit 772485d

File tree

11 files changed

+143
-73
lines changed

11 files changed

+143
-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/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/include/sourcekitd/Internal.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,8 @@ void handleRequest(sourcekitd_object_t Request,
180180

181181
void cancelRequest(SourceKitCancellationToken CancellationToken);
182182

183+
void disposeCancellationToken(SourceKitCancellationToken CancellationToken);
184+
183185
void printRequestObject(sourcekitd_object_t Obj, llvm::raw_ostream &OS);
184186
void printResponse(sourcekitd_response_t Resp, llvm::raw_ostream &OS);
185187

tools/SourceKit/tools/sourcekitd/lib/API/Requests.cpp

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -390,20 +390,27 @@ void sourcekitd::handleRequest(sourcekitd_object_t Req,
390390
}
391391

392392
handleRequestImpl(
393-
Req, CancellationToken, [Receiver](sourcekitd_response_t Resp) {
393+
Req, CancellationToken,
394+
[Receiver, CancellationToken](sourcekitd_response_t Resp) {
394395
LOG_SECTION("handleRequest-after", InfoHighPrio) {
395396
// Responses are big, print them out with info medium priority.
396397
if (Logger::isLoggingEnabledForLevel(Logger::Level::InfoMediumPrio))
397398
sourcekitd::printResponse(Resp, Log->getOS());
398399
}
399400

401+
sourcekitd::disposeCancellationToken(CancellationToken);
402+
400403
Receiver(Resp);
401404
});
402405
}
403406

404407
void sourcekitd::cancelRequest(SourceKitCancellationToken CancellationToken) {
405-
getGlobalContext().getSlowRequestSimulator()->cancel(CancellationToken);
406-
getGlobalContext().getSwiftLangSupport().cancelRequest(CancellationToken);
408+
getGlobalContext().getRequestTracker()->cancel(CancellationToken);
409+
}
410+
411+
void sourcekitd::disposeCancellationToken(
412+
SourceKitCancellationToken CancellationToken) {
413+
getGlobalContext().getRequestTracker()->stopTracking(CancellationToken);
407414
}
408415

409416
static std::unique_ptr<llvm::MemoryBuffer> getInputBufForRequest(

0 commit comments

Comments
 (0)