Skip to content

Commit f90b4ff

Browse files
committed
[Executors] improved executor precondition messages
1 parent 7bfa061 commit f90b4ff

File tree

11 files changed

+308
-56
lines changed

11 files changed

+308
-56
lines changed

include/swift/Runtime/Concurrency.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -891,6 +891,19 @@ ExecutorRef swift_task_getMainExecutor(void);
891891
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
892892
bool swift_task_isCurrentExecutor(ExecutorRef executor);
893893

894+
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
895+
TypeNamePair swift_task_getExecutorRefTypeName(ExecutorRef executor);
896+
897+
/// Since the `nullptr, 0` executor ref is a valid "generic executor" reference,
898+
/// when we need to know "was the generic one actually set, or are we just defaulting to it
899+
struct ExecutorActiveAndRef {
900+
bool active;
901+
ExecutorRef ref;
902+
};
903+
904+
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
905+
ExecutorActiveAndRef swift_task_getCurrentActiveExecutorRef();
906+
894907
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
895908
void swift_task_reportUnexpectedExecutor(
896909
const unsigned char *file, uintptr_t fileLength, bool fileIsASCII,

stdlib/public/CompatibilityOverride/CompatibilityOverrideConcurrency.def

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,14 @@ OVERRIDE_ACTOR(task_isCurrentExecutor, bool,
124124
SWIFT_EXPORT_FROM(swift_Concurrency), SWIFT_CC(swift),
125125
swift::, (ExecutorRef executor), (executor))
126126

127+
OVERRIDE_ACTOR(task_getExecutorRefTypeName, TypeNamePair,
128+
SWIFT_EXPORT_FROM(swift_Concurrency), SWIFT_CC(swift),
129+
swift::, (ExecutorRef executor), (executor))
130+
131+
OVERRIDE_ACTOR(task_getCurrentActiveExecutorRef, ExecutorActiveAndRef,
132+
SWIFT_EXPORT_FROM(swift_Concurrency), SWIFT_CC(swift),
133+
swift::, ,)
134+
127135
OVERRIDE_ACTOR(task_switch, void,
128136
SWIFT_EXPORT_FROM(swift_Concurrency), SWIFT_CC(swiftasync),
129137
swift::, (SWIFT_ASYNC_CONTEXT AsyncContext *resumeToContext,

stdlib/public/Concurrency/Actor.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,15 +295,47 @@ JobPriority swift::swift_task_getCurrentThreadPriority() {
295295
#endif
296296
}
297297

298+
SWIFT_CC(swift)
299+
static TypeNamePair swift_task_getExecutorRefTypeNameImpl(ExecutorRef ref) {
300+
TypeNamePair executorName;
301+
if (ref.isDefaultActor()) {
302+
auto defaultActorExecutorName = "DefaultActorExecutor";
303+
executorName = TypeNamePair{defaultActorExecutorName, strlen(defaultActorExecutorName)};
304+
} else if (ref.isMainExecutor()) {
305+
auto mainActorExecutorName = "MainActorExecutor";
306+
executorName = TypeNamePair{mainActorExecutorName, strlen(mainActorExecutorName)};
307+
} else if (ref.isGeneric()) {
308+
auto genericExecutorName = "GenericExecutor"; // TODO: what's a better name for it?
309+
executorName = TypeNamePair{genericExecutorName, strlen(genericExecutorName)};
310+
} else {
311+
HeapObject * identity = ref.getIdentity();
312+
assert(identity);
313+
auto *metadata = identity->metadata;
314+
executorName = swift_getTypeName(metadata, /*qualified=*/true);
315+
}
316+
317+
return executorName;
318+
}
319+
298320
SWIFT_CC(swift)
299321
static bool swift_task_isCurrentExecutorImpl(ExecutorRef executor) {
300322
if (auto currentTracking = ExecutorTrackingInfo::current()) {
301323
return currentTracking->getActiveExecutor() == executor;
302324
}
303325

326+
// TODO(ktoso): wrong assumption; we can be on the main queue without being on main thread
304327
return executor.isMainExecutor() && isExecutingOnMainThread();
305328
}
306329

330+
SWIFT_CC(swift)
331+
static ExecutorActiveAndRef swift_task_getCurrentActiveExecutorRefImpl() {
332+
if (auto currentTracking = ExecutorTrackingInfo::current()) {
333+
return ExecutorActiveAndRef{true, currentTracking->getActiveExecutor()}; // FIXME; special case main and default
334+
}
335+
336+
return ExecutorActiveAndRef{false, ExecutorRef::generic()};
337+
}
338+
307339
/// Logging level for unexpected executors:
308340
/// 0 - no logging
309341
/// 1 - warn on each instance

stdlib/public/Concurrency/ExecutorAssertions.swift

Lines changed: 49 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ import SwiftShims
3333
/// programming error.
3434
///
3535
/// - Parameter executor: the expected current executor
36-
@available(SwiftStdlib 5.9, *) // FIXME: use @backDeploy(before: SwiftStdlib 5.9)
36+
@available(SwiftStdlib 5.9, *)
37+
@_transparent
3738
public
3839
func preconditionTaskOnExecutor(
3940
_ executor: some SerialExecutor,
@@ -46,10 +47,9 @@ func preconditionTaskOnExecutor(
4647

4748
let expectationCheck = _taskIsCurrentExecutor(executor.asUnownedSerialExecutor().executor)
4849

49-
/// TODO: implement the logic in-place perhaps rather than delegating to precondition()?
5050
precondition(expectationCheck,
51-
// TODO: offer information which executor we actually got
52-
"Incorrect actor executor assumption; Expected '\(executor)' executor. \(message())",
51+
"Expected '\(_executorGetTypeName(executor.asUnownedSerialExecutor().executor))' executor, " +
52+
"but was executing on '\(_executorGetCurrentActiveExecutorName())'.",
5353
file: file, line: line) // short-cut so we get the exact same failure reporting semantics
5454
}
5555

@@ -70,7 +70,8 @@ func preconditionTaskOnExecutor(
7070
/// programming error.
7171
///
7272
/// - Parameter actor: the actor whose serial executor we expect to be the current executor
73-
@available(SwiftStdlib 5.9, *) // FIXME: use @backDeploy(before: SwiftStdlib 5.9)
73+
@available(SwiftStdlib 5.9, *)
74+
@_transparent
7475
public
7576
func preconditionTaskOnActorExecutor(
7677
_ actor: some Actor,
@@ -83,10 +84,10 @@ func preconditionTaskOnActorExecutor(
8384

8485
let expectationCheck = _taskIsCurrentExecutor(actor.unownedExecutor.executor)
8586

86-
// TODO: offer information which executor we actually got
8787
precondition(expectationCheck,
88-
// TODO: figure out a way to get the typed repr out of the unowned executor
89-
"Incorrect actor executor assumption; Expected '\(actor.unownedExecutor)' executor. \(message())",
88+
"Expected same executor as actor '\(actor)' " +
89+
"('\(_executorGetTypeName(actor.unownedExecutor.executor))'), " +
90+
"but was executing on '\(_executorGetCurrentActiveExecutorName())'. \(message())",
9091
file: file, line: line)
9192
}
9293

@@ -107,7 +108,8 @@ func preconditionTaskOnActorExecutor(
107108
/// assumption is a serious programming error.
108109
///
109110
/// - Parameter executor: the expected current executor
110-
@available(SwiftStdlib 5.9, *) // FIXME: use @backDeploy(before: SwiftStdlib 5.9)
111+
@available(SwiftStdlib 5.9, *)
112+
@_transparent
111113
public
112114
func assertTaskOnExecutor(
113115
_ executor: some SerialExecutor,
@@ -119,10 +121,10 @@ func assertTaskOnExecutor(
119121
}
120122

121123
guard _taskIsCurrentExecutor(executor.asUnownedSerialExecutor().executor) else {
122-
// TODO: offer information which executor we actually got
123-
let msg = "Incorrect actor executor assumption; Expected '\(executor)' executor. \(message())"
124-
/// TODO: implement the logic in-place perhaps rather than delegating to precondition()?
125-
assertionFailure(msg, file: file, line: line)
124+
assertionFailure(
125+
"Expected '\(_executorGetTypeName(executor.asUnownedSerialExecutor().executor))' executor, " +
126+
"but was executing on '\(_executorGetCurrentActiveExecutorName())'.",
127+
file: file, line: line)
126128
return
127129
}
128130
}
@@ -142,7 +144,8 @@ func assertTaskOnExecutor(
142144
///
143145
///
144146
/// - Parameter actor: the actor whose serial executor we expect to be the current executor
145-
@available(SwiftStdlib 5.9, *) // FIXME: use @backDeploy(before: SwiftStdlib 5.9)
147+
@available(SwiftStdlib 5.9, *)
148+
@_transparent
146149
public
147150
func assertTaskOnActorExecutor(
148151
_ actor: some Actor,
@@ -154,10 +157,11 @@ func assertTaskOnActorExecutor(
154157
}
155158

156159
guard _taskIsCurrentExecutor(actor.unownedExecutor.executor) else {
157-
// TODO: offer information which executor we actually got
158-
// TODO: figure out a way to get the typed repr out of the unowned executor
159-
let msg = "Incorrect actor executor assumption; Expected '\(actor.unownedExecutor)' executor. \(message())"
160-
/// TODO: implement the logic in-place perhaps rather than delegating to precondition()?
160+
let msg =
161+
"Expected same executor as actor '\(actor)' " +
162+
"('\(_executorGetTypeName(actor.unownedExecutor.executor))'), " +
163+
"but was executing on '\(_executorGetCurrentActiveExecutorName())'. " +
164+
"\(message())";
161165
assertionFailure(msg, file: file, line: line) // short-cut so we get the exact same failure reporting semantics
162166
return
163167
}
@@ -179,7 +183,7 @@ func assertTaskOnActorExecutor(
179183
/// if another actor uses the same serial executor--by using ``MainActor/sharedUnownedExecutor``
180184
/// as its own ``Actor/unownedExecutor``--this check will succeed, as from a concurrency safety
181185
/// perspective, the serial executor guarantees mutual exclusion of those two actors.
182-
@available(SwiftStdlib 5.9, *) // FIXME: use @backDeploy(before: SwiftStdlib 5.9)
186+
@available(SwiftStdlib 5.9, *)
183187
@_unavailableFromAsync(message: "await the call to the @MainActor closure directly")
184188
public
185189
func assumeOnMainActorExecutor<T>(
@@ -191,9 +195,13 @@ func assumeOnMainActorExecutor<T>(
191195

192196
/// This is guaranteed to be fatal if the check fails,
193197
/// as this is our "safe" version of this API.
194-
guard _taskIsCurrentExecutor(Builtin.buildMainActorExecutorRef()) else {
195-
// TODO: offer information which executor we actually got
196-
fatalError("Incorrect actor executor assumption; Expected 'MainActor' executor.", file: file, line: line)
198+
let mainExecutor = Builtin.buildMainActorExecutorRef()
199+
200+
guard _taskIsCurrentExecutor(mainExecutor) else {
201+
fatalError(
202+
"Expected '\(_executorGetTypeName(mainExecutor))' executor, " +
203+
"but was executing on '\(_executorGetCurrentActiveExecutorName())'.",
204+
file: file, line: line)
197205
}
198206

199207
// To do the unsafe cast, we have to pretend it's @escaping.
@@ -217,7 +225,7 @@ func assumeOnMainActorExecutor<T>(
217225
/// if another actor uses the same serial executor--by using that actor's ``Actor/unownedExecutor``
218226
/// as its own ``Actor/unownedExecutor``--this check will succeed, as from a concurrency safety
219227
/// perspective, the serial executor guarantees mutual exclusion of those two actors.
220-
@available(SwiftStdlib 5.9, *) // FIXME: use @backDeploy(before: SwiftStdlib 5.9)
228+
@available(SwiftStdlib 5.9, *)
221229
@_unavailableFromAsync(message: "express the closure as an explicit function declared on the specified 'actor' instead")
222230
public
223231
func assumeOnActorExecutor<Act: Actor, T>(
@@ -232,8 +240,11 @@ func assumeOnActorExecutor<Act: Actor, T>(
232240
/// as this is our "safe" version of this API.
233241
let executor: Builtin.Executor = actor.unownedExecutor.executor
234242
guard _taskIsCurrentExecutor(executor) else {
235-
// TODO: offer information which executor we actually got
236-
fatalError("Incorrect actor executor assumption; Expected same executor as \(actor).", file: file, line: line)
243+
fatalError(
244+
"Expected same executor as actor '\(actor)' " +
245+
"('\(_executorGetTypeName(actor.unownedExecutor.executor))'), " +
246+
"but was executing on '\(_executorGetCurrentActiveExecutorName())'.",
247+
file: file, line: line)
237248
}
238249

239250
// To do the unsafe cast, we have to pretend it's @escaping.
@@ -244,4 +255,17 @@ func assumeOnActorExecutor<Act: Actor, T>(
244255
}
245256
}
246257

258+
/// Specifically get the name of the current *active* executor, without falling back to assuming the generic one.
259+
/// I.e. if we're not executing within a task, we expect to have `<unknown>` executor rather than the generic one.
260+
@usableFromInline
261+
@available(SwiftStdlib 5.9, *)
262+
func _executorGetCurrentActiveExecutorName() -> String {
263+
let (wasActive, ref) = _executorGetCurrentActiveExecutorRef()
264+
guard wasActive else {
265+
return "<unknown>"
266+
}
267+
268+
return _executorGetTypeName(ref)
269+
}
270+
247271
// TODO(ktoso): implement assume for distributed actors as well

stdlib/public/Concurrency/Task.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -978,6 +978,30 @@ func _taskCreateNullaryContinuationJob(priority: Int, continuation: Builtin.RawU
978978
@_silgen_name("swift_task_isCurrentExecutor")
979979
func _taskIsCurrentExecutor(_ executor: Builtin.Executor) -> Bool
980980

981+
@available(SwiftStdlib 5.9, *)
982+
@usableFromInline
983+
@_silgen_name("swift_task_getExecutorRefTypeName")
984+
func _executorGetTypeNameRaw(_ executor: Builtin.Executor) -> (UnsafePointer<UInt8>?, Int)
985+
986+
@available(SwiftStdlib 5.9, *)
987+
@usableFromInline
988+
func _executorGetTypeName(_ executor: Builtin.Executor) -> String {
989+
let (stringPtr, count) = _executorGetTypeNameRaw(executor)
990+
guard let stringPtr else {
991+
return "<unknown>"
992+
}
993+
994+
// FIXME: can't use this since it is internal in stdlib and we're in concurrency
995+
// return String._fromUTF8Repairing(
996+
// UnsafeBufferPointer(start: stringPtr, count: count)).0
997+
return String(cString: stringPtr)
998+
}
999+
1000+
@available(SwiftStdlib 5.9, *)
1001+
@usableFromInline
1002+
@_silgen_name("swift_task_getCurrentActiveExecutorRef")
1003+
func _executorGetCurrentActiveExecutorRef() -> (Bool, Builtin.Executor)
1004+
9811005
@available(SwiftStdlib 5.1, *)
9821006
@usableFromInline
9831007
@_silgen_name("swift_task_reportUnexpectedExecutor")

stdlib/public/Concurrency/TaskGroup.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,7 @@ public struct ThrowingTaskGroup<ChildTaskResult: Sendable, Failure: Error> {
632632
/// } catch {
633633
/// // other errors though we print and cancel the group,
634634
/// // and all of the remaining child tasks within it.
635+
/// print("Error: \(error)")
635636
/// group.cancelAll()
636637
/// }
637638
/// }

test/Concurrency/Runtime/actor_assert_precondition_executor.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ actor Someone {
7373
}
7474

7575
tests.test("preconditionTaskOnActorExecutor(main): wrongly assume the main executor, from actor on other executor") {
76-
expectCrashLater(withMessage: "Incorrect actor executor assumption; Expected 'MainActor' executor.")
76+
expectCrashLater(withMessage: "Expected same executor as actor 'Swift.MainActor' ('MainActorExecutor'), but was executing on 'DefaultActorExecutor'.")
7777
await Someone().callCheckMainActor()
7878
}
7979

0 commit comments

Comments
 (0)