mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
406 lines
14 KiB
Swift
406 lines
14 KiB
Swift
// RUN: %empty-directory(%t)
|
|
// RUN: %target-build-swift -target %target-future-triple %import-libdispatch -parse-stdlib -parse-as-library -module-name=main %s -o %t/a.out
|
|
// RUN: %target-codesign %t/a.out
|
|
// RUN: %env-SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE=legacy %target-run %t/a.out | %FileCheck %s
|
|
|
|
// REQUIRES: libdispatch
|
|
// REQUIRES: executable_test
|
|
// REQUIRES: concurrency
|
|
// REQUIRES: distributed
|
|
|
|
// UNSUPPORTED: use_os_stdlib
|
|
// UNSUPPORTED: back_deployment_runtime
|
|
|
|
// FIXME(distributed): Distributed actors currently have some issues on windows, isRemote always returns false. rdar://82593574
|
|
// UNSUPPORTED: OS=windows-msvc
|
|
|
|
import Swift
|
|
import _Concurrency
|
|
import Distributed
|
|
import Dispatch
|
|
|
|
#if canImport(Darwin)
|
|
import Darwin
|
|
typealias ThreadID = pthread_t
|
|
func getCurrentThreadID() -> ThreadID { pthread_self() }
|
|
func equalThreadIDs(_ a: ThreadID, _ b: ThreadID) -> Bool { pthread_equal(a, b) != 0 }
|
|
#elseif canImport(Glibc)
|
|
import Glibc
|
|
typealias ThreadID = pthread_t
|
|
func getCurrentThreadID() -> ThreadID { pthread_self() }
|
|
func equalThreadIDs(_ a: ThreadID, _ b: ThreadID) -> Bool { pthread_equal(a, b) != 0 }
|
|
#elseif os(Windows)
|
|
import WinSDK
|
|
typealias ThreadID = UInt32
|
|
func getCurrentThreadID() -> ThreadID { GetCurrentThreadId() }
|
|
func equalThreadIDs(_ a: ThreadID, _ b: ThreadID) -> Bool { a == b }
|
|
#endif
|
|
|
|
var mainThread: ThreadID?
|
|
func isMainThread() -> Bool {
|
|
return equalThreadIDs(getCurrentThreadID(), mainThread!)
|
|
}
|
|
|
|
@_silgen_name("swift_task_isCurrentExecutor")
|
|
private func isCurrentExecutor(_ executor: Builtin.Executor) -> Bool
|
|
|
|
func getExecutor(_ a: AnyActor) -> Builtin.Executor {
|
|
let pack = (a, UnsafeRawPointer?.none)
|
|
return unsafeBitCast(pack, to: Builtin.Executor.self)
|
|
}
|
|
|
|
func isCurrent(_ a: AnyActor) -> Bool {
|
|
return isCurrentExecutor(getExecutor(a))
|
|
}
|
|
|
|
func isMainExecutor() -> Bool {
|
|
isCurrentExecutor(Builtin.buildMainActorExecutorRef())
|
|
}
|
|
|
|
actor A {}
|
|
|
|
distributed actor DA {
|
|
init(system: FakeActorSystem) {
|
|
self.actorSystem = system
|
|
}
|
|
}
|
|
|
|
distributed actor DA_userDefined {
|
|
init(system: FakeActorSystem) {
|
|
self.actorSystem = system
|
|
}
|
|
|
|
nonisolated deinit {}
|
|
}
|
|
|
|
distributed actor DA_userDefined_nonisolated {
|
|
init(system: FakeActorSystem) {
|
|
self.actorSystem = system
|
|
}
|
|
|
|
nonisolated deinit {
|
|
print("Deinitializing \(self.id) remote:\(__isRemoteActor(self)) isolated:\(isCurrent(self)) mainThread:\(isMainThread())")
|
|
}
|
|
}
|
|
|
|
distributed actor DA_userDefined_isolated {
|
|
init(system: FakeActorSystem) {
|
|
self.actorSystem = system
|
|
}
|
|
|
|
isolated deinit {
|
|
print("Deinitializing \(self.id) remote:\(__isRemoteActor(self)) isolated:\(isCurrent(self)) mainThread:\(isMainThread())")
|
|
}
|
|
}
|
|
|
|
distributed actor DA_state_nonisolated {
|
|
var name: String
|
|
var age: Int
|
|
|
|
init(name: String, age: Int, system: FakeActorSystem) {
|
|
self.name = name
|
|
self.age = age
|
|
self.actorSystem = system
|
|
}
|
|
|
|
nonisolated deinit {
|
|
print("Deinitializing \(self.id) name=\(name) age=\(age) remote:\(__isRemoteActor(self)) isolated:\(isCurrent(self)) mainThread:\(isMainThread())")
|
|
return
|
|
}
|
|
}
|
|
|
|
distributed actor DA_state_isolated {
|
|
var name: String
|
|
var age: Int
|
|
|
|
init(name: String, age: Int, system: FakeActorSystem) {
|
|
self.name = name
|
|
self.age = age
|
|
self.actorSystem = system
|
|
}
|
|
|
|
isolated deinit {
|
|
print("Deinitializing \(self.id) name=\(name) age=\(age) remote:\(__isRemoteActor(self)) isolated:\(isCurrent(self)) mainThread:\(isMainThread())")
|
|
return
|
|
}
|
|
}
|
|
|
|
@globalActor actor AnotherActor: GlobalActor {
|
|
static let shared = AnotherActor()
|
|
}
|
|
|
|
distributed actor DA_state_isolated_on_another {
|
|
let name: String
|
|
let age: Int
|
|
|
|
init(name: String, age: Int, system: FakeActorSystem) {
|
|
self.name = name
|
|
self.age = age
|
|
self.actorSystem = system
|
|
}
|
|
|
|
@AnotherActor
|
|
deinit {
|
|
print("Deinitializing \(self.id) name=\(name) age=\(age) remote:\(__isRemoteActor(self)) isolated-self:\(isCurrent(self)) isolated-other:\(isCurrent(AnotherActor.shared)) mainThread:\(isMainThread())")
|
|
return
|
|
}
|
|
}
|
|
|
|
// ==== Fake Transport ---------------------------------------------------------
|
|
|
|
struct ActorAddress: Sendable, Hashable, Codable {
|
|
let address: String
|
|
init(parse address : String) {
|
|
self.address = address
|
|
}
|
|
}
|
|
|
|
final class FakeActorSystem: @unchecked Sendable, DistributedActorSystem {
|
|
typealias ActorID = ActorAddress
|
|
typealias SerializationRequirement = Codable
|
|
typealias InvocationDecoder = FakeDistributedInvocationEncoder
|
|
typealias InvocationEncoder = FakeDistributedInvocationEncoder
|
|
typealias ResultHandler = FakeResultHandler
|
|
|
|
var n = 0
|
|
let group: DispatchGroup
|
|
|
|
init(group: DispatchGroup) {
|
|
self.group = group
|
|
}
|
|
|
|
deinit {
|
|
print("Deinit ActorSystem: mainExecutor=\(isMainExecutor()) mainThread=\(isMainThread())")
|
|
group.leave()
|
|
}
|
|
|
|
func resolve<Act>(id: ActorID, as actorType: Act.Type) throws -> Act?
|
|
where Act: DistributedActor,
|
|
Act.ID == ActorID {
|
|
print("resolve type:\(actorType), address:\(id)")
|
|
return nil
|
|
}
|
|
|
|
func assignID<Act>(_ actorType: Act.Type) -> ActorID
|
|
where Act: DistributedActor,
|
|
Act.ID == ActorID {
|
|
n += 1
|
|
let address = ActorAddress(parse: "addr-\(n)")
|
|
print("assign type:\(actorType), address:\(address)")
|
|
return address
|
|
}
|
|
|
|
func actorReady<Act>(_ actor: Act)
|
|
where Act: DistributedActor,
|
|
Act.ID == ActorID {
|
|
print("ready actor:\(actor), address:\(actor.id)")
|
|
}
|
|
|
|
func resignID(_ id: ActorID) {
|
|
print("resign address:\(id)")
|
|
}
|
|
|
|
@inlinable func makeInvocationEncoder() -> InvocationEncoder {
|
|
.init()
|
|
}
|
|
|
|
func remoteCall<Act, Err, Res>(
|
|
on actor: Act,
|
|
target: RemoteCallTarget,
|
|
invocation invocationEncoder: inout InvocationEncoder,
|
|
throwing: Err.Type,
|
|
returning: Res.Type
|
|
) async throws -> Res
|
|
where Act: DistributedActor,
|
|
Act.ID == ActorID,
|
|
Err: Error,
|
|
Res: SerializationRequirement {
|
|
fatalError("not implemented: \(#function)")
|
|
}
|
|
|
|
func remoteCallVoid<Act, Err>(
|
|
on actor: Act,
|
|
target: RemoteCallTarget,
|
|
invocation invocationEncoder: inout InvocationEncoder,
|
|
throwing: Err.Type
|
|
) async throws
|
|
where Act: DistributedActor,
|
|
Act.ID == ActorID,
|
|
Err: Error {
|
|
fatalError("not implemented: \(#function)")
|
|
}
|
|
}
|
|
|
|
class FakeDistributedInvocationEncoder: DistributedTargetInvocationEncoder, DistributedTargetInvocationDecoder {
|
|
typealias SerializationRequirement = Codable
|
|
|
|
func recordGenericSubstitution<T>(_ type: T.Type) throws { }
|
|
func recordArgument<Value: SerializationRequirement>(_ argument: RemoteCallArgument<Value>) throws { }
|
|
func recordReturnType<R: SerializationRequirement>(_ type: R.Type) throws { }
|
|
func recordErrorType<E: Error>(_ type: E.Type) throws { }
|
|
func doneRecording() throws { }
|
|
|
|
// === Receiving / decoding -------------------------------------------------
|
|
|
|
func decodeGenericSubstitutions() throws -> [Any.Type] {
|
|
[]
|
|
}
|
|
func decodeNextArgument<Argument: SerializationRequirement>() throws -> Argument {
|
|
fatalError()
|
|
}
|
|
func decodeReturnType() throws -> Any.Type? {
|
|
nil
|
|
}
|
|
func decodeErrorType() throws -> Any.Type? {
|
|
nil
|
|
}
|
|
}
|
|
|
|
@available(SwiftStdlib 5.5, *)
|
|
public struct FakeResultHandler: DistributedTargetInvocationResultHandler {
|
|
public typealias SerializationRequirement = Codable
|
|
|
|
public func onReturn<Success: SerializationRequirement>(value: Success) async throws {
|
|
fatalError("Not implemented: \(#function)")
|
|
}
|
|
|
|
public func onReturnVoid() async throws {
|
|
fatalError("Not implemented: \(#function)")
|
|
}
|
|
|
|
public func onThrow<Err: Error>(error: Err) async throws {
|
|
fatalError("Not implemented: \(#function)")
|
|
}
|
|
}
|
|
|
|
typealias DefaultDistributedActorSystem = FakeActorSystem
|
|
|
|
// ==== Execute ----------------------------------------------------------------
|
|
|
|
func test() {
|
|
let group = DispatchGroup()
|
|
var dummy: AnyObject?
|
|
|
|
func check(factory: () -> AnyObject) {
|
|
dummy = factory()
|
|
// Test requires actor system to be released before the actor,
|
|
// so that release of the actor system from actor deinit is the last one
|
|
group.enter()
|
|
dummy = nil
|
|
group.wait()
|
|
}
|
|
|
|
// no lifecycle things make sense for a normal actor, double check we didn't emit them
|
|
print("before A")
|
|
_ = A()
|
|
print("after A")
|
|
// CHECK: before A
|
|
// CHECK: after A
|
|
|
|
check {
|
|
DA(system: DefaultDistributedActorSystem(group: group))
|
|
}
|
|
// CHECK: assign type:DA, address:[[ADDRESS:.*]]
|
|
// CHECK: ready actor:main.DA, address:ActorAddress(address: "[[ADDR1:addr-[0-9]]]")
|
|
// CHECK: resign address:ActorAddress(address: "[[ADDR1]]")
|
|
// CHECK-NEXT: Deinit ActorSystem: mainExecutor=true mainThread=true
|
|
|
|
check {
|
|
DA_userDefined(system: DefaultDistributedActorSystem(group: group))
|
|
}
|
|
// CHECK: assign type:DA_userDefined, address:[[ADDRESS:.*]]
|
|
// CHECK: ready actor:main.DA_userDefined, address:ActorAddress(address: "[[ADDR2:addr-[0-9]]]")
|
|
// CHECK: resign address:ActorAddress(address: "[[ADDR2]]")
|
|
// CHECK-NEXT: Deinit ActorSystem: mainExecutor=true mainThread=true
|
|
|
|
// resign must happen as the _last thing_ after user-deinit completed
|
|
check {
|
|
DA_userDefined_nonisolated(system: DefaultDistributedActorSystem(group: group))
|
|
}
|
|
// CHECK: assign type:DA_userDefined_nonisolated, address:[[ADDRESS:.*]]
|
|
// CHECK: ready actor:main.DA_userDefined_nonisolated, address:ActorAddress(address: "[[ADDR3:addr-[0-9]]]")
|
|
// CHECK: Deinitializing ActorAddress(address: "[[ADDR3]]") remote:false isolated:false mainThread:true
|
|
// CHECK-NEXT: resign address:ActorAddress(address: "[[ADDR3]]")
|
|
// CHECK-NEXT: Deinit ActorSystem: mainExecutor=true mainThread=true
|
|
|
|
// resign must happen as the _last thing_ after user-deinit completed
|
|
check {
|
|
DA_userDefined_isolated(system: DefaultDistributedActorSystem(group: group))
|
|
}
|
|
// CHECK: assign type:DA_userDefined_isolated, address:[[ADDRESS:.*]]
|
|
// CHECK: ready actor:main.DA_userDefined_isolated, address:ActorAddress(address: "[[ADDR4:addr-[0-9]]]")
|
|
// CHECK: Deinitializing ActorAddress(address: "[[ADDR4]]") remote:false isolated:true mainThread:true
|
|
// CHECK-NEXT: resign address:ActorAddress(address: "[[ADDR4]]")
|
|
// CHECK-NEXT: Deinit ActorSystem: mainExecutor=false mainThread=true
|
|
|
|
// resign must happen as the _last thing_ after user-deinit completed
|
|
check {
|
|
DA_state_nonisolated(name: "Foo", age:37, system: DefaultDistributedActorSystem(group: group))
|
|
}
|
|
// CHECK: assign type:DA_state_nonisolated, address:[[ADDRESS:.*]]
|
|
// CHECK: ready actor:main.DA_state_nonisolated, address:ActorAddress(address: "[[ADDR5:addr-[0-9]]]")
|
|
// CHECK: Deinitializing ActorAddress(address: "[[ADDR5]]") name=Foo age=37 remote:false isolated:false mainThread:true
|
|
// CHECK-NEXT: resign address:ActorAddress(address: "[[ADDR5]]")
|
|
// CHECK-NEXT: Deinit ActorSystem: mainExecutor=true mainThread=true
|
|
|
|
// resign must happen as the _last thing_ after user-deinit completed
|
|
check {
|
|
DA_state_isolated(name: "Bar", age:42, system: DefaultDistributedActorSystem(group: group))
|
|
}
|
|
// CHECK: assign type:DA_state_isolated, address:[[ADDRESS:.*]]
|
|
// CHECK: ready actor:main.DA_state_isolated, address:ActorAddress(address: "[[ADDR6:addr-[0-9]]]")
|
|
// CHECK: Deinitializing ActorAddress(address: "[[ADDR6]]") name=Bar age=42 remote:false isolated:true mainThread:true
|
|
// CHECK-NEXT: resign address:ActorAddress(address: "[[ADDR6]]")
|
|
// CHECK-NEXT: Deinit ActorSystem: mainExecutor=false mainThread=true
|
|
|
|
// resign must happen as the _last thing_ after user-deinit completed
|
|
check {
|
|
DA_state_isolated_on_another(name: "Baz", age:57, system: DefaultDistributedActorSystem(group: group))
|
|
}
|
|
// CHECK: assign type:DA_state_isolated_on_another, address:[[ADDRESS:.*]]
|
|
// CHECK: ready actor:main.DA_state_isolated_on_another, address:ActorAddress(address: "[[ADDR6:addr-[0-9]]]")
|
|
// CHECK: Deinitializing ActorAddress(address: "[[ADDR6]]") name=Baz age=57 remote:false isolated-self:false isolated-other:true mainThread:false
|
|
// CHECK-NEXT: resign address:ActorAddress(address: "[[ADDR6]]")
|
|
// CHECK-NEXT: Deinit ActorSystem: mainExecutor=false mainThread=false
|
|
|
|
// a remote actor should not resign it's address, it was never "assigned" it
|
|
check {
|
|
let address = ActorAddress(parse: "remote-1")
|
|
return try! DA_userDefined_nonisolated.resolve(id: address, using: DefaultDistributedActorSystem(group: group))
|
|
}
|
|
// CHECK-NEXT: resolve type:DA_userDefined_nonisolated, address:ActorAddress(address: "remote-1")
|
|
// MUST NOT run deinit body for a remote distributed actor
|
|
// CHECK-NOT: Deinitializing ActorAddress(address: "remote-1")
|
|
// CHECK-NEXT: Deinit ActorSystem: mainExecutor=true mainThread=true
|
|
|
|
// a remote actor should not resign it's address, it was never "assigned" it
|
|
check {
|
|
let address = ActorAddress(parse: "remote-2")
|
|
return try! DA_userDefined_isolated.resolve(id: address, using: DefaultDistributedActorSystem(group: group))
|
|
}
|
|
// CHECK-NEXT: resolve type:DA_userDefined_isolated, address:ActorAddress(address: "remote-2")
|
|
// MUST NOT run deinit body for a remote distributed actor
|
|
// CHECK-NOT: Deinitializing ActorAddress(address: "remote-2")
|
|
// CHECK-NEXT: Deinit ActorSystem: mainExecutor=true mainThread=true
|
|
|
|
// a remote actor should not resign it's address, it was never "assigned" it
|
|
check {
|
|
let address = ActorAddress(parse: "remote-3")
|
|
return try! DA_state_isolated_on_another.resolve(id: address, using: DefaultDistributedActorSystem(group: group))
|
|
}
|
|
// CHECK-NEXT: resolve type:DA_state_isolated_on_another, address:ActorAddress(address: "remote-3")
|
|
// MUST NOT run deinit body for a remote distributed actor
|
|
// CHECK-NOT: Deinitializing ActorAddress(address: "remote-3")
|
|
// CHECK-NEXT: Deinit ActorSystem: mainExecutor=true mainThread=true
|
|
|
|
print("DONE")
|
|
// CHECK-NEXT: DONE
|
|
}
|
|
|
|
@main struct Main {
|
|
static func main() async {
|
|
mainThread = getCurrentThreadID()
|
|
test()
|
|
}
|
|
}
|