Skip to content

Commit 028e87b

Browse files
authored
Implement setting actor metadata with property wrapper fields (apple#988)
1 parent 2f981fd commit 028e87b

18 files changed

+449
-286
lines changed

.swiftformat

+6
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@
2424
--disable redundantGet
2525
--disable redundantReturn
2626

27+
# enum namespaces does not understand some instances of code where an instance
28+
# IS necessary because of how dynamic member lookup is used; or we even create
29+
# instances manually, since we must have *member methods* for dynamic lookup
30+
# to work.
31+
--disable enumNamespaces
32+
2733
# we want to have fine grained control over extensions by marking each function
2834
# explicitly, rather than it being forced onto the extension entirely.
2935
--extensionacl on-declarations

Sources/ActorSingletonPlugin/ActorSingletonProxy.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ internal class ActorSingletonProxy<Message: Codable> {
171171
}
172172

173173
private func updateRef(_ context: _ActorContext<Message>, _ newRef: _ActorRef<Message>?) {
174-
context.log.debug("Updating ref from [\(optional: self.ref)] to [\(optional: newRef)], flushing \(self.buffer.count) messages")
174+
context.log.debug("Updating ref from [\(String(describing: self.ref))] to [\(String(describing: newRef))], flushing \(self.buffer.count) messages")
175175
self.ref = newRef
176176

177177
// Unstash messages if we have the singleton
@@ -222,7 +222,7 @@ extension ActorSingletonProxy {
222222
"singleton/buffer": "\(self.buffer.count)/\(self.settings.bufferCapacity)",
223223
]
224224

225-
metadata["targetNode"] = "\(optional: self.targetNode?.debugDescription)"
225+
metadata["targetNode"] = "\(String(describing: self.targetNode?.debugDescription))"
226226
if let ref = self.ref {
227227
metadata["ref"] = "\(ref.id)"
228228
}

Sources/DistributedActors/ActorID.swift

+113-52
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,61 @@ import Distributed
2020
/// Convenience alias for ``ClusterSystem/ActorID``.
2121
public typealias ActorID = ClusterSystem.ActorID
2222

23+
extension DistributedActor where ActorSystem == ClusterSystem {
24+
public nonisolated var metadata: ActorMetadata {
25+
self.id.metadata
26+
}
27+
}
28+
29+
extension ClusterSystem.ActorID {
30+
@propertyWrapper
31+
public struct Metadata<Value: Sendable & Codable> {
32+
let keyType: Any.Type
33+
let id: String
34+
// public init(_: Key.Type) {
35+
// // no initial value; it must be set during initialization
36+
// }
37+
38+
public init(_ keyPath: KeyPath<ActorMetadataKeys, ActorMetadataKey<Value>>) {
39+
let key = ActorMetadataKeys()[keyPath: keyPath]
40+
self.id = key.id
41+
self.keyType = type(of: key)
42+
}
43+
44+
public var wrappedValue: Value {
45+
get { fatalError("called wrappedValue getter") }
46+
set { fatalError("called wrappedValue setter") }
47+
}
48+
49+
public var projectedValue: Value {
50+
get { fatalError("called projectedValue getter") }
51+
set { fatalError("called projectedValue setter") }
52+
}
53+
54+
public static subscript<EnclosingSelf: DistributedActor, FinalValue>(
55+
_enclosingInstance myself: EnclosingSelf,
56+
wrapped wrappedKeyPath: ReferenceWritableKeyPath<EnclosingSelf, FinalValue>,
57+
storage storageKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Self>
58+
) -> Value where EnclosingSelf.ActorSystem == ClusterSystem, EnclosingSelf.ID == ClusterSystem.ActorID {
59+
get {
60+
let key = myself[keyPath: storageKeyPath]
61+
guard let value = myself.id.metadata[key.id] else {
62+
fatalError("ActorID Metadata for key \(key.id):\(key.keyType) was not assigned initial value, assign one in the distributed actor's initializer.")
63+
}
64+
return value as! Value
65+
}
66+
set {
67+
let metadata = myself.id.metadata
68+
let key = myself[keyPath: storageKeyPath]
69+
if let value = metadata[key.id] {
70+
fatalError("Attempted to override ActorID Metadata for key \(key.id):\(key.keyType) which already had value: \(value); with new value: \(String(describing: newValue))")
71+
}
72+
metadata[key.id] = newValue
73+
}
74+
}
75+
}
76+
}
77+
2378
extension ClusterSystem {
2479
/// Uniquely identifies a DistributedActor within the cluster.
2580
///
@@ -40,7 +95,7 @@ extension ClusterSystem {
4095
/// The location of the distributed actor (i.e. it being "remote" or "local") is not taken into account
4196
/// during comparison of IDs, however does matter that it is on the same actual unique node.
4297
///
43-
/// Additional information can be attached to them using ``ActorTags`` however those do not impact
98+
/// Additional information can be attached to them using ``ActorMetadata`` however those do not impact
4499
/// the identity or equality of the ID, or distributed actors identified by those IDs.
45100
///
46101
/// ## Lifecycle
@@ -64,7 +119,7 @@ extension ClusterSystem {
64119
/// Some tags may be carried to remote peers, while others are intended only for local use,
65120
/// e.g. to inform the actor system to resolve the actor identity using some special treatment etc.
66121
///
67-
/// Please refer to ``ActorTags`` for an in depth discussion about tagging.
122+
/// Please refer to ``ActorMetadata`` for an in depth discussion about tagging.
68123
///
69124
/// ## Serialization
70125
///
@@ -96,8 +151,10 @@ extension ClusterSystem {
96151
/// Tags MAY be transferred to other peers as the identity is replicated, however they are not necessary to uniquely identify the actor.
97152
/// Tags can carry additional information such as the type of the actor identified by this identity, or any other user defined "roles" or similar tags.
98153
///
99-
/// - SeeAlso: `ActorTags` for a detailed discussion of some frequently used tags.
100-
public var tags: ActorTags
154+
/// - SeeAlso: ``ActorMetadata`` for a detailed discussion of some frequently used tags.
155+
public var metadata: ActorMetadata {
156+
self.context.metadata
157+
}
101158

102159
/// Internal "actor context" which is used as storage for additional cluster actor features, such as watching.
103160
internal var context: DistributedActorContext
@@ -106,13 +163,13 @@ extension ClusterSystem {
106163
// FIXME(distributed): make optional
107164
public var path: ActorPath {
108165
get {
109-
guard let path = tags[ActorTags.path] else {
110-
fatalError("FIXME: ActorTags.path was not set on \(self)! NOTE THAT PATHS ARE TO BECOME OPTIONAL!!!") // FIXME(distributed): must be removed
166+
guard let path = metadata.path else {
167+
fatalError("FIXME: ActorTags.path was not set on \(self.incarnation)! NOTE THAT PATHS ARE TO BECOME OPTIONAL!!!") // FIXME(distributed): must be removed
111168
}
112169
return path
113170
}
114171
set {
115-
self.tags[ActorTags.path] = newValue
172+
self.metadata[ActorMetadataKeys().path.id] = newValue
116173
}
117174
}
118175

@@ -135,10 +192,9 @@ extension ClusterSystem {
135192
init(local node: UniqueNode, path: ActorPath?, incarnation: ActorIncarnation) {
136193
self.context = .init(lifecycle: nil)
137194
self._location = .local(node)
138-
self.tags = ActorTags()
139195
self.incarnation = incarnation
140196
if let path {
141-
self.tags[ActorTags.path] = path
197+
self.context.metadata[ActorMetadataKeys().path.id] = path
142198
}
143199
traceLog_DeathWatch("Made ID: \(self)")
144200
}
@@ -153,9 +209,8 @@ extension ClusterSystem {
153209
self.context = .init(lifecycle: nil)
154210
self._location = .remote(node)
155211
self.incarnation = incarnation
156-
self.tags = ActorTags()
157212
if let path {
158-
self.tags[ActorTags.path] = path
213+
self.context.metadata[ActorMetadataKeys().path.id] = path
159214
}
160215
traceLog_DeathWatch("Made ID: \(self)")
161216
}
@@ -166,10 +221,8 @@ extension ClusterSystem {
166221
self.context = .init(lifecycle: nil)
167222
self._location = .remote(node)
168223
self.incarnation = incarnation
169-
self.tags = ActorTags()
170-
// TODO: avoid mangling names on every spawn?
171-
if let mangledName = _mangledTypeName(type) {
172-
self.tags[ActorTags.type] = .init(mangledName: mangledName)
224+
if let mangledName = _mangledTypeName(type) { // TODO: avoid mangling names on every spawn?
225+
self.context.metadata.type = .init(mangledName: mangledName)
173226
}
174227
traceLog_DeathWatch("Made ID: \(self)")
175228
}
@@ -180,12 +233,9 @@ extension ClusterSystem {
180233
{
181234
self.context = context
182235
self._location = .local(node)
183-
self.tags = ActorTags()
184236
self.incarnation = incarnation
185-
self.tags = ActorTags()
186-
// TODO: avoid mangling names on every spawn?
187-
if let mangledName = _mangledTypeName(type) {
188-
self.tags[ActorTags.type] = .init(mangledName: mangledName)
237+
if let mangledName = _mangledTypeName(type) { // TODO: avoid mangling names on every spawn?
238+
self.context.metadata.type = .init(mangledName: mangledName)
189239
}
190240
traceLog_DeathWatch("Made ID: \(self)")
191241
}
@@ -197,17 +247,27 @@ extension ClusterSystem {
197247
self.context = context
198248
self._location = .remote(node)
199249
self.incarnation = incarnation
200-
self.tags = ActorTags()
201-
// TODO: avoid mangling names on every spawn?
202-
if let mangledName = _mangledTypeName(type) {
203-
self.tags[ActorTags.type] = .init(mangledName: mangledName)
250+
if let mangledName = _mangledTypeName(type) { // TODO: avoid mangling names on every spawn?
251+
self.context.metadata.type = .init(mangledName: mangledName)
204252
}
205253
traceLog_DeathWatch("Made ID: \(self)")
206254
}
207255

208-
internal var withoutContext: Self {
256+
internal var withoutLifecycle: Self {
209257
var copy = self
210-
copy.context = .init(lifecycle: nil)
258+
copy.context = .init(
259+
lifecycle: nil,
260+
metadata: self.metadata
261+
)
262+
return copy
263+
}
264+
265+
public var withoutMetadata: Self {
266+
var copy = self
267+
copy.context = .init(
268+
lifecycle: self.context.lifecycle,
269+
metadata: nil
270+
)
211271
return copy
212272
}
213273
}
@@ -857,30 +917,31 @@ extension UniqueNodeID {
857917

858918
extension ActorID: Codable {
859919
public func encode(to encoder: Encoder) throws {
860-
let tagSettings = encoder.actorSerializationContext?.system.settings.tags
861-
let encodeCustomTags: (ActorID, inout KeyedEncodingContainer<ActorCoding.TagKeys>) throws -> Void =
862-
tagSettings?.encodeCustomTags ?? ({ _, _ in () })
920+
let metadataSettings = encoder.actorSerializationContext?.system.settings.actorMetadata
921+
let encodeCustomMetadata =
922+
metadataSettings?.encodeCustomMetadata ?? ({ _, _ in () })
863923

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

869-
if !self.tags.isEmpty {
870-
var tagsContainer = container.nestedContainer(keyedBy: ActorCoding.TagKeys.self, forKey: ActorCoding.CodingKeys.tags)
929+
if !self.metadata.isEmpty {
930+
var metadataContainer = container.nestedContainer(keyedBy: ActorCoding.MetadataKeys.self, forKey: ActorCoding.CodingKeys.metadata)
871931

872-
if (tagSettings == nil || tagSettings!.propagateTags.contains(AnyActorTagKey(ActorTags.path))),
873-
let value = self.tags[ActorTags.path]
932+
let keys = ActorMetadataKeys()
933+
if (metadataSettings == nil || metadataSettings!.propagateMetadata.contains(keys.path.id)),
934+
let value = self.metadata.path
874935
{
875-
try tagsContainer.encode(value, forKey: ActorCoding.TagKeys.path)
936+
try metadataContainer.encode(value, forKey: ActorCoding.MetadataKeys.path)
876937
}
877-
if (tagSettings == nil || tagSettings!.propagateTags.contains(AnyActorTagKey(ActorTags.type))),
878-
let value = self.tags[ActorTags.type]
938+
if (metadataSettings == nil || metadataSettings!.propagateMetadata.contains(keys.type.id)),
939+
let value = self.metadata.type
879940
{
880-
try tagsContainer.encode(value, forKey: ActorCoding.TagKeys.type)
941+
try metadataContainer.encode(value, forKey: ActorCoding.MetadataKeys.type)
881942
}
882943

883-
try encodeCustomTags(self, &tagsContainer)
944+
try encodeCustomMetadata(self.metadata, &metadataContainer)
884945
}
885946
}
886947

@@ -893,23 +954,23 @@ extension ActorID: Codable {
893954
self.init(remote: node, path: path, incarnation: ActorIncarnation(incarnation))
894955

895956
// Decode any tags:
896-
if let tagsContainer = try? container.nestedContainer(keyedBy: ActorCoding.TagKeys.self, forKey: ActorCoding.CodingKeys.tags) {
957+
if let metadataContainer = try? container.nestedContainer(keyedBy: ActorCoding.MetadataKeys.self, forKey: ActorCoding.CodingKeys.metadata) {
897958
// tags container found, try to decode all known tags:
898-
if let path = try tagsContainer.decodeIfPresent(ActorPath.self, forKey: .path) {
899-
self.tags[ActorTags.path] = path
900-
}
959+
960+
// FIXME: implement decoding tags/metadata in general
901961

902962
if let context = decoder.actorSerializationContext {
903-
let decodeCustomTags = context.system.settings.tags.decodeCustomTags
904-
905-
for tag in try decodeCustomTags(tagsContainer) {
906-
func store<K: ActorTagKey>(_: K.Type) {
907-
if let value = tag.value as? K.Value {
908-
self.tags[K.self] = value
909-
}
910-
}
911-
_openExistential(tag.keyType as any ActorTagKey.Type, do: store) // the `as` here is required, because: inferred result type 'any ActorTagKey.Type' requires explicit coercion due to loss of generic requirements
912-
}
963+
let decodeCustomMetadata = context.system.settings.actorMetadata.decodeCustomMetadata
964+
try decodeCustomMetadata(metadataContainer, self.metadata)
965+
966+
// for (key, value) in try decodeCustomMetadata(metadataContainer) {
967+
// func store(_: K.Type) {
968+
// if let value = tag.value as? K.Value {
969+
// self.metadata[K.self] = value
970+
// }
971+
// }
972+
// _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
973+
// }
913974
}
914975
}
915976
}

Sources/DistributedActors/ActorTagSettings.swift Sources/DistributedActors/ActorIDMetadataSettings.swift

+17-15
Original file line numberDiff line numberDiff line change
@@ -19,37 +19,39 @@ import NIOSSL
1919
import ServiceDiscovery
2020
import SWIM
2121

22-
/// Configures default actor tagging behavior, as well as handling of tags on actors.
23-
public struct ActorTagSettings {
24-
public static var `default`: ActorTagSettings {
22+
/// Configures default actor id metadta behavior, like which metadata should be propagated cross process and which not.
23+
internal struct ActorIDMetadataSettings {
24+
public static var `default`: ActorIDMetadataSettings {
2525
return .init()
2626
}
2727

28-
public struct TagOnInit {
29-
internal enum _TagOnInit {
28+
/// Configures metadata which should be
29+
public struct AutoIDMetadata {
30+
internal enum _AutoIDMetadata: Hashable {
3031
case typeName
3132
}
3233

33-
internal var underlying: _TagOnInit
34+
internal var underlying: _AutoIDMetadata
3435

3536
/// Tag every actor with an additional human-readable type name
3637
// TODO: expose this eventually
3738
internal static let typeName = Self(underlying: .typeName)
3839
}
3940

4041
// TODO: expose this eventually
41-
internal var tagOnInit: [TagOnInit] = []
42+
/// List of metadata which the system should automatically include in an `ActorID` for types it manages.
43+
internal var autoIncludedMetadata: [AutoIDMetadata] = []
4244

4345
/// What type of tags, known and defined by the cluster system itself, should be automatically propagated.
44-
/// Other types of tags, such as user-defined tags, must be propagated by declaring apropriate functions for `encodeCustomTags` and `decodeCustomTags`.
45-
internal var propagateTags: Set<AnyActorTagKey> = [
46-
.init(ActorTags.path),
47-
.init(ActorTags.type),
46+
/// Other types of tags, such as user-defined tags, must be propagated by declaring apropriate functions for ``encodeCustomMetadata`` and ``decodeCustomMetadata``.
47+
internal var propagateMetadata: Set<String> = [
48+
ActorMetadataKeys().path.id,
49+
ActorMetadataKeys().type.id,
4850
]
4951

50-
// TODO: expose this eventually
51-
internal var encodeCustomTags: (ActorID, inout KeyedEncodingContainer<ActorCoding.TagKeys>) throws -> Void = { _, _ in () }
52+
internal var encodeCustomMetadata: (ActorMetadata, inout KeyedEncodingContainer<ActorCoding.MetadataKeys>) throws -> Void =
53+
{ _, _ in () }
5254

53-
// TODO: expose this eventually
54-
internal var decodeCustomTags: ((KeyedDecodingContainer<ActorCoding.TagKeys>) throws -> [any ActorTag]) = { _ in [] }
55+
internal var decodeCustomMetadata: ((KeyedDecodingContainer<ActorCoding.MetadataKeys>, ActorMetadata) throws -> Void) =
56+
{ _, _ in () }
5557
}

0 commit comments

Comments
 (0)