// 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 %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(id: ActorID, as actorType: Act.Type) throws -> Act? where Act: DistributedActor, Act.ID == ActorID { print("resolve type:\(actorType), address:\(id)") return nil } func assignID(_ 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(_ 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( 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( 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(_ type: T.Type) throws { } func recordArgument(_ argument: RemoteCallArgument) throws { } func recordReturnType(_ type: R.Type) throws { } func recordErrorType(_ type: E.Type) throws { } func doneRecording() throws { } // === Receiving / decoding ------------------------------------------------- func decodeGenericSubstitutions() throws -> [Any.Type] { [] } func decodeNextArgument() 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(value: Success) async throws { fatalError("Not implemented: \(#function)") } public func onReturnVoid() async throws { fatalError("Not implemented: \(#function)") } public func onThrow(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() } }