Skip to content
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
10 changes: 5 additions & 5 deletions Protos/ActorID.proto
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,18 @@ option optimize_for = SPEED;
option swift_prefix = "_Proto";

message ActorID {
UniqueNode node = 1;
ActorPath path = 2;
uint32 incarnation = 3;
// TODO: encode tags
UniqueNode node = 1;
ActorPath path = 2;
uint32 incarnation = 3;
map<string, bytes> metadata = 4;
}

message ActorPath {
repeated string segments = 1;
}

message UniqueNode {
Node node = 1;
Node node = 1;
uint64 nid = 2;
}

Expand Down
191 changes: 94 additions & 97 deletions Sources/DistributedActors/ActorID.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,22 @@
//===----------------------------------------------------------------------===//

import Distributed
import Foundation

// ==== ----------------------------------------------------------------------------------------------------------------
// MARK: ActorID

/// Convenience alias for ``ClusterSystem/ActorID``.
public typealias ActorID = ClusterSystem.ActorID

extension DistributedActor where ActorSystem == ClusterSystem {
public nonisolated var metadata: ActorMetadata {
self.id.metadata
}
}

extension ClusterSystem.ActorID {
@propertyWrapper
public struct Metadata<Value: Sendable & Codable> {
let keyType: Any.Type
let id: String

public init(_ keyPath: KeyPath<ActorMetadataKeys, ActorMetadataKey<Value>>) {
let key = ActorMetadataKeys()[keyPath: keyPath]
let key = ActorMetadataKeys.__instance[keyPath: keyPath]
self.id = key.id
self.keyType = type(of: key)
}
Expand Down Expand Up @@ -64,9 +59,13 @@ extension ClusterSystem.ActorID {
let metadata = myself.id.metadata
let key = myself[keyPath: storageKeyPath]
if let value = metadata[key.id] {
fatalError("Attempted to override ActorID Metadata for key \(key.id):\(key.keyType) which already had value: \(value); with new value: \(String(describing: newValue))")
fatalError("Attempted to override ActorID Metadata for key \(key.id):\(key.keyType) which already had value: [\(value)] with new value: [\(String(describing: newValue))]")
}
metadata[key.id] = newValue

if key.id == ActorMetadataKeys.__instance.wellKnown.id {
myself.actorSystem._wellKnownActorReady(myself)
}
}
}
}
Expand Down Expand Up @@ -143,6 +142,10 @@ extension ClusterSystem {
}
}

#if DEBUG
private var debugID = UUID()
#endif

/// Collection of tags associated with this actor identity.
///
/// Tags MAY be transferred to other peers as the identity is replicated, however they are not necessary to uniquely identify the actor.
Expand All @@ -157,23 +160,21 @@ extension ClusterSystem {
internal var context: DistributedActorContext

/// Underlying path representation, not attached to a specific Actor instance.
// FIXME(distributed): make optional
public var path: ActorPath {
public var path: ActorPath { // FIXME(distributed): make optional
get {
guard let path = metadata.path else {
fatalError("FIXME: ActorTags.path was not set on \(self.incarnation)! NOTE THAT PATHS ARE TO BECOME OPTIONAL!!!") // FIXME(distributed): must be removed
}
return path
}
set {
self.metadata[ActorMetadataKeys().path.id] = newValue
self.metadata[ActorMetadataKeys.__instance.path.id] = newValue
}
}

/// Returns the name of the actor represented by this path.
/// This is equal to the last path segments string representation.
// FIXME(distributed): make optional
public var name: String {
public var name: String { // FIXME(distributed): make optional
self.path.name
}

Expand All @@ -186,7 +187,7 @@ extension ClusterSystem {
self._location = .local(node)
self.incarnation = incarnation
if let path {
self.context.metadata[ActorMetadataKeys().path.id] = path
self.context.metadata.path = path
}
traceLog_DeathWatch("Made ID: \(self)")
}
Expand All @@ -197,7 +198,7 @@ extension ClusterSystem {
self._location = .remote(node)
self.incarnation = incarnation
if let path {
self.context.metadata[ActorMetadataKeys().path.id] = path
self.context.metadata.path = path
}
traceLog_DeathWatch("Made ID: \(self)")
}
Expand Down Expand Up @@ -263,22 +264,56 @@ extension ClusterSystem {
}

extension DistributedActor where ActorSystem == ClusterSystem {
/// INTERNAL: Provides the actor context for use within this actor.
public nonisolated var metadata: ActorMetadata {
self.id.metadata
}
}

extension DistributedActor where ActorSystem == ClusterSystem {
/// Provides the actor context for use within this actor.
/// The context must not be mutated concurrently with the owning actor, however the things it stores may provide additional synchronization to make this safe.
internal var context: DistributedActorContext {
self.id.context
}
}

extension ActorID: Hashable {
public static func == (lhs: ActorID, rhs: ActorID) -> Bool {
lhs.incarnation == rhs.incarnation && // quickest to check if the incarnations are the same
// if they happen to be equal, we don't know yet for sure if it's the same actor or not, as incarnation is just a random ID
// thus we need to compare the node and path as well
lhs.uniqueNode == rhs.uniqueNode && lhs.path == rhs.path
// Check the metadata based well-known identity names.
//
// The legacy "well known path" is checked using the normal path below,
// since it is implemented as incarnation == 0, and an unique path.
if let lhsWellKnownName = lhs.metadata.wellKnown {
if let rhsWellKnownName = rhs.metadata.wellKnown {
// If we're comparing "well known" actors, we ignore the concrete incarnation,
// and compare the well known name instead. This works for example for "$receptionist"
// and other well known names, that can be resolved using them, without an incarnation number.
if lhsWellKnownName == rhsWellKnownName, lhs.uniqueNode == rhs.uniqueNode {
return true
}
} else {
// 'lhs' WAS well known, but 'rhs' was not
return false
}
} else if rhs.metadata.wellKnown != nil {
// 'lhs' was NOT well known, but 'rhs' was:
return false
}

// quickest to check if the incarnations are the same
// if they happen to be equal, we don't know yet for sure if it's the same actor or not,
// as incarnation is just a random ID thus we need to compare the node and path as well
return lhs.incarnation == rhs.incarnation &&
lhs.uniqueNode == rhs.uniqueNode &&
lhs.path == rhs.path
}

public func hash(into hasher: inout Hasher) {
hasher.combine(self.incarnation)
if let wellKnownName = self.metadata.wellKnown {
hasher.combine(wellKnownName)
} else {
hasher.combine(self.incarnation)
}
hasher.combine(self.uniqueNode)
hasher.combine(self.path)
}
Expand All @@ -290,7 +325,22 @@ extension ActorID: CustomStringConvertible {
if self._isRemote {
res += "\(self.uniqueNode)"
}
res += "\(self.path)"
if let path = self.metadata.path {
// this is ready for making paths optional already -- and behavior removals
res += "\(path)"
} else {
res += "\(self.incarnation)"
}

if !self.metadata.isEmpty {
// TODO: we special case the "just a path" metadata to not break existing ActorRef tests
if self.metadata.count == 1, self.metadata.path != nil {
return res
}

res += self.metadata.description
}

return res
}

Expand All @@ -301,23 +351,33 @@ extension ActorID: CustomStringConvertible {
}
res += "\(self.path)"

if self.incarnation == ActorIncarnation.wellKnown {
return res
} else {
return "\(res)#\(self.incarnation.value)"
if self.incarnation != ActorIncarnation.wellKnown {
res += "#\(self.incarnation.value)"
}

if !self.metadata.isEmpty {
res += self.metadata.description
}

return res
}

/// Prints all information contained in the ID, including `incarnation` and all `metadata`.
public var fullDescription: String {
var res = ""
res += "\(reflecting: self.uniqueNode)"
res += "\(self.path)"
res += "#\(self.incarnation.value)"

if self.incarnation == ActorIncarnation.wellKnown {
return res
} else {
return "\(res)#\(self.incarnation.value)"
if !self.metadata.isEmpty {
res += self.metadata.description
}

#if DEBUG
res += "{debugID:\(debugID)}"
#endif

return res
}
}

Expand Down Expand Up @@ -517,11 +577,11 @@ extension ActorPath {
public static let _system: ActorPath = try! ActorPath(root: "system")

internal func makeLocalID(on node: UniqueNode, incarnation: ActorIncarnation) -> ActorID {
.init(local: node, path: self, incarnation: incarnation)
ActorID(local: node, path: self, incarnation: incarnation)
}

internal func makeRemoteID(on node: UniqueNode, incarnation: ActorIncarnation) -> ActorID {
.init(remote: node, path: self, incarnation: incarnation)
ActorID(remote: node, path: self, incarnation: incarnation)
}
}

Expand Down Expand Up @@ -704,7 +764,8 @@ public struct ActorIncarnation: Equatable, Hashable, ExpressibleByIntegerLiteral
extension ActorIncarnation {
/// To be used ONLY by special actors whose existence is wellKnown and identity never-changing.
/// Examples: `/system/deadLetters` or `/system/cluster`.
public static let wellKnown: ActorIncarnation = .init(0)
@available(*, deprecated, message: "Useful only with behavior actors, will be removed entirely")
internal static let wellKnown: ActorIncarnation = .init(0)

public static func random() -> ActorIncarnation {
ActorIncarnation(UInt32.random(in: UInt32(1) ... UInt32.max))
Expand Down Expand Up @@ -903,70 +964,6 @@ extension UniqueNodeID {
}
}

// ==== ----------------------------------------------------------------------------------------------------------------
// MARK: Codable ActorAddress

extension ActorID: Codable {
public func encode(to encoder: Encoder) throws {
let metadataSettings = encoder.actorSerializationContext?.system.settings.actorMetadata
let encodeCustomMetadata =
metadataSettings?.encodeCustomMetadata ?? ({ _, _ in () })

var container = encoder.container(keyedBy: ActorCoding.CodingKeys.self)
try container.encode(self.uniqueNode, forKey: ActorCoding.CodingKeys.node)
try container.encode(self.path, forKey: ActorCoding.CodingKeys.path) // TODO: remove as we remove the tree
try container.encode(self.incarnation, forKey: ActorCoding.CodingKeys.incarnation)

if !self.metadata.isEmpty {
var metadataContainer = container.nestedContainer(keyedBy: ActorCoding.MetadataKeys.self, forKey: ActorCoding.CodingKeys.metadata)

let keys = ActorMetadataKeys()
if (metadataSettings == nil || metadataSettings!.propagateMetadata.contains(keys.path.id)),
let value = self.metadata.path
{
try metadataContainer.encode(value, forKey: ActorCoding.MetadataKeys.path)
}
if (metadataSettings == nil || metadataSettings!.propagateMetadata.contains(keys.type.id)),
let value = self.metadata.type
{
try metadataContainer.encode(value, forKey: ActorCoding.MetadataKeys.type)
}

try encodeCustomMetadata(self.metadata, &metadataContainer)
}
}

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: ActorCoding.CodingKeys.self)
let node = try container.decode(UniqueNode.self, forKey: ActorCoding.CodingKeys.node)
let path = try container.decodeIfPresent(ActorPath.self, forKey: ActorCoding.CodingKeys.path)
let incarnation = try container.decode(UInt32.self, forKey: ActorCoding.CodingKeys.incarnation)

self.init(remote: node, path: path, incarnation: ActorIncarnation(incarnation))

// Decode any tags:
if let metadataContainer = try? container.nestedContainer(keyedBy: ActorCoding.MetadataKeys.self, forKey: ActorCoding.CodingKeys.metadata) {
// tags container found, try to decode all known tags:

// FIXME: implement decoding tags/metadata in general

if let context = decoder.actorSerializationContext {
let decodeCustomMetadata = context.system.settings.actorMetadata.decodeCustomMetadata
try decodeCustomMetadata(metadataContainer, self.metadata)

// for (key, value) in try decodeCustomMetadata(metadataContainer) {
// func store(_: K.Type) {
// if let value = tag.value as? K.Value {
// self.metadata[K.self] = value
// }
// }
// _openExistential(key, do: store) // the `as` here is required, because: inferred result type 'any ActorTagKey.Type' requires explicit coercion due to loss of generic requirements
// }
}
}
}
}

// ==== ----------------------------------------------------------------------------------------------------------------
// MARK: Path errors

Expand Down
5 changes: 3 additions & 2 deletions Sources/DistributedActors/ActorIDMetadataSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ internal struct ActorIDMetadataSettings {
/// What type of tags, known and defined by the cluster system itself, should be automatically propagated.
/// Other types of tags, such as user-defined tags, must be propagated by declaring apropriate functions for ``encodeCustomMetadata`` and ``decodeCustomMetadata``.
internal var propagateMetadata: Set<String> = [
ActorMetadataKeys().path.id,
ActorMetadataKeys().type.id,
ActorMetadataKeys.__instance.path.id,
ActorMetadataKeys.__instance.type.id,
ActorMetadataKeys.__instance.wellKnown.id,
]

internal var encodeCustomMetadata: (ActorMetadata, inout KeyedEncodingContainer<ActorCoding.MetadataKeys>) throws -> Void =
Expand Down
Loading