NeonTetra
(Park Byeong Gwan)
1
This code is compiled and have no warning.
even though State is shared between different actor which is unSendable and even not makred as sending.
func bar(isolation actor: isolated any Actor = #isolation) async {
var value = 0
// this is allowed because we are taking copy of Sendable?
await foo(isolation: MainActor.shared) {
return 100
} body: { mutableNumber in
mutableNumber += 100
value += 100
}
// NonSendable is shared between, MainActor and current Actor
await foo(isolation: MainActor.shared) {
return NonSendable()
} body: { unSendable in
value += 100
unSendable.doUnsafe()
unSendable = NonSendable()
}
// mutate TaskGroup from MainActor and current Actor
await foo2 (isolation: MainActor.shared) { group in
value += 100
group.addTask {
}
}
}
func foo<T:~Copyable>(
isolation actor: isolated any Actor = #isolation,
provider: @Sendable () -> sending T,
body: (inout T) async -> Void
) async -> Void {
var ref = provider()
await body(&ref)
}
func foo2(
isolation actor: isolated any Actor = #isolation,
body: (inout TaskGroup<Void>) async -> Void
) async -> Void {
await withTaskGroup(of: Void.self) { group in
// this is actor context
group.addTask {
}
// body is caller's context ( actor != caller )
await body(&group)
}
}
struct NonSendable: ~Copyable { }
@available(*, unavailable)
extension NonSendable: Sendable {
}
for now NonSendable state is shared in Swift 6.
At least in the code above there is no data race in runtime.
I think pattern like above could potentionally make data race, or did I miss something?
Above pattern is inspired by new TaskGroup implementation
Edited for to use valid case since Actor instance method somehow not works
NeonTetra
(Park Byeong Gwan)
2
Still xcode 16 beta 4 prevents this kind of pattern partially. (not fully!)
func bar(isolation actor: isolated any Actor = #isolation) async {
var value = 0
// this is allowed because we are taking copy of Sendable?
await foo(isolation: MainActor.shared) {
return 100
} body: { mutableNumber in
/// why this is allowd?
mutableNumber += 100
value += 100
}
// NonSendable is shared between, MainActor and current Actor
await foo(isolation: MainActor.shared) {
return NonSendable()
} body: { unSendable in
value += 100
unSendable.doUnsafe()
unSendable = NonSendable()
}
// taskGroup is created from MainActor and passed to #actor ?
await foo2 (isolation: MainActor.shared) { group in
value += 100
///Sending 'actor'-isolated value of type '(inout TaskGroup<Void>) async -> Void' with later accesses to main actor-isolated context risks causing data races
let _ = actor
group.addTask {
}
}
}
parameter of foo2 is protected by the MainActor. but local mutable variable value which is isolated to the actor can be accessed within MainActor without any error.