-
-
Notifications
You must be signed in to change notification settings - Fork 69
Description
New Issue Checklist
- [x ] I am not disclosing a vulnerability.
- [ x] I am not just asking a question.
- [ x] I have searched through existing issues.
- [ x] I can reproduce the issue with the latest versions of Parse Server and the Parse Swift SDK.
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
- Parse Swift SDK version:
4.14.2
- Xcode version:
14.3.1
- Operating system (iOS, macOS, watchOS, etc.):
iOS
- Operating system version:
17.0.0
Server
- Parse Server version:
4.3
- Operating system:
linux
- Local or remote host (AWS, Azure, Google Cloud, Heroku, Digital Ocean, etc):
local
Database
- System (MongoDB or Postgres):
SQL
- Database version:
:shrug:
- Local or remote host (MongoDB Atlas, mLab, AWS, Azure, Google Cloud, etc):
local
Logs
#0 0x0000000113814acb in objc_msgSend ()
#1 0x000000011f261b9c in Dictionary.Variant.setValue(:forKey:) ()
#2 0x000000011f224ae8 in Dictionary.subscript.setter ()
#3 0x0000000116428ecd in InMemoryKeyValueStore.set<τ_0_0>(:for:) at /Users/alessio.diimperio/Documents/ios/Pods/ParseSwift/Sources/ParseSwift/Storage/ParseKeyValueStore.swift:61
#4 0x00000001164291bd in protocol witness for ParsePrimitiveStorable.set<τ_0_0>(:for:) in conformance InMemoryKeyValueStore ()
#5 0x000000011651af87 in ParseStorage.set<τ_0_0>(: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) ()