Skip to content

Commit 23e21ff

Browse files
authored
Merge pull request #81206 from mikeash/fix-statusRecordLock-6.2
[6.2][Concurrency] Fix alreadyLocked in withStatusRecordLock.
2 parents c072992 + a44dadd commit 23e21ff

File tree

3 files changed

+50
-4
lines changed

3 files changed

+50
-4
lines changed

stdlib/public/Concurrency/TaskPrivate.h

+4-3
Original file line numberDiff line numberDiff line change
@@ -829,11 +829,12 @@ struct AsyncTask::PrivateStorage {
829829
// result of the task
830830
auto oldStatus = task->_private()._status().load(std::memory_order_relaxed);
831831
while (true) {
832-
// Task is completing
833832
assert(oldStatus.getInnermostRecord() == NULL &&
834833
"Status records should have been removed by this time!");
835-
assert(!oldStatus.isStatusRecordLocked() &&
836-
"Task is completing, cannot be locked anymore!");
834+
835+
// Don't assert !isStatusRecordLocked(). It can be legitimately true here,
836+
// for example if another thread is canceling this task right as it
837+
// completes.
837838

838839
assert(oldStatus.isRunning());
839840

stdlib/public/Concurrency/TaskStatus.cpp

+13-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,19 @@ static void withStatusRecordLock(
6767
// see that the is-locked bit is now set, and then wait for the lock.
6868
task->_private().statusLock.lock();
6969

70-
bool alreadyLocked = status.isStatusRecordLocked();
70+
bool alreadyLocked = false;
71+
72+
// `status` was loaded before we acquired the lock. If its is-locked bit is
73+
// not set, then we know that this thread doesn't already hold the lock.
74+
// However, if the is-locked bit is set, then we don't know if this thread
75+
// held the lock or another thread did. In that case, we reload the status
76+
// after acquiring the lock. If the reloaded status still has the is-locked
77+
// bit set, then we know it's this thread. If it doesn't, then we know it was
78+
// a different thread.
79+
if (status.isStatusRecordLocked()) {
80+
status = task->_private()._status().load(std::memory_order_relaxed);
81+
alreadyLocked = status.isStatusRecordLocked();
82+
}
7183

7284
// If it's already locked then this thread is the thread that locked it, and
7385
// we can leave that bit alone here.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// RUN: %target-run-simple-swift( -target %target-swift-5.1-abi-triple %import-libdispatch)
2+
// REQUIRES: concurrency
3+
// REQUIRES: executable_test
4+
5+
// REQUIRES: concurrency_runtime
6+
// UNSUPPORTED: freestanding
7+
8+
func recurseABunch(_ call: () async throws -> Void, n: Int = 100) async throws {
9+
try await withTaskCancellationHandler {
10+
if n == 0 {
11+
try await call()
12+
return
13+
}
14+
15+
try await recurseABunch(call, n: n - 1)
16+
} onCancel: {
17+
}
18+
}
19+
20+
for _ in 0..<100_000 {
21+
let task: Task<Void, any Error> = Task {
22+
try await Task.sleep(nanoseconds: UInt64.random(in: 0..<1_000))
23+
try await recurseABunch() {
24+
await withTaskCancellationHandler {
25+
} onCancel: {
26+
}
27+
}
28+
}
29+
Task {
30+
try await Task.sleep(nanoseconds: UInt64.random(in: 0..<1_000))
31+
task.cancel()
32+
}
33+
}

0 commit comments

Comments
 (0)