Skip to content

Commit 1d08044

Browse files
Use server time for object ID per spec
This was skipped in dcdb350 due to time constraints. Resolves #50.
1 parent f928c7e commit 1d08044

File tree

7 files changed

+64
-33
lines changed

7 files changed

+64
-33
lines changed

AblyLiveObjects.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 5 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.resolved

Lines changed: 5 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@ let package = Package(
2020
dependencies: [
2121
.package(
2222
url: "https://github.com/ably/ably-cocoa",
23-
from: "1.2.46",
23+
// TODO: Unpin before next release
24+
revision: "c43c3fbaa9fadd0d6b185645a16b6fd7a55106ff",
2425
),
2526
.package(
2627
url: "https://github.com/ably/ably-cocoa-plugin-support",
2728
// Be sure to use `exact` here and not `from`; SPM does not have any special handling of 0.x versions and will resolve 'from: "0.2.0"' to anything less than 1.0.0.
28-
exact: "0.2.0",
29+
// TODO: Unpin before next release
30+
revision: "37ad19df2cc6063c74ec88ecefc2ddf491db5ebf",
2931
),
3032
.package(
3133
url: "https://github.com/apple/swift-argument-parser",

Sources/AblyLiveObjects/Internal/CoreSDK.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ internal protocol CoreSDK: AnyObject, Sendable {
88
/// Implements the internal `#publish` method of RTO15.
99
func publish(objectMessages: [OutboundObjectMessage]) async throws(ARTErrorInfo)
1010

11+
/// Implements the server time fetch of RTO16, including the storing and usage of the local clock offset.
12+
func fetchServerTime() async throws(ARTErrorInfo) -> Date
13+
1114
/// Replaces the implementation of ``publish(objectMessages:)``.
1215
///
1316
/// Used by integration tests, for example to disable `ObjectMessage` publishing so that a test can verify that a behaviour is not a side effect of an `ObjectMessage` sent by the SDK.
@@ -81,6 +84,28 @@ internal final class DefaultCoreSDK: CoreSDK {
8184
}
8285
}
8386

87+
internal func fetchServerTime() async throws(ARTErrorInfo) -> Date {
88+
try await withCheckedContinuation { (continuation: CheckedContinuation<Result<Date, ARTErrorInfo>, _>) in
89+
let internalQueue = pluginAPI.internalQueue(for: client)
90+
91+
internalQueue.async { [client, pluginAPI] in
92+
pluginAPI.nosync_fetchServerTime(for: client) { serverTime, error in
93+
// We don't currently rely on this documented behaviour of `noSync_fetchServerTime` but we may do later, so assert it to be sure it's happening.
94+
dispatchPrecondition(condition: .onQueue(internalQueue))
95+
96+
if let error {
97+
continuation.resume(returning: .failure(ARTErrorInfo.castPluginPublicErrorInfo(error)))
98+
} else {
99+
guard let serverTime else {
100+
preconditionFailure("nosync_fetchServerTime gave nil serverTime and nil error")
101+
}
102+
continuation.resume(returning: .success(serverTime))
103+
}
104+
}
105+
}
106+
}.get()
107+
}
108+
84109
internal var nosync_channelState: _AblyPluginSupportPrivate.RealtimeChannelState {
85110
pluginAPI.nosync_state(for: channel)
86111
}

Sources/AblyLiveObjects/Internal/InternalDefaultRealtimeObjects.swift

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -172,14 +172,17 @@ internal final class InternalDefaultRealtimeObjects: Sendable, LiveMapObjectsPoo
172172
}
173173

174174
internal func createMap(entries: [String: InternalLiveMapValue], coreSDK: CoreSDK) async throws(ARTErrorInfo) -> InternalDefaultLiveMap {
175-
let creationOperation = try mutableStateMutex.withSync { _ throws(ARTErrorInfo) in
175+
try mutableStateMutex.withSync { _ throws(ARTErrorInfo) in
176176
// RTO11d
177177
try coreSDK.nosync_validateChannelState(notIn: [.detached, .failed, .suspended], operationDescription: "RealtimeObjects.createMap")
178+
}
178179

180+
// RTO11f7
181+
let timestamp = try await coreSDK.fetchServerTime()
182+
183+
let creationOperation = mutableStateMutex.withSync { _ in
179184
// RTO11f
180-
// TODO: This is a stopgap; change to use server time per RTO11f5 (https://github.com/ably/ably-liveobjects-swift-plugin/issues/50)
181-
let timestamp = clock.now
182-
return ObjectCreationHelpers.nosync_creationOperationForLiveMap(
185+
ObjectCreationHelpers.nosync_creationOperationForLiveMap(
183186
entries: entries,
184187
timestamp: timestamp,
185188
)
@@ -218,8 +221,9 @@ internal final class InternalDefaultRealtimeObjects: Sendable, LiveMapObjectsPoo
218221

219222
// RTO12f
220223

221-
// TODO: This is a stopgap; change to use server time per RTO12f5 (https://github.com/ably/ably-liveobjects-swift-plugin/issues/50)
222-
let timestamp = clock.now
224+
// RTO12f5
225+
let timestamp = try await coreSDK.fetchServerTime()
226+
223227
let creationOperation = ObjectCreationHelpers.creationOperationForLiveCounter(
224228
count: count,
225229
timestamp: timestamp,

Tests/AblyLiveObjectsTests/InternalDefaultRealtimeObjectsTests.swift

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1202,15 +1202,15 @@ struct InternalDefaultRealtimeObjectsTests {
12021202
}
12031203
}
12041204

1205+
// @spec RTO11f7
12051206
// @spec RTO11g
12061207
// @spec RTO11h3a
12071208
// @spec RTO11h3b
12081209
@Test
12091210
func publishesObjectMessageAndCreatesMap() async throws {
12101211
let internalQueue = TestFactories.createInternalQueue()
1211-
let clock = MockSimpleClock(currentTime: .init(timeIntervalSince1970: 1_754_042_434))
1212-
let realtimeObjects = InternalDefaultRealtimeObjectsTests.createDefaultRealtimeObjects(clock: clock, internalQueue: internalQueue)
1213-
let coreSDK = MockCoreSDK(channelState: .attached, internalQueue: internalQueue)
1212+
let realtimeObjects = InternalDefaultRealtimeObjectsTests.createDefaultRealtimeObjects(internalQueue: internalQueue)
1213+
let coreSDK = MockCoreSDK(channelState: .attached, serverTime: .init(timeIntervalSince1970: 1_754_042_434), internalQueue: internalQueue)
12141214

12151215
// Track published messages
12161216
var publishedMessages: [OutboundObjectMessage] = []
@@ -1234,8 +1234,7 @@ struct InternalDefaultRealtimeObjectsTests {
12341234
#expect(publishedMessage.operation?.action == .known(.mapCreate))
12351235
let objectID = try #require(publishedMessage.operation?.objectId)
12361236
#expect(objectID.hasPrefix("map:"))
1237-
// TODO: This is a stopgap; change to use server time per RTO11f5 (https://github.com/ably/ably-liveobjects-swift-plugin/issues/50)
1238-
#expect(objectID.contains("1754042434000")) // check contains the mock clock's timestamp in milliseconds
1237+
#expect(objectID.contains("1754042434000")) // check contains the server timestamp in milliseconds per RTO11f7
12391238
#expect(publishedMessage.operation?.map?.entries == [
12401239
"stringKey": .init(data: .init(string: "stringValue")),
12411240
])
@@ -1336,15 +1335,15 @@ struct InternalDefaultRealtimeObjectsTests {
13361335
}
13371336
}
13381337

1338+
// @spec RTO12f5
13391339
// @spec RTO12g
13401340
// @spec RTO12h3a
13411341
// @spec RTO12h3b
13421342
@Test
13431343
func publishesObjectMessageAndCreatesCounter() async throws {
13441344
let internalQueue = TestFactories.createInternalQueue()
1345-
let clock = MockSimpleClock(currentTime: .init(timeIntervalSince1970: 1_754_042_434))
1346-
let realtimeObjects = InternalDefaultRealtimeObjectsTests.createDefaultRealtimeObjects(clock: clock, internalQueue: internalQueue)
1347-
let coreSDK = MockCoreSDK(channelState: .attached, internalQueue: internalQueue)
1345+
let realtimeObjects = InternalDefaultRealtimeObjectsTests.createDefaultRealtimeObjects(internalQueue: internalQueue)
1346+
let coreSDK = MockCoreSDK(channelState: .attached, serverTime: .init(timeIntervalSince1970: 1_754_042_434), internalQueue: internalQueue)
13481347

13491348
// Track published messages
13501349
var publishedMessages: [OutboundObjectMessage] = []
@@ -1363,8 +1362,7 @@ struct InternalDefaultRealtimeObjectsTests {
13631362
#expect(publishedMessage.operation?.action == .known(.counterCreate))
13641363
let objectID = try #require(publishedMessage.operation?.objectId)
13651364
#expect(objectID.hasPrefix("counter:"))
1366-
// TODO: This is a stopgap; change to use server time per RTO11f5 (https://github.com/ably/ably-liveobjects-swift-plugin/issues/50)
1367-
#expect(objectID.contains("1754042434000")) // check contains the mock clock's timestamp in milliseconds
1365+
#expect(objectID.contains("1754042434000")) // check contains the server timestamp in milliseconds per RTO12f5
13681366
#expect(publishedMessage.operation?.counter?.count == 10.5)
13691367

13701368
// Verify initial value was merged per RTO12h3a

Tests/AblyLiveObjectsTests/Mocks/MockCoreSDK.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ final class MockCoreSDK: CoreSDK {
88
private nonisolated(unsafe) var _publishHandler: (([OutboundObjectMessage]) async throws(ARTErrorInfo) -> Void)?
99

1010
private let channelStateMutex: DispatchQueueMutex<_AblyPluginSupportPrivate.RealtimeChannelState>
11+
private let serverTime: Date
1112

12-
init(channelState: _AblyPluginSupportPrivate.RealtimeChannelState, internalQueue: DispatchQueue) {
13+
init(channelState: _AblyPluginSupportPrivate.RealtimeChannelState, serverTime: Date = .init(), internalQueue: DispatchQueue) {
1314
channelStateMutex = DispatchQueueMutex(dispatchQueue: internalQueue, initialValue: channelState)
15+
self.serverTime = serverTime
1416
}
1517

1618
func publish(objectMessages: [OutboundObjectMessage]) async throws(ARTErrorInfo) {
@@ -35,4 +37,8 @@ final class MockCoreSDK: CoreSDK {
3537
_publishHandler = handler
3638
}
3739
}
40+
41+
func fetchServerTime() async throws(ARTErrorInfo) -> Date {
42+
serverTime
43+
}
3844
}

0 commit comments

Comments
 (0)