diff --git a/Sources/ActorSingletonPlugin/ActorSingletonProxy.swift b/Sources/ActorSingletonPlugin/ActorSingletonProxy.swift index 4a301d817..fd1e79f93 100644 --- a/Sources/ActorSingletonPlugin/ActorSingletonProxy.swift +++ b/Sources/ActorSingletonPlugin/ActorSingletonProxy.swift @@ -116,7 +116,7 @@ internal distributed actor ActorSingletonProxy: A private func updateTargetNode(node: UniqueNode?) async throws { guard self.targetNode != node else { - self.log.debug("Skip updating target node. New node is already the same as current targetNode.", metadata: self.metadata()) + self.log.trace("Skip updating target node. New node is already the same as current targetNode.", metadata: self.metadata()) return } diff --git a/Sources/DistributedActors/ActorID.swift b/Sources/DistributedActors/ActorID.swift index 6ebef0351..a0f131ac5 100644 --- a/Sources/DistributedActors/ActorID.swift +++ b/Sources/DistributedActors/ActorID.swift @@ -952,15 +952,25 @@ extension ActorID: Codable { var metadataContainer = container.nestedContainer(keyedBy: ActorCoding.MetadataKeys.self, forKey: ActorCoding.CodingKeys.metadata) let keys = ActorMetadataKeys.__instance - if (metadataSettings == nil || metadataSettings!.propagateMetadata.contains(keys.path.id)), - let value = self.metadata.path - { + func shouldPropagate(_ key: ActorMetadataKey, metadata: ActorMetadata) -> V? { + if metadataSettings == nil || metadataSettings!.propagateMetadata.contains(key.id) { + if let value = metadata[key.id] { + let value = value as! V // as!-safe, the keys guarantee we only store well typed values in metadata + return value + } + } + return nil + } + + // Handle well known metadata types + if let value = shouldPropagate(keys.path, metadata: self.metadata) { 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) + if let value = shouldPropagate(keys.type, metadata: self.metadata) { + try metadataContainer.encode(value.mangledName, forKey: ActorCoding.MetadataKeys.type) + } + if let value = shouldPropagate(keys.wellKnown, metadata: self.metadata) { + try metadataContainer.encode(value, forKey: ActorCoding.MetadataKeys.wellKnown) } try encodeCustomMetadata(self.metadata, &metadataContainer) @@ -979,8 +989,17 @@ extension ActorID: Codable { 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 - + let metadata = ActorMetadata() + if let value = try? metadataContainer.decodeIfPresent(ActorPath.self, forKey: ActorCoding.MetadataKeys.path) { + metadata.path = value + } + if let value = try? metadataContainer.decodeIfPresent(String.self, forKey: ActorCoding.MetadataKeys.type) { + metadata.type = .init(mangledName: value) + } + if let value = try? metadataContainer.decodeIfPresent(String.self, forKey: ActorCoding.MetadataKeys.wellKnown) { + metadata.wellKnown = value + } + if let context = decoder.actorSerializationContext { let decodeCustomMetadata = context.system.settings.actorMetadata.decodeCustomMetadata try decodeCustomMetadata(metadataContainer, self.metadata) @@ -994,6 +1013,8 @@ extension ActorID: Codable { // _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 // } } + + self.context = .init(lifecycle: nil, remoteCallInterceptor: nil, metadata: metadata) } } } diff --git a/Sources/DistributedActors/Serialization/ActorRef+Serialization.swift b/Sources/DistributedActors/Serialization/ActorRef+Serialization.swift index 763c93f09..8cc91b299 100644 --- a/Sources/DistributedActors/Serialization/ActorRef+Serialization.swift +++ b/Sources/DistributedActors/Serialization/ActorRef+Serialization.swift @@ -32,12 +32,14 @@ public enum ActorCoding { public enum MetadataKeys: CodingKey { case path case type + case wellKnown case custom(String) public init?(stringValue: String) { switch stringValue { case "path": self = .path case "type": self = .type + case "wellKnown": self = .wellKnown default: self = .custom(stringValue) } } @@ -46,7 +48,8 @@ public enum ActorCoding { switch self { case .path: return 0 case .type: return 1 - case .custom: return 2 + case .wellKnown: return 2 + case .custom: return 64 } } @@ -58,6 +61,7 @@ public enum ActorCoding { switch self { case .path: return "path" case .type: return "type" + case .wellKnown: return "wellKnown" case .custom(let id): return id } } diff --git a/Tests/DistributedActorsTests/ActorIDMetadataTests.swift b/Tests/DistributedActorsTests/ActorIDMetadataTests.swift index d4ec8f446..121eb7300 100644 --- a/Tests/DistributedActorsTests/ActorIDMetadataTests.swift +++ b/Tests/DistributedActorsTests/ActorIDMetadataTests.swift @@ -32,6 +32,9 @@ public protocol ExampleClusterSingletonProtocol: DistributedActor { distributed actor ThereCanBeOnlyOneClusterSingleton: ExampleClusterSingletonProtocol { typealias ActorSystem = ClusterSystem + @ActorID.Metadata(\.wellKnown) + public var wellKnownName: String + @ActorID.Metadata(\.exampleClusterSingletonID) public var singletonID: String // TODO(swift): impossible to assign initial value here, as _enclosingInstance is not available yet "the-one" @@ -39,6 +42,7 @@ distributed actor ThereCanBeOnlyOneClusterSingleton: ExampleClusterSingletonProt init(actorSystem: ActorSystem) async { self.actorSystem = actorSystem self.singletonID = "the-boss" + self.wellKnownName = "boss-singleton" } } @@ -79,11 +83,23 @@ final class ActorIDMetadataTests: ClusteredActorSystemsXCTestCase { "\(example)".shouldContain("\"user-id\": \"user-1234\"") try await example.assertThat(userID: userID) } - + func test_metadata_initializedInline() async throws { let system = await setUpNode("first") let singleton = await ThereCanBeOnlyOneClusterSingleton(actorSystem: system) singleton.metadata.exampleClusterSingletonID.shouldEqual("the-boss") } + + func test_metadata_wellKnown_serialized() async throws { + let system = await setUpNode("first") + let singleton = await ThereCanBeOnlyOneClusterSingleton(actorSystem: system) + + let encoded = try JSONEncoder().encode(singleton) + let encodedString = String(data: encoded, encoding: .utf8)! + encodedString.shouldContain("\"wellKnown\":\"boss-singleton\"") + + let back = try! JSONDecoder().decode(ActorID.self, from: encoded) + back.metadata.wellKnown.shouldEqual("boss-singleton") + } }