sindresorhus / Defaults

💾 Swifty and modern UserDefaults
https://swiftpackageindex.com/sindresorhus/Defaults/documentation/defaults
MIT License
1.93k stars 115 forks source link

Crashes related to KVO and UserDefaults #161

Open leoMehlig opened 5 months ago

leoMehlig commented 5 months ago

Since using this library, we are seeing some crash reports which look related to KVO and UserDefaults. We haven't been able to reproduce them but are seeing crash reports on all iOS versions (although more on iOS 15).

Has anyone seen something similar or any pointers on how to fix this (happy to submit a PR)?

Here are some ideas/observations/infos:

OS Version: iOS 17.2.1 (21C66)
Report Version: 104

Exception Type: EXC_BREAKPOINT (SIGTRAP)
Crashed Thread: 13

Application Specific Information:
_replaceRangeInArrayAtIndex:withRange:

Thread 13 Crashed:
0   libobjc.A.dylib                 0x31c20a838         object_getClass
1   Foundation                      0x329a60850         _NSKeyValueObservationInfoGetObservances
2   Foundation                      0x329a605b0         NSKeyValueWillChange
3   Foundation                      0x329adda48         -[NSObject(NSKeyValueObservingPrivate) _changeValueForKeys:count:maybeOldValuesDict:maybeNewValuesDict:usingBlock:]
4   Foundation                      0x329b39a20         -[NSObject(NSKeyValueObservingPrivate) _notifyObserversOfChangeFromValuesForKeys:toValuesForKeys:]
5   CoreFoundation                  0x32bbcebe4         -[CFPrefsSource forEachObserver:]
6   CoreFoundation                  0x32bbbe898         -[CFPrefsSource _notifyObserversOfChangeFromValuesForKeys:toValuesForKeys:]
7   CoreFoundation                  0x32bbbe794         ___CFPrefsDeliverPendingKVONotificationsGuts_block_invoke
8   CoreFoundation                  0x32bb46858         __CFDictionaryApplyFunction_block_invoke
9   CoreFoundation                  0x32bb44dec         CFBasicHashApply
10  CoreFoundation                  0x32bb35bc8         CFDictionaryApplyFunction
11  CoreFoundation                  0x32bb6a3c0         _CFPrefsDeliverPendingKVONotificationsGuts
12  CoreFoundation                  0x32bb68a60         -[_CFXPreferences _deliverPendingKVONotifications]
13  CoreFoundation                  0x32bb68954         __108-[_CFXPreferences withSearchListForIdentifier:container:cloudConfigurationURL:perform:]_block_invoke
14  CoreFoundation                  0x32bb68748         normalizeQuintuplet
15  CoreFoundation                  0x32bb685bc         -[_CFXPreferences withSearchListForIdentifier:container:cloudConfigurationURL:perform:]
16  CoreFoundation                  0x32bbc92a4         -[_CFXPreferences setValue:forKey:appIdentifier:container:configurationURL:]
17  CoreFoundation                  0x32bbc91e4         _CFPreferencesSetAppValueWithContainerAndConfiguration
18  Foundation                      0x329b0a8f0         -[NSUserDefaults(NSUserDefaults) setObject:forKey:]
19  Structured                      0x2053b982c         NSUserDefaults._set<T> (UserDefaults.swift:18)
20  Structured                      0x2053b9ae8         NSUserDefaults.subscript.setter (UserDefaults.swift:24)
21  Structured                      0x2053b93f4         NSUserDefaults.subscript.setter
22  Structured                      0x2054365a0         [inlined] Defaults.subscript.setter
23  Structured                      0x2054365a0         BackupManager.createBackup (Backup.swift:378)
OS Version: iOS 15.6.1 (19G82)
Report Version: 104

Exception Type: EXC_BREAKPOINT (SIGTRAP)
Crashed Thread: 24

Application Specific Information:
_replaceRangeInArrayAtIndex:withRange: > removeIndex:

Thread 24 Crashed:
0   libobjc.A.dylib                 0x38c072028         object_getClass
1   Foundation                      0x35d53a86c         _NSKeyValueObservationInfoGetObservances
2   Foundation                      0x35d53bf38         NSKeyValueWillChange
3   Foundation                      0x35d5248a4         -[NSObject(NSKeyValueObservingPrivate) _changeValueForKeys:count:maybeOldValuesDict:maybeNewValuesDict:usingBlock:]
4   Foundation                      0x35d543604         -[NSObject(NSKeyValueObservingPrivate) _notifyObserversOfChangeFromValuesForKeys:toValuesForKeys:]
5   CoreFoundation                  0x35a568004         -[CFPrefsSource forEachObserver:]
6   CoreFoundation                  0x35a603148         -[CFPrefsSource _notifyObserversOfChangeFromValuesForKeys:toValuesForKeys:]
7   CoreFoundation                  0x35a5de1b8         ___CFPrefsDeliverPendingKVONotificationsGuts_block_invoke
8   CoreFoundation                  0x35a5337d8         __CFDictionaryApplyFunction_block_invoke
9   CoreFoundation                  0x35a531090         CFBasicHashApply
10  CoreFoundation                  0x35a532fb0         CFDictionaryApplyFunction
11  CoreFoundation                  0x35a5c9558         _CFPrefsDeliverPendingKVONotificationsGuts
12  CoreFoundation                  0x35a52ffec         -[_CFXPreferences _deliverPendingKVONotifications]
13  CoreFoundation                  0x35a55ed3c         __108-[_CFXPreferences withSearchListForIdentifier:container:cloudConfigurationURL:perform:]_block_invoke
14  CoreFoundation                  0x35a536bd0         normalizeQuintuplet
15  CoreFoundation                  0x35a52fcc4         -[_CFXPreferences withSearchListForIdentifier:container:cloudConfigurationURL:perform:]
16  CoreFoundation                  0x35a5e1338         -[_CFXPreferences setValue:forKey:appIdentifier:container:configurationURL:]
17  CoreFoundation                  0x35a5642a4         _CFPreferencesSetAppValueWithContainerAndConfiguration
18  Foundation                      0x35d5337c8         -[NSUserDefaults(NSUserDefaults) setObject:forKey:]
19  Structured                      0x20126887c         NSUserDefaults._set<T> (UserDefaults.swift:18)
20  Structured                      0x201268b38         NSUserDefaults.subscript.setter (UserDefaults.swift:24)
21  Structured                      0x201268444         NSUserDefaults.subscript.setter
22  Structured                      0x200c4e018         [inlined] Defaults.subscript.setter
23  Structured                      0x200c4e018         AppState.activate (AppState.swift:182)
24  WidgetKit                       0x369c6ff3c         objectdestroy.36Tm
sindresorhus commented 5 months ago

Which Defaults version? And which observation methods?

leoMehlig commented 5 months ago

@sindresorhus We are using 7.3 in the latest production, and use the Defaults.publisher(keys:) and Defaults.updates methods.

sindresorhus commented 5 months ago

All crashes happen on a background queue, which might suggest it is some concurrency issue. UserDefaults is thread safe, but maybe KVO isn't?

The crash happens in Apple's code that notifies about KVO, not the code that subscribes to KVO, so this really seems like UserDefaults itself is accidentally not fully thread-safe.

sindresorhus commented 5 months ago

Without a way to reproduce the issue locally, I cannot really come up with a workaround. Defaults cannot wrap calls to UserDefaulta in DispatchQueue.main.async as it can cause synchronization issues (for example, if the value is read right after setting it).

sindresorhus commented 5 months ago

I also have not seen this kind of crash in any of my 40 apps (almost all of them use Defaults).

sindresorhus commented 5 months ago

You could try this: https://github.com/sindresorhus/Defaults/commit/eef0df09305106ff18ba9d575c1144d3c886c62d But it could cause problems if the value is read right after.

leoMehlig commented 5 months ago

Thanks a lot. I've moved to this branch and will report bck after the next update.