Files
swift-mirror/test/Distributed/Runtime/distributed_actor_deinit.swift
2024-12-12 16:41:02 +09:00

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()
}
}