Skip to content

Instantly share code, notes, and snippets.

@samsonjs
Last active June 22, 2024 16:46
Show Gist options
  • Save samsonjs/70b6b3a0f46c4bead3c650ff4be83090 to your computer and use it in GitHub Desktop.
Save samsonjs/70b6b3a0f46c4bead3c650ff4be83090 to your computer and use it in GitHub Desktop.
SyncState isolation example
final class SendableWrapper<T>: @unchecked Sendable {
private var unsafeValue: T
private let lock = NSLock()
var value: T {
get {
lock.withLock { unsafeValue }
}
set {
lock.withLock { unsafeValue = newValue }
}
}
init(_ value: T) {
unsafeValue = value
}
}
typealias UpdateSequenceNumber = Int
final class StoreSyncState: SyncState {
private let store: any SyncStore
private var unsafeServiceUSN = SendableWrapper(UpdateSequenceNumber(0))
private let lock = NSLock()
var serviceUSN: UpdateSequenceNumber {
get { lock.withLock { unsafeServiceUSN.value } }
set { lock.withLock { unsafeServiceUSN.value = newValue } }
}
private(set) var usn: UpdateSequenceNumber
init(store: any SyncStore) throws {
self.store = store
self.usn = try store.lastUpdateCount
}
func updateUSN(_ newUSN: UpdateSequenceNumber) throws {
SyncLogger.shared.log("Updating USN to \(newUSN)")
try store.updateLastUpdateCount(newUSN)
usn = newUSN
}
}
actor SyncEngine {
let store: any SyncStore
let service: SyncService
private func syncInForeground() async throws -> SyncResult {
let state = try StoreSyncState(store: store)
state.serviceUSN = 42 // in reality this talks to a server, unimportant details
// send our local changes to the server
let dirtyProjects = try store.dirtyProjects(since: state.serviceUSN)
let (projectsToCreate, projectsToUpdate) = dirtyProjects.partition { $0.usn == nil }
try await state.updatingUSN {
var result = state.serviceUSN
if projectsToCreate.isEmpty == false {
let response = try await service.createProjects(projectsToCreate)
for (project, usn) in zip(projectsToCreate, response.updateCounts) {
try store.didSyncProject(id: project.id, usn: usn)
result = usn
}
}
return result
}
for project in projectsToUpdate {
try await state.updatingUSN {
let response = try await service.updateProject(project)
try store.didSyncProject(id: project.id, usn: response.updateCount)
return response.updateCount
}
}
// ... more of the same elided
}
}
protocol SyncState {
var serviceUSN: UpdateSequenceNumber { get set }
var usn: UpdateSequenceNumber { get }
func updateUSN(_ newUSN: UpdateSequenceNumber) throws
func updatingUSN(
_ actor: isolated any Actor,
_ makeUSN: () async throws -> UpdateSequenceNumber
) async throws
}
extension SyncState {
func updatingUSN(
_ actor: isolated any Actor = #isolation,
_ makeUSN: () async throws -> UpdateSequenceNumber
) async throws {
try await updateUSN(makeUSN())
}
}
protocol SyncStore: AnyObject {
// details unimportant
}
class FileSyncStore: SyncStore {
// details unimportant
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment