Skip to content

Crash: Installation.current setter not thread safe #443

@alessdiimperio

Description

@alessdiimperio

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

  • 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) ()

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions