Skip to content

Commit 5a6341b

Browse files
authored
Merge pull request #40493 from ahoppen/pr/enqueue-consumer-on-background-queue
[SourceKit] Enqueue `SwiftASTConsumer`s async on a queue
2 parents e2f1907 + c90c7a2 commit 5a6341b

File tree

2 files changed

+50
-35
lines changed

2 files changed

+50
-35
lines changed

tools/SourceKit/lib/SwiftLang/SwiftASTManager.cpp

Lines changed: 40 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -457,15 +457,16 @@ class ASTProducer : public std::enable_shared_from_this<ASTProducer> {
457457
/// execute. Operations are guaranteed to be in FIFO order, that is the first
458458
/// one in the vector is the oldes build operation.
459459
SmallVector<ASTBuildOperationRef, 4> BuildOperations = {};
460-
llvm::sys::Mutex BuildOperationsMtx;
460+
WorkQueue BuildOperationsQueue = WorkQueue(
461+
WorkQueue::Dequeuing::Serial, "ASTProducer.BuildOperationsQueue");
461462

462463
/// Erase all finished build operations with a result except for the latest
463464
/// one which contains a successful results.
464465
/// This cleans up all stale build operations (probably containing old ASTs),
465466
/// but keeps the latest AST around, so that new consumers can be served from
466467
/// it, if possible.
467468
///
468-
/// Assumes that \c BuildOperationsMtx has been claimed.
469+
/// Must be executed on \c BuildOperationsQueue.
469470
void cleanBuildOperations() {
470471
auto ReverseOperations = llvm::reverse(BuildOperations);
471472
auto LastOperationWithResultIt =
@@ -485,7 +486,7 @@ class ASTProducer : public std::enable_shared_from_this<ASTProducer> {
485486
/// Returns the latest build operation which can serve the \p Consumer or
486487
/// \c nullptr if no such build operation exists.
487488
///
488-
/// Assumes that \c BuildOperationsMtx has been claimed.
489+
/// Must be executed on \c BuildOperationsQueue.
489490
ASTBuildOperationRef getBuildOperationForConsumer(
490491
SwiftASTConsumerRef Consumer,
491492
IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
@@ -1162,13 +1163,13 @@ bool ASTBuildOperation::addConsumer(SwiftASTConsumerRef Consumer) {
11621163
} else {
11631164
assert(OperationState != State::Finished);
11641165
auto WeakThis = std::weak_ptr<ASTBuildOperation>(shared_from_this());
1166+
Consumers.push_back(Consumer);
11651167
Consumer->setCancellationRequestCallback(
11661168
[WeakThis](SwiftASTConsumerRef Consumer) {
11671169
if (auto This = WeakThis.lock()) {
11681170
This->requestConsumerCancellation(Consumer);
11691171
}
11701172
});
1171-
Consumers.push_back(Consumer);
11721173
}
11731174
return true;
11741175
}
@@ -1201,35 +1202,40 @@ void ASTProducer::enqueueConsumer(
12011202
SwiftASTConsumerRef Consumer,
12021203
IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
12031204
SwiftASTManagerRef Mgr) {
1204-
// We can't use a llvm::sys::ScopedLock here because we need to unlock it
1205-
// before calling enqueueConsumer again in the !WasAdded case below.
1206-
std::unique_lock<llvm::sys::Mutex> BuildOperationsLock(BuildOperationsMtx);
1207-
if (auto BuildOp = getBuildOperationForConsumer(Consumer, FileSystem, Mgr)) {
1208-
bool WasAdded = BuildOp->addConsumer(Consumer);
1209-
if (!WasAdded) {
1210-
// The build operation was cancelled after the call to
1211-
// getBuildOperationForConsumer but before the consumer could be added.
1212-
// This should be an absolute edge case. Let's just try again.
1213-
BuildOperationsLock.unlock();
1214-
enqueueConsumer(Consumer, FileSystem, Mgr);
1215-
return;
1216-
}
1217-
} else {
1218-
auto WeakThis = std::weak_ptr<ASTProducer>(shared_from_this());
1219-
auto DidFinishCallback = [WeakThis, Mgr]() {
1220-
if (auto This = WeakThis.lock()) {
1221-
{
1222-
llvm::sys::ScopedLock L(This->BuildOperationsMtx);
1223-
This->cleanBuildOperations();
1224-
}
1225-
// Re-register the object with the cache to update its memory cost.
1226-
Mgr->Impl.ASTCache.set(This->InvokRef->Impl.Key, This);
1205+
// Enqueue the consumer in the background because getBuildOperationForConsumer
1206+
// consults the file system and might be slow. Also, there's no need to do
1207+
// this synchronously since all results will be delivered async anyway.
1208+
auto This = shared_from_this();
1209+
BuildOperationsQueue.dispatch([Consumer, FileSystem, Mgr, This]() {
1210+
if (auto BuildOp =
1211+
This->getBuildOperationForConsumer(Consumer, FileSystem, Mgr)) {
1212+
bool WasAdded = BuildOp->addConsumer(Consumer);
1213+
if (!WasAdded) {
1214+
// The build operation was cancelled after the call to
1215+
// getBuildOperationForConsumer but before the consumer could be
1216+
// added. This should be an absolute edge case. Let's just try
1217+
// again.
1218+
This->enqueueConsumer(Consumer, FileSystem, Mgr);
12271219
}
1228-
};
1229-
ASTBuildOperationRef NewBuildOp = std::make_shared<ASTBuildOperation>(
1230-
FileSystem, InvokRef, Mgr, DidFinishCallback);
1231-
BuildOperations.push_back(NewBuildOp);
1232-
NewBuildOp->addConsumer(Consumer);
1233-
NewBuildOp->schedule(Mgr->Impl.ASTBuildQueue);
1234-
}
1220+
} else {
1221+
auto WeakThis = std::weak_ptr<ASTProducer>(This);
1222+
auto DidFinishCallback = [WeakThis, Mgr]() {
1223+
if (auto This = WeakThis.lock()) {
1224+
This->BuildOperationsQueue.dispatchSync(
1225+
[This]() { This->cleanBuildOperations(); });
1226+
// Re-register the object with the cache to update its memory
1227+
// cost.
1228+
Mgr->Impl.ASTCache.set(This->InvokRef->Impl.Key, This);
1229+
}
1230+
};
1231+
ASTBuildOperationRef NewBuildOp = std::make_shared<ASTBuildOperation>(
1232+
FileSystem, This->InvokRef, Mgr, DidFinishCallback);
1233+
This->BuildOperations.push_back(NewBuildOp);
1234+
bool WasAdded = NewBuildOp->addConsumer(Consumer);
1235+
assert(WasAdded && "Consumer wasn't added to a new build operation "
1236+
"that can't have been cancelled yet?");
1237+
(void)WasAdded;
1238+
NewBuildOp->schedule(Mgr->Impl.ASTBuildQueue);
1239+
}
1240+
});
12351241
}

tools/SourceKit/lib/SwiftLang/SwiftASTManager.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@ class SwiftASTConsumer : public std::enable_shared_from_this<SwiftASTConsumer> {
145145
Optional<std::function<void(std::shared_ptr<SwiftASTConsumer>)>>
146146
CancellationRequestCallback;
147147

148+
bool IsCancelled = false;
149+
148150
public:
149151
virtual ~SwiftASTConsumer() { }
150152

@@ -157,6 +159,7 @@ class SwiftASTConsumer : public std::enable_shared_from_this<SwiftASTConsumer> {
157159
/// depending on it.
158160
void requestCancellation() {
159161
llvm::sys::ScopedLock L(CancellationRequestCallbackMtx);
162+
IsCancelled = true;
160163
if (CancellationRequestCallback.hasValue()) {
161164
(*CancellationRequestCallback)(shared_from_this());
162165
CancellationRequestCallback = None;
@@ -168,12 +171,18 @@ class SwiftASTConsumer : public std::enable_shared_from_this<SwiftASTConsumer> {
168171
/// currently no callback set.
169172
/// The cancellation request callback will automatically be removed when the
170173
/// SwiftASTManager is cancelled.
174+
/// If this \c SwiftASTConsumer has already been cancelled when this method is
175+
/// called, \c NewCallback will be called immediately.
171176
void setCancellationRequestCallback(
172177
std::function<void(std::shared_ptr<SwiftASTConsumer>)> NewCallback) {
173178
llvm::sys::ScopedLock L(CancellationRequestCallbackMtx);
174179
assert(!CancellationRequestCallback.hasValue() &&
175180
"Can't set two cancellation callbacks on a SwiftASTConsumer");
176-
CancellationRequestCallback = NewCallback;
181+
if (IsCancelled) {
182+
NewCallback(shared_from_this());
183+
} else {
184+
CancellationRequestCallback = NewCallback;
185+
}
177186
}
178187

179188
/// Removes the cancellation request callback previously set by \c

0 commit comments

Comments
 (0)