parse-community / Parse-Swift

The Swift SDK for Parse Platform (iOS, macOS, watchOS, tvOS, Linux, Android, Windows)
https://parseplatform.org
MIT License
308 stars 69 forks source link

Crash: Installation.current setter not thread safe #443

Open alessdiimperio opened 1 year ago

alessdiimperio commented 1 year ago

New Issue Checklist

Issue Description

We store user information on the installation object. And when modifying or changing the data we save the installation. The issue seems to be that the underlying InMemoryKeyValueStore setter does not support concurrency.

we have services and managers that handle different parts of our app and for example if a user logs in we set a pointer on the installation with the current user and save it async. We also set the push permissions state async and save we also set the channels and device token async and save we check analytics tracking consent and if accepted async and save

now not all of these are fired off at the same time but in some cases 2-3 of these saves may be executed concurrently on app startup which causes the setter to be called concurrently and attempt to write to the underlying dictionary at the same time causing a runtime error as write operations on a dictionary may not be done concurrently.

mutating func set(_ object: T, for key: String) throws where T: Encodable { let data = try encoder.encode(object) storage[key] = data <- this is where it crashes }

Perhaps modifying the storage[:] dictionary should be done on a custom serial queue so it cannot be modified concurrently on different threads?

Steps to reproduce

for index in 1..<10 { Task.detached { try await installation.save() } }

Actual Outcome

It crashes with runtime exception

Expected Outcome

Should not crash

Environment

Client

Server

Database

Logs

0 0x0000000113814acb in objc_msgSend ()

1 0x000000011f261b9c in Dictionary.Variant.setValue(:forKey:) ()

2 0x000000011f224ae8 in Dictionary.subscript.setter ()

3 0x0000000116428ecd in InMemoryKeyValueStore.set<τ_00>(:for:) at /Users/alessio.diimperio/Documents/ios/Pods/ParseSwift/Sources/ParseSwift/Storage/ParseKeyValueStore.swift:61

4 0x00000001164291bd in protocol witness for ParsePrimitiveStorable.set<τ_00>(:for:) in conformance InMemoryKeyValueStore ()

5 0x000000011651af87 in ParseStorage.set<τ_00>(:for:) at /Users/alessio.diimperio/Documents/ios/Pods/ParseSwift/Sources/ParseSwift/Storage/ParseStorage.swift:56

6 0x00000001163ddc00 in static ParseInstallation.currentContainer.setter at /Users/alessio.diimperio/Documents/ios/Pods/ParseSwift/Sources/ParseSwift/Objects/ParseInstallation.swift:257

7 0x00000001163decbf in static ParseInstallation.current.setter at /Users/alessio.diimperio/Documents/ios/Pods/ParseSwift/Sources/ParseSwift/Objects/ParseInstallation.swift:302

8 0x00000001163e3b99 in static ParseInstallation.updateKeychainIfNeeded(_:deleting:) at /Users/alessio.diimperio/Documents/ios/Pods/ParseSwift/Sources/ParseSwift/Objects/ParseInstallation.swift:495

9 0x0000000116417736 in ParseInstallation.command(method:ignoringCustomObjectIdConfig:options:callbackQueue:) at /Users/alessio.diimperio/Documents/ios/Pods/ParseSwift/Sources/ParseSwift/Objects/ParseInstallation+async.swift:356

10 0x00000001163e76b0 in closure #1 in ParseInstallation.save(ignoringCustomObjectIdConfig:options:callbackQueue:completion:) at /Users/alessio.diimperio/Documents/ios/Pods/ParseSwift/Sources/ParseSwift/Objects/ParseInstallation.swift:699

11 0x00000001163e8570 in partial apply for closure #1 in ParseInstallation.save(ignoringCustomObjectIdConfig:options:callbackQueue:completion:) ()

12 0x0000000116299580 in thunk for @escaping @callee_guaranteed @Sendable @async () -> (@out τ_0_0) ()

13 0x000000011629a4b0 in partial apply for thunk for @escaping @callee_guaranteed @Sendable @async () -> (@out τ_0_0) ()

parse-github-assistant[bot] commented 1 year ago

Thanks for opening this issue!