Skip to content

Commit 268c10c

Browse files
committed
[Executors] improved executor precondition messages
document the get active current executor method
1 parent 37d242c commit 268c10c

File tree

11 files changed

+310
-57
lines changed

11 files changed

+310
-57
lines changed

include/swift/Runtime/Concurrency.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -891,6 +891,22 @@ 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+
/// Different than `swift_task_getCurrentExecutor` in that it does not default to the generic executor.
905+
/// This method is to be used if we are interested specifically IF we have an executor set in this context,
906+
/// and if so, which one it is. If no executor is available in the current context, the `active` bool will be false.
907+
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
908+
ExecutorActiveAndRef swift_task_getCurrentActiveExecutorRef(void);
909+
894910
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
895911
void swift_task_reportUnexpectedExecutor(
896912
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
@@ -34,7 +34,8 @@ import SwiftShims
3434
/// programming error.
3535
///
3636
/// - Parameter executor: the expected current executor
37-
@available(SwiftStdlib 5.9, *) // FIXME: use @backDeploy(before: SwiftStdlib 5.9)
37+
@available(SwiftStdlib 5.9, *)
38+
@_transparent
3839
public
3940
func preconditionTaskOnExecutor(
4041
_ executor: some SerialExecutor,
@@ -47,10 +48,9 @@ func preconditionTaskOnExecutor(
4748

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

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

@@ -71,7 +71,8 @@ func preconditionTaskOnExecutor(
7171
/// programming error.
7272
///
7373
/// - Parameter actor: the actor whose serial executor we expect to be the current executor
74-
@available(SwiftStdlib 5.9, *) // FIXME: use @backDeploy(before: SwiftStdlib 5.9)
74+
@available(SwiftStdlib 5.9, *)
75+
@_transparent
7576
public
7677
func preconditionTaskOnActorExecutor(
7778
_ actor: some Actor,
@@ -84,10 +85,10 @@ func preconditionTaskOnActorExecutor(
8485

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

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

@@ -108,7 +109,8 @@ func preconditionTaskOnActorExecutor(
108109
/// assumption is a serious programming error.
109110
///
110111
/// - Parameter executor: the expected current executor
111-
@available(SwiftStdlib 5.9, *) // FIXME: use @backDeploy(before: SwiftStdlib 5.9)
112+
@available(SwiftStdlib 5.9, *)
113+
@_transparent
112114
public
113115
func assertTaskOnExecutor(
114116
_ executor: some SerialExecutor,
@@ -120,10 +122,10 @@ func assertTaskOnExecutor(
120122
}
121123

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

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

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

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

240251
// To do the unsafe cast, we have to pretend it's @escaping.
@@ -245,5 +256,18 @@ func assumeOnActorExecutor<Act: Actor, T>(
245256
}
246257
}
247258

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

stdlib/public/Concurrency/Task.swift

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

1026+
@available(SwiftStdlib 5.9, *)
1027+
@usableFromInline
1028+
@_silgen_name("swift_task_getExecutorRefTypeName")
1029+
func _executorGetTypeNameRaw(_ executor: Builtin.Executor) -> (UnsafePointer<UInt8>?, Int)
1030+
1031+
@available(SwiftStdlib 5.9, *)
1032+
@usableFromInline
1033+
func _executorGetTypeName(_ executor: Builtin.Executor) -> String {
1034+
let (stringPtr, count) = _executorGetTypeNameRaw(executor)
1035+
guard let stringPtr else {
1036+
return "<unknown>"
1037+
}
1038+
1039+
// FIXME: can't use this since it is internal in stdlib and we're in concurrency
1040+
// return String._fromUTF8Repairing(
1041+
// UnsafeBufferPointer(start: stringPtr, count: count)).0
1042+
return String(cString: stringPtr)
1043+
}
1044+
1045+
@available(SwiftStdlib 5.9, *)
1046+
@usableFromInline
1047+
@_silgen_name("swift_task_getCurrentActiveExecutorRef")
1048+
func _executorGetCurrentActiveExecutorRef() -> (Bool, Builtin.Executor)
1049+
10261050
@available(SwiftStdlib 5.1, *)
10271051
@usableFromInline
10281052
@_silgen_name("swift_task_reportUnexpectedExecutor")

stdlib/public/Concurrency/TaskGroup.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -632,7 +632,6 @@ 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)")
636635
/// group.cancelAll()
637636
/// }
638637
/// }

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)