Skip to content

Commit cf466ac

Browse files
authored
=serialization rm 5.2.4 workaround, fix LWWMap with Codable Value (Codable in Proto nesting) (#782)
* =fix,ser serialization would crash if LWWMap used a value that was Codable * debug CI; this works locally in docker swift 5.2.5 + linux... * dont install jazzy on xenial * version
1 parent d42b708 commit cf466ac

File tree

5 files changed

+70
-72
lines changed

5 files changed

+70
-72
lines changed

Sources/DistributedActors/Serialization/Serialization+Codable.swift

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -80,42 +80,3 @@ extension Encodable {
8080
try encoder.encode(self)
8181
}
8282
}
83-
84-
// TODO: could not get it to work
85-
//// ==== ----------------------------------------------------------------------------------------------------------------
86-
//// MARK: encode with manifest
87-
//
88-
// extension KeyedEncodingContainerProtocol {
89-
// mutating func encode<T>(_ value: T, forKey key: Self.Key, forManifestKey manifestKey: Self.Key) throws where T: Encodable {
90-
// let encoder = self.superEncoder()
91-
// guard let context: Serialization.Context = encoder.actorSerializationContext else {
92-
// throw SerializationError.missingSerializationContext(encoder, value)
93-
// }
94-
//
95-
// let serialized = try context.serialization.serialize(value)
96-
//
97-
// try self.encode(serialized.manifest, forKey: manifestKey)
98-
// try self.encode(serialized.buffer.readData(), forKey: key)
99-
// }
100-
//
101-
//
102-
// }
103-
//
104-
// extension KeyedDecodingContainerProtocol {
105-
// func decode<T>(
106-
// _ type: T.Type, forKey key: Self.Key, forManifestKey manifestKey: Self.Key,
107-
// file: String = #file, line: UInt = #line
108-
// ) throws -> T where T: Decodable {
109-
// let decoder = self.superEncoder()
110-
//
111-
// guard let context: Serialization.Context = decoder.actorSerializationContext else {
112-
// throw SerializationError.missingSerializationContext(T.self, details: "Missing context", file: file, line: line)
113-
// }
114-
//
115-
// let manifest = try self.decode(Serialization.Manifest.self, forKey: manifestKey)
116-
// let data = try self.decode(Foundation.Data.self, forKey: manifestKey)
117-
//
118-
// return try context.serialization.deserialize(as: T.self, from: .data(data), using: manifest)
119-
// }
120-
//
121-
// }

Sources/DistributedActors/Serialization/TopLevelProtobufCoders.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ import NIOFoundationCompat
2020
// ==== ----------------------------------------------------------------------------------------------------------------
2121
// MARK: Top-Level Bytes-Blob Encoder
2222

23-
// TODO: TopLevelDataEncoder
24-
2523
final class TopLevelProtobufBlobEncoder: _TopLevelBlobEncoder {
2624
let allocator: ByteBufferAllocator
2725

@@ -78,7 +76,7 @@ final class TopLevelProtobufBlobEncoder: _TopLevelBlobEncoder {
7876

7977
func unkeyedContainer() -> UnkeyedEncodingContainer {
8078
fatalErrorBacktrace("Attempted \(#function) in \(self)")
81-
// TopLevelProtobufBlobEncoderContainer(superEncoder: self)
79+
// TopLevelProtobufBlobUnkeyedEncodingContainer(superEncoder: self)
8280
}
8381

8482
func singleValueContainer() -> SingleValueEncodingContainer {
@@ -100,7 +98,9 @@ struct TopLevelProtobufBlobSingleValueEncodingContainer: SingleValueEncodingCont
10098
case let repr as AnyProtobufRepresentable:
10199
try repr.encode(to: self.superEncoder)
102100
case let data as Data:
103-
try data.encode(to: self.superEncoder)
101+
try self.superEncoder.store(data: data)
102+
case let bytes as [UInt8]:
103+
try self.superEncoder.store(bytes: bytes)
104104
default:
105105
throw SerializationError.unableToSerialize(hint: "Attempted encode \(T.self) into a \(Self.self) which only supports ProtobufRepresentable")
106106
}

Tests/DistributedActorsTests/CRDT/CRDTGossipReplicationClusteredTests.swift

Lines changed: 61 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,25 @@ final class CRDTGossipReplicationClusteredTests: ClusteredActorSystemsXCTestCase
3232

3333
override func configureActorSystem(settings: inout ActorSystemSettings) {
3434
settings.serialization.register(CRDT.ORSet<String>.self)
35-
settings.serialization.register(CRDT.LWWMap<String, ValueHolder<String?>>.self)
36-
settings.serialization.register(CRDT.LWWRegister<ValueHolder<String?>>.self)
37-
settings.serialization.register(ValueHolder<String?>.self)
35+
settings.serialization.register(CRDT.LWWMap<String, String?>.self)
36+
settings.serialization.register(CRDT.LWWRegister<String?>.self)
37+
settings.serialization.register(CodableExampleValue.self)
38+
settings.serialization.register(CRDT.LWWMap<String, CodableExampleValue>.self)
39+
settings.serialization.register(CRDT.LWWRegister<CodableExampleValue>.self)
40+
settings.serialization.register(String?.self)
3841
}
3942

43+
struct CodableExampleValue: Equatable, Codable, ExpressibleByStringLiteral {
44+
let value: String
45+
46+
init(stringLiteral value: StringLiteralType) {
47+
self.value = value
48+
}
49+
}
50+
51+
// ==== ----------------------------------------------------------------------------------------------------------------
52+
// MARK: Owns Set
53+
4054
enum OwnsSetMessage: NonTransportableActorMessage {
4155
case insert(String, CRDT.OperationConsistency)
4256
}
@@ -58,6 +72,9 @@ final class CRDTGossipReplicationClusteredTests: ClusteredActorSystemsXCTestCase
5872
}
5973
}
6074

75+
// ==== ----------------------------------------------------------------------------------------------------------------
76+
// MARK: Owns Counter
77+
6178
func ownsCounter(p: ActorTestProbe<CRDT.GCounter>?) -> Behavior<Int> {
6279
.setup { context in
6380
let counter: CRDT.ActorOwned<CRDT.GCounter> = CRDT.GCounter.makeOwned(by: context, id: "counter")
@@ -72,6 +89,9 @@ final class CRDTGossipReplicationClusteredTests: ClusteredActorSystemsXCTestCase
7289
}
7390
}
7491

92+
// ==== ----------------------------------------------------------------------------------------------------------------
93+
// MARK: Owns Map
94+
7595
enum OwnsMapMessage<Value: Codable>: NonTransportableActorMessage {
7696
case set(key: String, value: Value, CRDT.OperationConsistency)
7797
}
@@ -123,15 +143,36 @@ final class CRDTGossipReplicationClusteredTests: ClusteredActorSystemsXCTestCase
123143
try self.expectSet(probe: p2, expected: ["a", "aa", "b"])
124144
}
125145

126-
struct ValueHolder<Value: Codable & Equatable>: Codable, Equatable {
127-
let value: Value?
128-
129-
init(_ value: Value? = nil) {
130-
self.value = value
146+
func test_gossip_localLWWMapUpdate_toOtherNode() throws {
147+
let configure: (inout ActorSystemSettings) -> Void = { settings in
148+
settings.crdt.gossipInterval = .seconds(1)
149+
settings.crdt.gossipIntervalRandomFactor = 0 // no random factor, exactly 1second intervals
131150
}
151+
let first = self.setUpNode("first", configure)
152+
let second = self.setUpNode("second", configure)
153+
try self.joinNodes(node: first, with: second, ensureMembers: .up)
154+
155+
let p1 = self.testKit(first).spawnTestProbe("probe-one", expecting: CRDT.LWWMap<String, String?>.self)
156+
let p2 = self.testKit(second).spawnTestProbe("probe-two", expecting: CRDT.LWWMap<String, String?>.self)
157+
158+
let one = try first.spawn("one", self.ownsLWWMap(p: p1, defaultValue: nil))
159+
let two = try second.spawn("two", self.ownsLWWMap(p: p2, defaultValue: nil))
160+
161+
one.tell(.set(key: "a", value: "foo", .local))
162+
one.tell(.set(key: "aa", value: nil, .local))
163+
164+
let gossipOneExpectMap: [String: String?] = ["a": .init("foo"), "aa": nil]
165+
try self.expectMap(probe: p1, expected: gossipOneExpectMap)
166+
try self.expectMap(probe: p2, expected: gossipOneExpectMap)
167+
168+
two.tell(.set(key: "b", value: "bar", .local))
169+
170+
let gossipTwoExpectMap: [String: String?] = ["a": "foo", "aa": nil, "b": "bar"]
171+
try self.expectMap(probe: p1, expected: gossipTwoExpectMap)
172+
try self.expectMap(probe: p2, expected: gossipTwoExpectMap)
132173
}
133174

134-
func test_gossip_localLWWMapUpdate_toOtherNode() throws {
175+
func test_gossip_localLWWMapUpdate_toOtherNode_codableValue() throws {
135176
let configure: (inout ActorSystemSettings) -> Void = { settings in
136177
settings.crdt.gossipInterval = .seconds(1)
137178
settings.crdt.gossipIntervalRandomFactor = 0 // no random factor, exactly 1second intervals
@@ -140,24 +181,22 @@ final class CRDTGossipReplicationClusteredTests: ClusteredActorSystemsXCTestCase
140181
let second = self.setUpNode("second", configure)
141182
try self.joinNodes(node: first, with: second, ensureMembers: .up)
142183

143-
let p1 = self.testKit(first).spawnTestProbe("probe-one", expecting: CRDT.LWWMap<String, ValueHolder<String?>>.self)
144-
let p2 = self.testKit(second).spawnTestProbe("probe-two", expecting: CRDT.LWWMap<String, ValueHolder<String?>>.self)
184+
let p1 = self.testKit(first).spawnTestProbe("probe-one", expecting: CRDT.LWWMap<String, CodableExampleValue>.self)
185+
let p2 = self.testKit(second).spawnTestProbe("probe-two", expecting: CRDT.LWWMap<String, CodableExampleValue>.self)
145186

146-
// 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
147-
let nilPlaceholder = ValueHolder<String?>()
148-
let one = try first.spawn("one", self.ownsLWWMap(p: p1, defaultValue: nilPlaceholder))
149-
let two = try second.spawn("two", self.ownsLWWMap(p: p2, defaultValue: nilPlaceholder))
187+
let one = try first.spawn("one", self.ownsLWWMap(p: p1, defaultValue: "<nil>"))
188+
let two = try second.spawn("two", self.ownsLWWMap(p: p2, defaultValue: "<nil>"))
150189

151-
one.tell(.set(key: "a", value: .init("foo"), .local))
152-
one.tell(.set(key: "aa", value: nilPlaceholder, .local))
190+
one.tell(.set(key: "a", value: "foo", .local))
191+
one.tell(.set(key: "aa", value: "baz", .local))
153192

154-
let gossipOneExpectMap: [String: ValueHolder<String?>] = ["a": .init("foo"), "aa": nilPlaceholder]
193+
let gossipOneExpectMap: [String: CodableExampleValue] = ["a": "foo", "aa": "baz"]
155194
try self.expectMap(probe: p1, expected: gossipOneExpectMap)
156195
try self.expectMap(probe: p2, expected: gossipOneExpectMap)
157196

158-
two.tell(.set(key: "b", value: .init("bar"), .local))
197+
two.tell(.set(key: "b", value: "bar", .local))
159198

160-
let gossipTwoExpectMap: [String: ValueHolder<String?>] = ["a": .init("foo"), "aa": nilPlaceholder, "b": .init("bar")]
199+
let gossipTwoExpectMap: [String: CodableExampleValue] = ["a": "foo", "aa": "baz", "b": "bar"]
161200
try self.expectMap(probe: p1, expected: gossipTwoExpectMap)
162201
try self.expectMap(probe: p2, expected: gossipTwoExpectMap)
163202
}
@@ -294,11 +333,11 @@ final class CRDTGossipReplicationClusteredTests: ClusteredActorSystemsXCTestCase
294333
}
295334
}
296335

297-
private func expectMap(probe: ActorTestProbe<CRDT.LWWMap<String, ValueHolder<String?>>>, expected: [String: ValueHolder<String?>], file: StaticString = #file, line: UInt = #line) throws {
336+
private func expectMap<Value>(probe: ActorTestProbe<CRDT.LWWMap<String, Value>>, expected: [String: Value], file: StaticString = #file, line: UInt = #line) throws {
298337
let testKit: ActorTestKit = self._testKits.first!
299338

300339
try testKit.eventually(within: .seconds(10)) {
301-
let replicated: CRDT.LWWMap<String, ValueHolder<String?>> = try probe.expectMessage(within: .seconds(10), file: file, line: line)
340+
let replicated: CRDT.LWWMap<String, Value> = try probe.expectMessage(within: .seconds(10), file: file, line: line)
302341
pinfo("[\(probe.name)] received updated crdt: \(replicated)")
303342

304343
guard expected == replicated.underlying else {

docker/Dockerfile

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,10 @@ ENV LANG en_US.UTF-8
1313
ENV LANGUAGE en_US.UTF-8
1414

1515
# dependencies
16-
RUN apt-get update && apt-get install -y wget
17-
RUN apt-get update && apt-get install -y lsof dnsutils netcat-openbsd net-tools curl jq # used by integration tests
18-
19-
# ruby and jazzy for docs generation
20-
RUN apt-get update && apt-get install -y ruby ruby-dev libsqlite3-dev
21-
RUN gem install jazzy --no-ri --no-rdoc
16+
RUN apt-get update && apt-get install -y wget \
17+
lsof dnsutils netcat-openbsd net-tools curl jq \
18+
ruby ruby-dev libsqlite3-dev
19+
RUN if [ "${ubuntu_version}" != "xenial" ] ; then gem install jazzy --no-ri --no-rdoc ; fi
2220
RUN gem install asciidoctor -v 1.5.8 --no-ri --no-rdoc
2321
RUN gem install asciidoctor-pdf --pre --no-ri --no-rdoc
2422
RUN gem install asciidoctor-diagram -v 1.5.8 --no-ri --no-rdoc

docker/docker-compose.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ services:
4343

4444
test:
4545
<<: *common
46-
command: /bin/bash -cl "SACT_WARNINGS_AS_ERRORS=yes swift test --enable-test-discovery -Xswiftc -DSACT_TESTS_LEAKS && ./scripts/integration_tests.sh"
46+
command: /bin/bash -cl "swift -version; SACT_WARNINGS_AS_ERRORS=yes swift test --enable-test-discovery -Xswiftc -DSACT_TESTS_LEAKS && ./scripts/integration_tests.sh"
4747

4848
bench:
4949
<<: *common

0 commit comments

Comments
 (0)