Skip to content

[Distributed] decode init for DistributedActor #38998

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions lib/AST/ASTContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2943,6 +2943,29 @@ AnyFunctionType::Param swift::computeSelfParam(AbstractFunctionDecl *AFD,
// inout self.
if (!containerTy->hasReferenceSemantics())
selfAccess = SelfAccessKind::Mutating;

// FIXME(distributed): pending swift-evolution, allow `self =` in class
// inits in general.
// See also: https://github.com/apple/swift/pull/19151 general impl
if (Ctx.LangOpts.EnableExperimentalDistributed) {
auto ext = dyn_cast<ExtensionDecl>(AFD->getDeclContext());
auto distProto =
Ctx.getProtocol(KnownProtocolKind::DistributedActor);
if (ext && ext->getExtendedNominal() &&
ext->getExtendedNominal()->getInterfaceType()
->isEqual(distProto->getInterfaceType())) {
auto name = CD->getName();
auto params = name.getArgumentNames();
if (params.size() == 1 && params[0] == Ctx.Id_from) {
// FIXME(distributed): this is a workaround to allow init(from:) to
// be implemented in AST by allowing the self to be mutable in the
// decoding initializer. This should become a general Swift
// feature, allowing this in all classes:
// https://forums.swift.org/t/allow-self-x-in-class-convenience-initializers/15924
selfAccess = SelfAccessKind::Mutating;
}
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so this only is enabled specifically for this single initializer in distributed actors; at least until we do an SE pitch for it.

} else {
// allocating constructors have metatype 'self'.
isStatic = true;
Expand Down
2 changes: 1 addition & 1 deletion stdlib/public/Distributed/ActorTransport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public protocol ActorTransport: Sendable {
///
/// Detecting liveness of such remote actors shall be offered / by transport libraries
/// by other means, such as "watching an actor for termination" or similar.
func resolve<Act>(_ identity: AnyActorIdentity, as actorType: Act.Type) throws -> Act? // TODO(distributed): make just optional
func resolve<Act>(_ identity: AnyActorIdentity, as actorType: Act.Type) throws -> Act?
where Act: DistributedActor

// ==== ---------------------------------------------------------------------
Expand Down
17 changes: 12 additions & 5 deletions stdlib/public/Distributed/DistributedActor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -93,16 +93,21 @@ extension DistributedActor {
// ==== Codable conformance ----------------------------------------------------

extension CodingUserInfoKey {
@available(SwiftStdlib 5.5, *)
static let actorTransportKey = CodingUserInfoKey(rawValue: "$dist_act_transport")!
@available(SwiftStdlib 5.5, *)
public static let actorTransportKey = CodingUserInfoKey(rawValue: "$dist_act_transport")!
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was a nasty omission! made sample apps impossible.

}

@available(SwiftStdlib 5.5, *)
extension DistributedActor {
nonisolated public init(from decoder: Decoder) throws {
// let id = try AnyActorIdentity(from: decoder)
// self = try Self.resolve(id, using: transport) // FIXME: This is going to be solved by the init() work!!!!
fatalError("\(#function) is not implemented yet for distributed actors'")
guard let transport = decoder.userInfo[.actorTransportKey] as? ActorTransport else {
throw DistributedActorCodingError(message:
"Missing ActorTransport (for key .actorTransportKey) " +
"in Decoder.userInfo, while decoding \(Self.self).")
}

let id: AnyActorIdentity = try transport.decodeIdentity(from: decoder)
self = try Self.resolve(id, using: transport)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason why we need the initializer semantics change.

}

nonisolated public func encode(to encoder: Encoder) throws {
Expand All @@ -121,12 +126,14 @@ public protocol ActorIdentity: Sendable, Hashable, Codable {}

@available(SwiftStdlib 5.5, *)
public struct AnyActorIdentity: ActorIdentity, @unchecked Sendable, CustomStringConvertible {
public let underlying: Any
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is important, impls need to be able to reach this.

@usableFromInline let _hashInto: (inout Hasher) -> ()
@usableFromInline let _equalTo: (Any) -> Bool
@usableFromInline let _encodeTo: (Encoder) throws -> ()
@usableFromInline let _description: () -> String

public init<ID>(_ identity: ID) where ID: ActorIdentity {
self.underlying = identity
_hashInto = { hasher in identity
.hash(into: &hasher)
}
Expand Down
218 changes: 218 additions & 0 deletions test/Distributed/Runtime/distributed_actor_decode.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
// RUN: %target-run-simple-swift(-Xfrontend -enable-experimental-distributed -parse-as-library) | %FileCheck %s

// REQUIRES: executable_test
// REQUIRES: concurrency
// REQUIRES: distributed

// UNSUPPORTED: use_os_stdlib
// UNSUPPORTED: back_deployment_runtime

import _Distributed

@available(SwiftStdlib 5.5, *)
distributed actor DA: CustomStringConvertible {
nonisolated var description: String {
"DA(\(self.id))"
}
}

// ==== Fake Transport ---------------------------------------------------------

@available(SwiftStdlib 5.5, *)
struct ActorAddress: ActorIdentity {
let address: String
init(parse address : String) {
self.address = address
}

// Explicit implementations to make our TestEncoder/Decoder simpler
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
self.address = try container.decode(String.self)
print("decode ActorAddress -> \(self)")
}

func encode(to encoder: Encoder) throws {
print("encode \(self)")
var container = encoder.singleValueContainer()
try container.encode(self.address)
}
}

@available(SwiftStdlib 5.5, *)
struct FakeTransport: ActorTransport {
func decodeIdentity(from decoder: Decoder) throws -> AnyActorIdentity {
print("FakeTransport.decodeIdentity from:\(decoder)")
let address = try ActorAddress(from: decoder)
return AnyActorIdentity(address)
}

func resolve<Act>(_ identity: AnyActorIdentity, as actorType: Act.Type) throws -> Act?
where Act: DistributedActor {
print("resolve type:\(actorType), address:\(identity)")
return nil
}

func assignIdentity<Act>(_ actorType: Act.Type) -> AnyActorIdentity
where Act: DistributedActor {
let address = ActorAddress(parse: "xxx")
print("assign type:\(actorType), address:\(address)")
return .init(address)
}

public func actorReady<Act>(_ actor: Act) where Act: DistributedActor {
print("ready actor:\(actor), address:\(actor.id)")
}

func resignIdentity(_ identity: AnyActorIdentity) {
print("resign address:\(identity)")
}
}

// ==== Test Coding ------------------------------------------------------------

@available(SwiftStdlib 5.5, *)
class TestEncoder: Encoder {
var codingPath: [CodingKey]
var userInfo: [CodingUserInfoKey: Any]

var data: String? = nil

init(transport: ActorTransport) {
self.codingPath = []
self.userInfo = [.actorTransportKey: transport]
}

func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> {
fatalError("Not implemented: \(#function)")
}

func unkeyedContainer() -> UnkeyedEncodingContainer {
fatalError("Not implemented: \(#function)")
}

func singleValueContainer() -> SingleValueEncodingContainer {
TestSingleValueEncodingContainer(parent: self)
}

class TestSingleValueEncodingContainer: SingleValueEncodingContainer {
let parent: TestEncoder
init(parent: TestEncoder) {
self.parent = parent
}

var codingPath: [CodingKey] = []

func encodeNil() throws { fatalError("Not implemented: \(#function)") }
func encode(_ value: Bool) throws { fatalError("Not implemented: \(#function)") }
func encode(_ value: String) throws {

}
func encode(_ value: Double) throws { fatalError("Not implemented: \(#function)") }
func encode(_ value: Float) throws { fatalError("Not implemented: \(#function)") }
func encode(_ value: Int) throws { fatalError("Not implemented: \(#function)") }
func encode(_ value: Int8) throws { fatalError("Not implemented: \(#function)") }
func encode(_ value: Int16) throws { fatalError("Not implemented: \(#function)") }
func encode(_ value: Int32) throws { fatalError("Not implemented: \(#function)") }
func encode(_ value: Int64) throws { fatalError("Not implemented: \(#function)") }
func encode(_ value: UInt) throws { fatalError("Not implemented: \(#function)") }
func encode(_ value: UInt8) throws { fatalError("Not implemented: \(#function)") }
func encode(_ value: UInt16) throws { fatalError("Not implemented: \(#function)") }
func encode(_ value: UInt32) throws { fatalError("Not implemented: \(#function)") }
func encode(_ value: UInt64) throws { fatalError("Not implemented: \(#function)") }
func encode<T: Encodable>(_ value: T) throws {
print("encode: \(value)")
if let identity = value as? AnyActorIdentity {
self.parent.data =
(identity.underlying as! ActorAddress).address
}
}
}

func encode<Act: DistributedActor>(_ actor: Act) throws -> String {
try actor.encode(to: self)
return self.data!
}
}

@available(SwiftStdlib 5.5, *)
class TestDecoder: Decoder {
let encoder: TestEncoder
let data: String

init(encoder: TestEncoder, transport: ActorTransport, data: String) {
self.encoder = encoder
self.userInfo = [.actorTransportKey: transport]
self.data = data
}

var codingPath: [CodingKey] = []
var userInfo: [CodingUserInfoKey : Any]

func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> where Key : CodingKey {
fatalError("Not implemented: \(#function)")
}
func unkeyedContainer() throws -> UnkeyedDecodingContainer {
fatalError("Not implemented: \(#function)")
}
func singleValueContainer() throws -> SingleValueDecodingContainer {
TestSingleValueDecodingContainer(parent: self)
}

class TestSingleValueDecodingContainer: SingleValueDecodingContainer {
let parent: TestDecoder
init(parent: TestDecoder) {
self.parent = parent
}

var codingPath: [CodingKey] = []
func decodeNil() -> Bool { fatalError("Not implemented: \(#function)") }
func decode(_ type: Bool.Type) throws -> Bool { fatalError("Not implemented: \(#function)") }
func decode(_ type: String.Type) throws -> String {
print("decode String -> \(self.parent.data)")
return self.parent.data
}
func decode(_ type: Double.Type) throws -> Double { fatalError("Not implemented: \(#function)") }
func decode(_ type: Float.Type) throws -> Float { fatalError("Not implemented: \(#function)") }
func decode(_ type: Int.Type) throws -> Int { fatalError("Not implemented: \(#function)") }
func decode(_ type: Int8.Type) throws -> Int8 { fatalError("Not implemented: \(#function)") }
func decode(_ type: Int16.Type) throws -> Int16 { fatalError("Not implemented: \(#function)") }
func decode(_ type: Int32.Type) throws -> Int32 { fatalError("Not implemented: \(#function)") }
func decode(_ type: Int64.Type) throws -> Int64 { fatalError("Not implemented: \(#function)") }
func decode(_ type: UInt.Type) throws -> UInt { fatalError("Not implemented: \(#function)") }
func decode(_ type: UInt8.Type) throws -> UInt8 { fatalError("Not implemented: \(#function)") }
func decode(_ type: UInt16.Type) throws -> UInt16 { fatalError("Not implemented: \(#function)") }
func decode(_ type: UInt32.Type) throws -> UInt32 { fatalError("Not implemented: \(#function)") }
func decode(_ type: UInt64.Type) throws -> UInt64 { fatalError("Not implemented: \(#function)") }
func decode<T>(_ type: T.Type) throws -> T where T : Decodable { fatalError("Not implemented: \(#function)") }
}
}

// ==== Execute ----------------------------------------------------------------

@available(SwiftStdlib 5.5, *)
func test() {
let transport = FakeTransport()

// CHECK: assign type:DA, address:ActorAddress(address: "xxx")
let da = DA(transport: transport)

// CHECK: encode: AnyActorIdentity(ActorAddress(address: "xxx"))
// CHECK: FakeTransport.decodeIdentity from:main.TestDecoder
let encoder = TestEncoder(transport: transport)
let data = try! encoder.encode(da)

// CHECK: decode String -> xxx
// CHECK: decode ActorAddress -> ActorAddress(address: "xxx")
let da2 = try! DA(from: TestDecoder(encoder: encoder, transport: transport, data: data))

// CHECK: decoded da2: DA(AnyActorIdentity(ActorAddress(address: "xxx")))
print("decoded da2: \(da2)")
}

@available(SwiftStdlib 5.5, *)
@main struct Main {
static func main() async {
test()
}
}