-
Notifications
You must be signed in to change notification settings - Fork 78
=serialization rm 5.2.4 workaround, fix LWWMap with Codable Value (Codable in Proto nesting) #782
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
Changes from all commits
f2e8d4b
e86b29e
f494b78
85931a7
8a0feff
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,8 +20,6 @@ import NIOFoundationCompat | |
| // ==== ---------------------------------------------------------------------------------------------------------------- | ||
| // MARK: Top-Level Bytes-Blob Encoder | ||
|
|
||
| // TODO: TopLevelDataEncoder | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this actually kind of implements just that I guess :nod: |
||
|
|
||
| final class TopLevelProtobufBlobEncoder: _TopLevelBlobEncoder { | ||
| let allocator: ByteBufferAllocator | ||
|
|
||
|
|
@@ -78,7 +76,7 @@ final class TopLevelProtobufBlobEncoder: _TopLevelBlobEncoder { | |
|
|
||
| func unkeyedContainer() -> UnkeyedEncodingContainer { | ||
| fatalErrorBacktrace("Attempted \(#function) in \(self)") | ||
| // TopLevelProtobufBlobEncoderContainer(superEncoder: self) | ||
| // TopLevelProtobufBlobUnkeyedEncodingContainer(superEncoder: self) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. though we don't actually need it, good |
||
| } | ||
|
|
||
| func singleValueContainer() -> SingleValueEncodingContainer { | ||
|
|
@@ -100,7 +98,9 @@ struct TopLevelProtobufBlobSingleValueEncodingContainer: SingleValueEncodingCont | |
| case let repr as AnyProtobufRepresentable: | ||
| try repr.encode(to: self.superEncoder) | ||
| case let data as Data: | ||
| try data.encode(to: self.superEncoder) | ||
| try self.superEncoder.store(data: data) | ||
| case let bytes as [UInt8]: | ||
| try self.superEncoder.store(bytes: bytes) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. so we're not really "encoding" we're just using it and that's great 👍
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the fix™ |
||
| default: | ||
| throw SerializationError.unableToSerialize(hint: "Attempted encode \(T.self) into a \(Self.self) which only supports ProtobufRepresentable") | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,11 +32,25 @@ final class CRDTGossipReplicationClusteredTests: ClusteredActorSystemsXCTestCase | |
|
|
||
| override func configureActorSystem(settings: inout ActorSystemSettings) { | ||
| settings.serialization.register(CRDT.ORSet<String>.self) | ||
| settings.serialization.register(CRDT.LWWMap<String, ValueHolder<String?>>.self) | ||
| settings.serialization.register(CRDT.LWWRegister<ValueHolder<String?>>.self) | ||
| settings.serialization.register(ValueHolder<String?>.self) | ||
| settings.serialization.register(CRDT.LWWMap<String, String?>.self) | ||
| settings.serialization.register(CRDT.LWWRegister<String?>.self) | ||
| settings.serialization.register(CodableExampleValue.self) | ||
| settings.serialization.register(CRDT.LWWMap<String, CodableExampleValue>.self) | ||
| settings.serialization.register(CRDT.LWWRegister<CodableExampleValue>.self) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. a bit annoying - but good reminder, to use a LWWMap one has to use a LWWRegister heh :~ |
||
| settings.serialization.register(String?.self) | ||
| } | ||
|
|
||
| struct CodableExampleValue: Equatable, Codable, ExpressibleByStringLiteral { | ||
| let value: String | ||
|
|
||
| init(stringLiteral value: StringLiteralType) { | ||
| self.value = value | ||
| } | ||
| } | ||
|
|
||
| // ==== ---------------------------------------------------------------------------------------------------------------- | ||
| // MARK: Owns Set | ||
|
|
||
| enum OwnsSetMessage: NonTransportableActorMessage { | ||
| case insert(String, CRDT.OperationConsistency) | ||
| } | ||
|
|
@@ -58,6 +72,9 @@ final class CRDTGossipReplicationClusteredTests: ClusteredActorSystemsXCTestCase | |
| } | ||
| } | ||
|
|
||
| // ==== ---------------------------------------------------------------------------------------------------------------- | ||
| // MARK: Owns Counter | ||
|
|
||
| func ownsCounter(p: ActorTestProbe<CRDT.GCounter>?) -> Behavior<Int> { | ||
| .setup { context in | ||
| let counter: CRDT.ActorOwned<CRDT.GCounter> = CRDT.GCounter.makeOwned(by: context, id: "counter") | ||
|
|
@@ -72,6 +89,9 @@ final class CRDTGossipReplicationClusteredTests: ClusteredActorSystemsXCTestCase | |
| } | ||
| } | ||
|
|
||
| // ==== ---------------------------------------------------------------------------------------------------------------- | ||
| // MARK: Owns Map | ||
|
|
||
| enum OwnsMapMessage<Value: Codable>: NonTransportableActorMessage { | ||
| case set(key: String, value: Value, CRDT.OperationConsistency) | ||
| } | ||
|
|
@@ -123,15 +143,36 @@ final class CRDTGossipReplicationClusteredTests: ClusteredActorSystemsXCTestCase | |
| try self.expectSet(probe: p2, expected: ["a", "aa", "b"]) | ||
| } | ||
|
|
||
| struct ValueHolder<Value: Codable & Equatable>: Codable, Equatable { | ||
| let value: Value? | ||
|
|
||
| init(_ value: Value? = nil) { | ||
| self.value = value | ||
| func test_gossip_localLWWMapUpdate_toOtherNode() throws { | ||
| let configure: (inout ActorSystemSettings) -> Void = { settings in | ||
| settings.crdt.gossipInterval = .seconds(1) | ||
| settings.crdt.gossipIntervalRandomFactor = 0 // no random factor, exactly 1second intervals | ||
| } | ||
| let first = self.setUpNode("first", configure) | ||
| let second = self.setUpNode("second", configure) | ||
| try self.joinNodes(node: first, with: second, ensureMembers: .up) | ||
|
|
||
| let p1 = self.testKit(first).spawnTestProbe("probe-one", expecting: CRDT.LWWMap<String, String?>.self) | ||
| let p2 = self.testKit(second).spawnTestProbe("probe-two", expecting: CRDT.LWWMap<String, String?>.self) | ||
|
|
||
| let one = try first.spawn("one", self.ownsLWWMap(p: p1, defaultValue: nil)) | ||
| let two = try second.spawn("two", self.ownsLWWMap(p: p2, defaultValue: nil)) | ||
|
|
||
| one.tell(.set(key: "a", value: "foo", .local)) | ||
| one.tell(.set(key: "aa", value: nil, .local)) | ||
|
|
||
| let gossipOneExpectMap: [String: String?] = ["a": .init("foo"), "aa": nil] | ||
| try self.expectMap(probe: p1, expected: gossipOneExpectMap) | ||
| try self.expectMap(probe: p2, expected: gossipOneExpectMap) | ||
|
|
||
| two.tell(.set(key: "b", value: "bar", .local)) | ||
|
|
||
| let gossipTwoExpectMap: [String: String?] = ["a": "foo", "aa": nil, "b": "bar"] | ||
| try self.expectMap(probe: p1, expected: gossipTwoExpectMap) | ||
| try self.expectMap(probe: p2, expected: gossipTwoExpectMap) | ||
| } | ||
|
|
||
| func test_gossip_localLWWMapUpdate_toOtherNode() throws { | ||
| func test_gossip_localLWWMapUpdate_toOtherNode_codableValue() throws { | ||
| let configure: (inout ActorSystemSettings) -> Void = { settings in | ||
| settings.crdt.gossipInterval = .seconds(1) | ||
| settings.crdt.gossipIntervalRandomFactor = 0 // no random factor, exactly 1second intervals | ||
|
|
@@ -140,24 +181,22 @@ final class CRDTGossipReplicationClusteredTests: ClusteredActorSystemsXCTestCase | |
| let second = self.setUpNode("second", configure) | ||
| try self.joinNodes(node: first, with: second, ensureMembers: .up) | ||
|
|
||
| let p1 = self.testKit(first).spawnTestProbe("probe-one", expecting: CRDT.LWWMap<String, ValueHolder<String?>>.self) | ||
| let p2 = self.testKit(second).spawnTestProbe("probe-two", expecting: CRDT.LWWMap<String, ValueHolder<String?>>.self) | ||
| let p1 = self.testKit(first).spawnTestProbe("probe-one", expecting: CRDT.LWWMap<String, CodableExampleValue>.self) | ||
| let p2 = self.testKit(second).spawnTestProbe("probe-two", expecting: CRDT.LWWMap<String, CodableExampleValue>.self) | ||
|
|
||
| // TODO: JSON serialization blows up on Swift 5.2.4 Linux with top-level values so we must wrap (https://bugs.swift.org/browse/SR-13173). Change nilPlaceholder to `:String? = .none` once fixed | ||
| let nilPlaceholder = ValueHolder<String?>() | ||
| let one = try first.spawn("one", self.ownsLWWMap(p: p1, defaultValue: nilPlaceholder)) | ||
| let two = try second.spawn("two", self.ownsLWWMap(p: p2, defaultValue: nilPlaceholder)) | ||
| let one = try first.spawn("one", self.ownsLWWMap(p: p1, defaultValue: "<nil>")) | ||
| let two = try second.spawn("two", self.ownsLWWMap(p: p2, defaultValue: "<nil>")) | ||
|
|
||
| one.tell(.set(key: "a", value: .init("foo"), .local)) | ||
| one.tell(.set(key: "aa", value: nilPlaceholder, .local)) | ||
| one.tell(.set(key: "a", value: "foo", .local)) | ||
| one.tell(.set(key: "aa", value: "baz", .local)) | ||
|
|
||
| let gossipOneExpectMap: [String: ValueHolder<String?>] = ["a": .init("foo"), "aa": nilPlaceholder] | ||
| let gossipOneExpectMap: [String: CodableExampleValue] = ["a": "foo", "aa": "baz"] | ||
| try self.expectMap(probe: p1, expected: gossipOneExpectMap) | ||
| try self.expectMap(probe: p2, expected: gossipOneExpectMap) | ||
|
|
||
| two.tell(.set(key: "b", value: .init("bar"), .local)) | ||
| two.tell(.set(key: "b", value: "bar", .local)) | ||
|
|
||
| let gossipTwoExpectMap: [String: ValueHolder<String?>] = ["a": .init("foo"), "aa": nilPlaceholder, "b": .init("bar")] | ||
| let gossipTwoExpectMap: [String: CodableExampleValue] = ["a": "foo", "aa": "baz", "b": "bar"] | ||
| try self.expectMap(probe: p1, expected: gossipTwoExpectMap) | ||
| try self.expectMap(probe: p2, expected: gossipTwoExpectMap) | ||
| } | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this would crash hard. |
||
|
|
@@ -294,11 +333,11 @@ final class CRDTGossipReplicationClusteredTests: ClusteredActorSystemsXCTestCase | |
| } | ||
| } | ||
|
|
||
| private func expectMap(probe: ActorTestProbe<CRDT.LWWMap<String, ValueHolder<String?>>>, expected: [String: ValueHolder<String?>], file: StaticString = #file, line: UInt = #line) throws { | ||
| private func expectMap<Value>(probe: ActorTestProbe<CRDT.LWWMap<String, Value>>, expected: [String: Value], file: StaticString = #file, line: UInt = #line) throws { | ||
| let testKit: ActorTestKit = self._testKits.first! | ||
|
|
||
| try testKit.eventually(within: .seconds(10)) { | ||
| let replicated: CRDT.LWWMap<String, ValueHolder<String?>> = try probe.expectMessage(within: .seconds(10), file: file, line: line) | ||
| let replicated: CRDT.LWWMap<String, Value> = try probe.expectMessage(within: .seconds(10), file: file, line: line) | ||
| pinfo("[\(probe.name)] received updated crdt: \(replicated)") | ||
|
|
||
| guard expected == replicated.underlying else { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not needed, cleanup