Skip to content

Commit 91f4f24

Browse files
committed
[SoruceKit] Audit SwiftASTManager to prevent deadlocks
- Ensure that no callbacks are called when either of these mutexes are held: - `SwiftASTConsumer::CancellationRequestCallbackAndIsCancelledMtx` - `ASTBuildOperation::DependencyStampsMtx` - Exception: We allow calls to `getBuferStamp` because that doesn’t claim any locks) - No change was necessary here - `ASTBuildOperation::ConsumersAndResultMtx` - `ASTBuildOperation::CacheMtx` - No change was necessary here - `ASTBuildOperation::ScheduledConsumersMtx` - Make sure we always inform the consumers asynchronously, even if the operation was already cancelled when the consumer got enqueued by introducing `ConsumerNotificationQueue` - Ensure that all callbacks to `SwiftASTConsumer` (i.e. `requestCancellation` and `handlePrimaryAST`, `failed`, `cancelled`) are performed asyncronously from a queue to avoid deadlocks
1 parent 025e919 commit 91f4f24

File tree

2 files changed

+65
-30
lines changed

2 files changed

+65
-30
lines changed

tools/SourceKit/lib/SwiftLang/SwiftASTManager.cpp

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,13 @@ struct SwiftASTManager::Implementation {
604604
WorkQueue ASTBuildQueue{ WorkQueue::Dequeuing::Serial,
605605
"sourcekit.swift.ASTBuilding" };
606606

607+
/// Queue on which consumers may be notified about results and cancellation.
608+
/// This is essentially just a background queue to which we can jump to inform
609+
/// consumers while making sure that no locks are currently claimed.
610+
WorkQueue ConsumerNotificationQueue{
611+
WorkQueue::Dequeuing::Concurrent,
612+
"SwiftASTManager::Implementation::ConsumerNotificationQueue"};
613+
607614
/// Remove all scheduled consumers that don't exist anymore. This is just a
608615
/// garbage-collection operation to make sure the \c ScheduledConsumers vector
609616
/// doesn't explode. One should never make assumptions that all consumers in
@@ -769,9 +776,11 @@ void SwiftASTManager::processASTAsync(
769776
// Cancel any consumers with the same OncePerASTToken.
770777
for (auto ScheduledConsumer : Impl.ScheduledConsumers) {
771778
if (ScheduledConsumer.OncePerASTToken == OncePerASTToken) {
772-
if (auto Consumer = ScheduledConsumer.Consumer.lock()) {
773-
Consumer->requestCancellation();
774-
}
779+
Impl.ConsumerNotificationQueue.dispatch([ScheduledConsumer]() {
780+
if (auto Consumer = ScheduledConsumer.Consumer.lock()) {
781+
Consumer->requestCancellation();
782+
}
783+
});
775784
}
776785
}
777786
}
@@ -781,11 +790,17 @@ void SwiftASTManager::processASTAsync(
781790
Producer->enqueueConsumer(ASTConsumer, fileSystem, shared_from_this());
782791

783792
auto WeakConsumer = SwiftASTConsumerWeakRef(ASTConsumer);
784-
Impl.ReqTracker->setCancellationHandler(CancellationToken, [WeakConsumer] {
785-
if (auto Consumer = WeakConsumer.lock()) {
786-
Consumer->requestCancellation();
787-
}
788-
});
793+
auto WeakThis = std::weak_ptr<SwiftASTManager>(shared_from_this());
794+
Impl.ReqTracker->setCancellationHandler(
795+
CancellationToken, [WeakConsumer, WeakThis] {
796+
if (auto This = WeakThis.lock()) {
797+
This->Impl.ConsumerNotificationQueue.dispatch([WeakConsumer]() {
798+
if (auto Consumer = WeakConsumer.lock()) {
799+
Consumer->requestCancellation();
800+
}
801+
});
802+
}
803+
});
789804
}
790805

791806
void SwiftASTManager::removeCachedAST(SwiftInvocationRef Invok) {
@@ -937,12 +952,14 @@ void ASTBuildOperation::requestConsumerCancellation(
937952
return;
938953
}
939954
Consumers.erase(ConsumerIndex);
940-
Consumer->cancelled();
941955
if (Consumers.empty()) {
942956
// If there are no more consumers waiting for this result, cancel the AST
943957
// build.
944958
CancellationFlag->store(true, std::memory_order_relaxed);
945959
}
960+
ASTManager->Impl.ConsumerNotificationQueue.dispatch([Consumer] {
961+
Consumer->cancelled();
962+
});
946963
}
947964

948965
static void collectModuleDependencies(ModuleDecl *TopMod,
@@ -1014,11 +1031,15 @@ void ASTBuildOperation::informConsumer(SwiftASTConsumerRef Consumer) {
10141031
"more consumers attached to it and should not accept any "
10151032
"new consumers if the build operation was cancelled. Thus "
10161033
"this case should never happen.");
1017-
Consumer->cancelled();
1034+
ASTManager->Impl.ConsumerNotificationQueue.dispatch([Consumer] {
1035+
Consumer->cancelled();
1036+
});
10181037
} else if (Result.AST) {
10191038
Result.AST->Impl.consumeAsync(Consumer, Result.AST);
10201039
} else {
1021-
Consumer->failed(Result.Error);
1040+
ASTManager->Impl.ConsumerNotificationQueue.dispatch([Consumer, Error = Result.Error] {
1041+
Consumer->failed(Error);
1042+
});
10221043
}
10231044
}
10241045

@@ -1136,6 +1157,7 @@ void ASTBuildOperation::schedule(WorkQueue Queue) {
11361157
/*ExpectedOldState=*/State::Running);
11371158
};
11381159

1160+
SmallVector<SwiftASTConsumerRef, 4> ConsumersToCancel;
11391161
{
11401162
llvm::sys::ScopedLock L(ConsumersAndResultMtx);
11411163
if (Consumers.empty()) {
@@ -1146,24 +1168,25 @@ void ASTBuildOperation::schedule(WorkQueue Queue) {
11461168
if (CancellationFlag->load(std::memory_order_relaxed)) {
11471169
assert(false && "We should only set the cancellation flag if there "
11481170
"are no more consumers");
1149-
for (auto &Consumer : Consumers) {
1150-
Consumer->cancelled();
1151-
}
1171+
ConsumersToCancel = Consumers;
11521172
}
11531173
}
1174+
for (auto &Consumer : ConsumersToCancel) {
1175+
Consumer->cancelled();
1176+
}
11541177

11551178
std::string Error;
11561179
assert(!Result && "We should only be producing a result once");
11571180
ASTUnitRef AST = buildASTUnit(Error);
1158-
SmallVector<SwiftASTConsumerRef, 4> LocalConsumers;
1181+
SmallVector<SwiftASTConsumerRef, 4> ConsumersToInform;
11591182
{
11601183
llvm::sys::ScopedLock L(ConsumersAndResultMtx);
11611184
bool WasCancelled = CancellationFlag->load(std::memory_order_relaxed);
11621185
Result.emplace(AST, Error, WasCancelled);
1163-
LocalConsumers = Consumers;
1186+
ConsumersToInform = Consumers;
11641187
Consumers = {};
11651188
}
1166-
for (auto &Consumer : LocalConsumers) {
1189+
for (auto &Consumer : ConsumersToInform) {
11671190
informConsumer(Consumer);
11681191
}
11691192
DidFinishCallback();

tools/SourceKit/lib/SwiftLang/SwiftASTManager.h

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,9 @@ class ASTUnit : public SourceKit::ThreadSafeRefCountedBase<ASTUnit> {
134134
typedef IntrusiveRefCntPtr<ASTUnit> ASTUnitRef;
135135

136136
class SwiftASTConsumer : public std::enable_shared_from_this<SwiftASTConsumer> {
137-
/// Mutex guarding all accesses to CancellationRequestCallback
138-
llvm::sys::Mutex CancellationRequestCallbackMtx;
137+
/// Mutex guarding all accesses to \c CancellationRequestCallback and \c
138+
/// IsCancelled.
139+
llvm::sys::Mutex CancellationRequestCallbackAndIsCancelledMtx;
139140

140141
/// A callback that informs the \c ASTBuildOperation, which is producing the
141142
/// AST for this consumer, that the consumer is no longer of interest. Calling
@@ -159,12 +160,17 @@ class SwiftASTConsumer : public std::enable_shared_from_this<SwiftASTConsumer> {
159160
/// cause the \c ASTBuildOperation to be cancelled if no other consumer is
160161
/// depending on it.
161162
void requestCancellation() {
162-
llvm::sys::ScopedLock L(CancellationRequestCallbackMtx);
163-
IsCancelled = true;
164-
if (CancellationRequestCallback.hasValue()) {
165-
(*CancellationRequestCallback)(shared_from_this());
163+
Optional<std::function<void(std::shared_ptr<SwiftASTConsumer>)>>
164+
CallbackToCall;
165+
{
166+
llvm::sys::ScopedLock L(CancellationRequestCallbackAndIsCancelledMtx);
167+
IsCancelled = true;
168+
CallbackToCall = CancellationRequestCallback;
166169
CancellationRequestCallback = None;
167170
}
171+
if (CallbackToCall.hasValue()) {
172+
(*CallbackToCall)(shared_from_this());
173+
}
168174
}
169175

170176
/// Set a cancellation request callback that informs a \c ASTBuildOperation
@@ -176,20 +182,26 @@ class SwiftASTConsumer : public std::enable_shared_from_this<SwiftASTConsumer> {
176182
/// called, \c NewCallback will be called immediately.
177183
void setCancellationRequestCallback(
178184
std::function<void(std::shared_ptr<SwiftASTConsumer>)> NewCallback) {
179-
llvm::sys::ScopedLock L(CancellationRequestCallbackMtx);
180-
assert(!CancellationRequestCallback.hasValue() &&
181-
"Can't set two cancellation callbacks on a SwiftASTConsumer");
182-
if (IsCancelled) {
185+
bool ShouldCallCallback = false;
186+
{
187+
llvm::sys::ScopedLock L(CancellationRequestCallbackAndIsCancelledMtx);
188+
assert(!CancellationRequestCallback.hasValue() &&
189+
"Can't set two cancellation callbacks on a SwiftASTConsumer");
190+
if (IsCancelled) {
191+
ShouldCallCallback = true;
192+
} else {
193+
CancellationRequestCallback = NewCallback;
194+
}
195+
}
196+
if (ShouldCallCallback) {
183197
NewCallback(shared_from_this());
184-
} else {
185-
CancellationRequestCallback = NewCallback;
186198
}
187199
}
188200

189201
/// Removes the cancellation request callback previously set by \c
190202
/// setCancellationRequestCallback.
191203
void removeCancellationRequestCallback() {
192-
llvm::sys::ScopedLock L(CancellationRequestCallbackMtx);
204+
llvm::sys::ScopedLock L(CancellationRequestCallbackAndIsCancelledMtx);
193205
CancellationRequestCallback = None;
194206
}
195207

0 commit comments

Comments
 (0)