Skip to content

Commit 9aab398

Browse files
committed
[Executors] Specialized message for failed assert between 2 default actors
fix missing default initializer for ExecutorActiveAndRef
1 parent 268c10c commit 9aab398

File tree

3 files changed

+89
-22
lines changed

3 files changed

+89
-22
lines changed

include/swift/Runtime/Concurrency.h

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -897,8 +897,16 @@ TypeNamePair swift_task_getExecutorRefTypeName(ExecutorRef executor);
897897
/// Since the `nullptr, 0` executor ref is a valid "generic executor" reference,
898898
/// when we need to know "was the generic one actually set, or are we just defaulting to it
899899
struct ExecutorActiveAndRef {
900-
bool active;
901-
ExecutorRef ref;
900+
const bool active;
901+
const ExecutorRef ref;
902+
903+
ExecutorActiveAndRef() :
904+
active(false),
905+
ref(ExecutorRef::generic()) {}
906+
907+
ExecutorActiveAndRef(bool active, ExecutorRef ref) :
908+
active(active),
909+
ref(ref) {}
902910
};
903911

904912
/// Different than `swift_task_getCurrentExecutor` in that it does not default to the generic executor.

stdlib/public/Concurrency/ExecutorAssertions.swift

Lines changed: 61 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,7 @@ func preconditionTaskOnExecutor(
4949
let expectationCheck = _taskIsCurrentExecutor(executor.asUnownedSerialExecutor().executor)
5050

5151
precondition(expectationCheck,
52-
"Expected '\(_executorGetTypeName(executor.asUnownedSerialExecutor().executor))' executor, " +
53-
"but was executing on '\(_executorGetCurrentActiveExecutorName())'.",
52+
makeUnexpectedExecutorMessage(executor: executor.asUnownedSerialExecutor().executor, message: message()),
5453
file: file, line: line) // short-cut so we get the exact same failure reporting semantics
5554
}
5655

@@ -86,9 +85,7 @@ func preconditionTaskOnActorExecutor(
8685
let expectationCheck = _taskIsCurrentExecutor(actor.unownedExecutor.executor)
8786

8887
precondition(expectationCheck,
89-
"Expected same executor as actor '\(actor)' " +
90-
"('\(_executorGetTypeName(actor.unownedExecutor.executor))'), " +
91-
"but was executing on '\(_executorGetCurrentActiveExecutorName())'. \(message())",
88+
makeUnexpectedExecutorMessage(actor: actor, message: message()),
9289
file: file, line: line)
9390
}
9491

@@ -123,8 +120,7 @@ func assertTaskOnExecutor(
123120

124121
guard _taskIsCurrentExecutor(executor.asUnownedSerialExecutor().executor) else {
125122
assertionFailure(
126-
"Expected '\(_executorGetTypeName(executor.asUnownedSerialExecutor().executor))' executor, " +
127-
"but was executing on '\(_executorGetCurrentActiveExecutorName())'.",
123+
makeUnexpectedExecutorMessage(executor: executor.asUnownedSerialExecutor().executor, message: message()),
128124
file: file, line: line)
129125
return
130126
}
@@ -158,12 +154,9 @@ func assertTaskOnActorExecutor(
158154
}
159155

160156
guard _taskIsCurrentExecutor(actor.unownedExecutor.executor) else {
161-
let msg =
162-
"Expected same executor as actor '\(actor)' " +
163-
"('\(_executorGetTypeName(actor.unownedExecutor.executor))'), " +
164-
"but was executing on '\(_executorGetCurrentActiveExecutorName())'. " +
165-
"\(message())";
166-
assertionFailure(msg, file: file, line: line) // short-cut so we get the exact same failure reporting semantics
157+
assertionFailure(
158+
makeUnexpectedExecutorMessage(actor: actor, message: message()),
159+
file: file, line: line)
167160
return
168161
}
169162
}
@@ -200,8 +193,7 @@ func assumeOnMainActorExecutor<T>(
200193

201194
guard _taskIsCurrentExecutor(mainExecutor) else {
202195
fatalError(
203-
"Expected '\(_executorGetTypeName(mainExecutor))' executor, " +
204-
"but was executing on '\(_executorGetCurrentActiveExecutorName())'.",
196+
makeUnexpectedExecutorMessage(executor: mainExecutor, message: nil),
205197
file: file, line: line)
206198
}
207199

@@ -241,10 +233,7 @@ func assumeOnActorExecutor<Act: Actor, T>(
241233
/// as this is our "safe" version of this API.
242234
let executor: Builtin.Executor = actor.unownedExecutor.executor
243235
guard _taskIsCurrentExecutor(executor) else {
244-
fatalError(
245-
"Expected same executor as actor '\(actor)' " +
246-
"('\(_executorGetTypeName(actor.unownedExecutor.executor))'), " +
247-
"but was executing on '\(_executorGetCurrentActiveExecutorName())'.",
236+
fatalError(makeUnexpectedExecutorMessage(actor: actor, message: nil),
248237
file: file, line: line)
249238
}
250239

@@ -260,7 +249,7 @@ func assumeOnActorExecutor<Act: Actor, T>(
260249
/// I.e. if we're not executing within a task, we expect to have `<unknown>` executor rather than the generic one.
261250
@usableFromInline
262251
@available(SwiftStdlib 5.9, *)
263-
func _executorGetCurrentActiveExecutorName() -> String {
252+
internal func _executorGetCurrentActiveExecutorName() -> String {
264253
let (wasActive, ref) = _executorGetCurrentActiveExecutorRef()
265254
guard wasActive else {
266255
return "<unknown>"
@@ -269,5 +258,57 @@ func _executorGetCurrentActiveExecutorName() -> String {
269258
return _executorGetTypeName(ref)
270259
}
271260

261+
@usableFromInline
262+
@available(SwiftStdlib 5.9, *)
263+
internal func makeUnexpectedExecutorMessage(actor: some Actor, message: String?) -> String {
264+
let expectedExecutor = _executorGetTypeName(actor.unownedExecutor.executor)
265+
let gotExecutor = _executorGetCurrentActiveExecutorName()
266+
267+
let sameExecutorNameClarification: String
268+
if expectedExecutor == gotExecutor {
269+
sameExecutorNameClarification = " of a different actor"
270+
} else {
271+
sameExecutorNameClarification = ""
272+
}
273+
274+
let trailingMessage: String
275+
if let message {
276+
trailingMessage = " \(message)"
277+
} else {
278+
trailingMessage = ""
279+
}
280+
281+
return "Expected same executor as actor '\(actor)' " +
282+
"('\(expectedExecutor)'), " +
283+
"but was executing on '\(gotExecutor)'" +
284+
"\(sameExecutorNameClarification)." +
285+
"\(trailingMessage)"
286+
}
287+
288+
@usableFromInline
289+
@available(SwiftStdlib 5.9, *)
290+
internal func makeUnexpectedExecutorMessage(executor: Builtin.Executor, message: String?) -> String {
291+
let expectedExecutor = _executorGetTypeName(executor)
292+
let gotExecutor = _executorGetCurrentActiveExecutorName()
293+
294+
let sameExecutorNameClarification: String
295+
if expectedExecutor == gotExecutor {
296+
sameExecutorNameClarification = " of a different actor"
297+
} else {
298+
sameExecutorNameClarification = ""
299+
}
300+
301+
let trailingMessage: String
302+
if let message {
303+
trailingMessage = " \(message)"
304+
} else {
305+
trailingMessage = ""
306+
}
307+
308+
return "Expected '\(expectedExecutor)' executor, " +
309+
"but was executing on '\(gotExecutor)'" +
310+
"\(sameExecutorNameClarification)."
311+
}
312+
272313
// TODO(ktoso): implement assume for distributed actors as well
273314
#endif // not SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY

test/Concurrency/Runtime/actor_assume_executor_general.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,15 @@ actor DefaultExecutorSomeone {
6161
}
6262
}
6363

64+
actor DefaultExecutorSomeoneElse {
65+
func checkSameExecutorAs(other: DefaultExecutorSomeone) {
66+
// this is expected to fail
67+
assumeOnActorExecutor(other) { isolatedOther in
68+
let s: String = isolatedOther.something // ok, if the runtime check were to pass
69+
}
70+
}
71+
}
72+
6473
actor DefaultExecutorSomeonesFriend {
6574
let someone: DefaultExecutorSomeone
6675
nonisolated var unownedExecutor: UnownedSerialExecutor {
@@ -175,6 +184,15 @@ final class MainActorEcho {
175184
expectCrashLater(withMessage: "Expected same executor as actor 'Swift.MainActor' ('MainActorExecutor'), but was executing on 'a.InlineExecutor'.")
176185
await InlineExecutorActor(someone: someone).checkMainActor()
177186
}
187+
188+
tests.test("assumeOnActorExecutor: wrongly assume 'same executor' as a different default actor, from a different default actor") {
189+
// TODO: this message is not very helpful, and we should try to print the exact default actor we executed on,
190+
// however, this is tricky due to the fact a default actor can be destroyed while executing on it, so we
191+
// must take extra care for how we'd retain and print such default actor. rdar://106487242
192+
expectCrashLater(withMessage: "Expected same executor as actor 'a.DefaultExecutorSomeone' ('DefaultActorExecutor'), but was executing on 'DefaultActorExecutor' of a different actor.")
193+
let someone = DefaultExecutorSomeone()
194+
await DefaultExecutorSomeoneElse().checkSameExecutorAs(other: someone)
195+
}
178196
}
179197

180198
await runAllTestsAsync()

0 commit comments

Comments
 (0)