Skip to content

fix: serialize updateLastUseDate with messageQueue on background entry#680

Merged
nickolas-dimitrakas merged 1 commit intomainfrom
fix/serialize-updateLastUseDate-on-messageQueue
Mar 23, 2026
Merged

fix: serialize updateLastUseDate with messageQueue on background entry#680
nickolas-dimitrakas merged 1 commit intomainfrom
fix/serialize-updateLastUseDate-on-messageQueue

Conversation

@nickolas-dimitrakas
Copy link
Copy Markdown
Contributor

@nickolas-dimitrakas nickolas-dimitrakas commented Mar 23, 2026

Background

  • Crash report showed EXC_BAD_ACCESS KERN_INVALID_ADDRESS in objc_retain during CFPrefsSource mergeIntoDictionary, triggered by NSUserDefaults objectForKey:.
  • Root cause: when UIApplicationDidEnterBackgroundNotification fires, MPBackendController dispatches its background work (which reads UserDefaults via MPPersistenceController.mpId) to the serial messageQueue, while MPStateMachine called updateLastUseDate: synchronously on the main thread — writing to the same UserDefaults. The concurrent read + write on CFPrefsSource's internal dictionary caused the crash.
  • This is the same class of bug fixed in v8.44.2 (fix: serialize backgroundTimeRemaining with cancellation check #667) — thread-unsafe access during app backgrounding — but at a different point: the instant of entering background rather than during the background time check loop.

What Has Changed

  • mParticle-Apple-SDK/MPStateMachine.mhandleApplicationDidEnterBackground: now dispatches updateLastUseDate: to messageQueue via dispatch_async, serializing the write with MPBackendController's concurrent read. The _launchDate ivar is captured into a local before the block to avoid capturing self. The termination handler (handleApplicationWillTerminate:) remains synchronous because MPBackendController does not observe that notification (no race) and dispatch_async risks losing the write if the process is killed.
  • UnitTests/ObjCTests/MPStateMachineTests.m — Added 3 regression tests:
    • testUpdateLastUseDateSerializedWithMessageQueueWork: 500-iteration stress test simulating the fixed pattern (both updateLastUseDate and backend controller work on messageQueue with concurrent reads from global queues).
    • testSubscriptAccessorThreadSafety: 1000-iteration concurrent read/write stress test on MPUserDefaults subscript accessors.
    • testUpdateLastUseDateWithNilDate: edge case verifying nil date produces @0 rather than crashing.

Additional Notes

Reported crash 1
Crashed: com.apple.main-thread
0  libobjc.A.dylib                0x144c objc_retain_x0 + 16
1  libobjc.A.dylib                0x144c objc_retain + 16
2  CoreFoundation                 0x19eefc -[__NSDictionaryM __setObject:forKey:] + 360
3  CoreFoundation                 0x12aed4 __72-[CFPrefsSource mergeIntoDictionary:sourceDictionary:cloudKeyEvaluator:]_block_invoke + 32
4  CoreFoundation                 0x1a0a18 -[__NSFrozenDictionaryM __apply:context:] + 128
5  CoreFoundation                 0x12ae74 -[CFPrefsSource mergeIntoDictionary:sourceDictionary:cloudKeyEvaluator:] + 160
6  CoreFoundation                 0x1b188c -[CFPrefsSearchListSource alreadylocked_getDictionary:] + 724
7  CoreFoundation                 0x170630 -[CFPrefsSearchListSource alreadylocked_copyValueForKey:] + 172
8  CoreFoundation                 0x12ada4 -[CFPrefsSource copyValueForKey:] + 44
9  CoreFoundation                 0x198c88 __76-[_CFXPreferences copyAppValueForKey:identifier:container:configurationURL:]_block_invoke + 32
10 CoreFoundation                 0x172e74 __108-[_CFXPreferences(SearchListAdditions) withSearchListForIdentifier:container:cloudConfigurationURL:perform:]_block_invoke + 396
11 CoreFoundation                 0x13524 normalizeQuintuplet + 404
12 CoreFoundation                 0x1b1f24 -[_CFXPreferences withSearchListForIdentifier:container:cloudConfigurationURL:perform:] + 164
13 CoreFoundation                 0x198c0c -[_CFXPreferences copyAppValueForKey:identifier:container:configurationURL:] + 156
14 CoreFoundation                 0x12a8c _CFPreferencesCopyAppValueWithContainerAndConfiguration + 112
15 Foundation                     0x9d6470 -[NSUserDefaults(NSUserDefaults) objectForKey:] + 60
16 mParticle_Apple_SDK            0xac424 __swift_project_boxed_opaque_existential_0Tm + 2848
17 mParticle_Apple_SDK            0xac9a4 __swift_project_boxed_opaque_existential_0Tm + 4256
18 mParticle_Apple_SDK            0x163d4 +[MPPersistenceController_PRIVATE mpId] + 240
19 mParticle_Apple_SDK            0xb0350 __swift_project_boxed_opaque_existential_0Tm + 19020
20 mParticle_Apple_SDK            0xacc10 __swift_project_boxed_opaque_existential_0Tm + 4876
21 mParticle_Apple_SDK            0x3f0bc +[MPApplication_PRIVATE updateLastUseDate:] + 132
22 mParticle_Apple_SDK            0x63a40 -[MPStateMachine_PRIVATE handleApplicationDidEnterBackground:] + 32
23 CoreFoundation                 0x50860 __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 148
24 CoreFoundation                 0x50910 ___CFXRegistrationPost_block_invoke + 92
25 CoreFoundation                 0x5076c _CFXRegistrationPost + 436
26 CoreFoundation                 0x50f0c _CFXNotificationPost + 736
27 Foundation                     0x9633cc -[NSNotificationCenter postNotificationName:object:userInfo:] + 92
28 UIKitCore                      0x3ae1b0 __47-[UIApplication _applicationDidEnterBackground]_block_invoke + 256
29 UIKitCore                      0xc98eb0 +[UIViewController _performWithoutDeferringTransitionsAllowingAnimation:actions:] + 140
30 UIKitCore                      0x3acaf4 -[UIApplication _applicationDidEnterBackground] + 136
31 UIKitCore                      0xa2e844 __101-[_UISceneLifecycleMultiplexer _evalTransitionToSettings:fromSettings:forceExit:withTransitionStore:]_block_invoke_2 + 512
32 UIKitCore                      0xd8eb0 _UIScenePerformActionsWithLifecycleActionMask + 112
33 UIKitCore                      0xa2e564 __101-[_UISceneLifecycleMultiplexer _evalTransitionToSettings:fromSettings:forceExit:withTransitionStore:]_block_invoke + 252
34 UIKitCore                      0xa2e070 -[_UISceneLifecycleMultiplexer _performBlock:withApplicationOfDeactivationReasons:fromReasons:] + 212
35 UIKitCore                      0xa2e37c -[_UISceneLifecycleMultiplexer _evalTransitionToSettings:fromSettings:forceExit:withTransitionStore:] + 608
36 UIKitCore                      0xa2de08 -[_UISceneLifecycleMultiplexer uiScene:transitionedFromState:withTransitionContext:] + 244
37 UIKitCore                      0xa39288 __186-[_UIWindowSceneFBSSceneTransitionContextDrivenLifecycleSettingsDiffAction _performActionsForUIScene:withUpdatedFBSScene:settingsDiff:fromSettings:transitionContext:lifecycleActionType:]_block_invoke + 148
38 UIKitCore                      0xeabec4 +[BSAnimationSettings(UIKit) tryAnimatingWithSettings:fromCurrentState:actions:completion:] + 736
39 UIKitCore                      0xd9cc8 _UISceneSettingsDiffActionPerformChangesWithTransitionContextAndCompletion + 224
40 UIKitCore                      0xa38f84 -[_UIWindowSceneFBSSceneTransitionContextDrivenLifecycleSettingsDiffAction _performActionsForUIScene:withUpdatedFBSScene:settingsDiff:fromSettings:transitionContext:lifecycleActionType:] + 316
41 UIKitCore                      0x8781e4 __64-[UIScene scene:didUpdateWithDiff:transitionContext:completion:]_block_invoke.218 + 616
42 UIKitCore                      0x877278 -[UIScene _emitSceneSettingsUpdateResponseForCompletion:afterSceneUpdateWork:] + 208
43 UIKitCore                      0x877e50 -[UIScene scene:didUpdateWithDiff:transitionContext:completion:] + 244
44 UIKitCore                      0xed502c -[UIApplicationSceneClientAgent scene:handleEvent:withCompletion:] + 336
45 FrontBoardServices             0x1fc2c __76-[FBSScene updater:didUpdateSettings:withDiff:transitionContext:completion:]_block_invoke.129 + 380
46 FrontBoardServices             0x1e8a4 -[FBSScene _callOutQueue_maybeCoalesceClientSettingsUpdates:] + 128
47 FrontBoardServices             0x1f8dc -[FBSScene updater:didUpdateSettings:withDiff:transitionContext:completion:] + 708
48 FrontBoardServices             0x8fbb0 __94-[FBSWorkspaceScenesClient _queue_updateScene:withSettings:diff:transitionContext:completion:]_block_invoke_2.cold.1 + 352
49 FrontBoardServices             0x4bb30 __94-[FBSWorkspaceScenesClient _queue_updateScene:withSettings:diff:transitionContext:completion:]_block_invoke_2 + 96
50 FrontBoardServices             0x2bf64 -[FBSWorkspace _calloutQueue_executeCalloutFromSource:withBlock:] + 168
51 libdispatch.dylib              0x1b7fc _dispatch_client_callout + 16
52 libdispatch.dylib              0x6bb4 _dispatch_block_invoke_direct + 284
53 BoardServices                  0x91d4 __BSSERVICEMAINRUNLOOPQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 52
54 BoardServices                  0x9054 BSServiceMainRunLoopSourceHandler + 224
55 CoreFoundation                 0x68f10 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 28
56 CoreFoundation                 0x68e84 __CFRunLoopDoSource0 + 172
57 CoreFoundation                 0x46acc __CFRunLoopDoSources0 + 232
58 CoreFoundation                 0x1d6d8 __CFRunLoopRun + 820
59 CoreFoundation                 0x1ca6c _CFRunLoopRunSpecificWithOptions + 532
60 GraphicsServices               0x1498 GSEventRunModal + 120
61 UIKitCore                      0x9ddf8 -[UIApplication _run] + 792
62 UIKitCore                      0x46e54 UIApplicationMain + 336
63 order                      0x4821b8 (Missing UUID 4c4c44ec55553144a1d504140580fce9)
64 order                      0x80c0 main + 8 (main.swift:8)
65 ???                            0x1813bae28 (Missing)

Checklist

  • I have performed a self-review of my own code.
  • I have made corresponding changes to the documentation.
  • I have added tests that prove my fix is effective or that my feature works.
  • I have tested this locally.

Reference Issue (For employees only. Ignore if you are an outside contributor)

@nickolas-dimitrakas nickolas-dimitrakas requested a review from a team as a code owner March 23, 2026 20:25
@cursor
Copy link
Copy Markdown

cursor bot commented Mar 23, 2026

PR Summary

Medium Risk
Touches app lifecycle background handling and changes the threading model for updateLastUseDate, which could affect persistence timing or introduce ordering issues if misused. Mitigated by added regression/stress tests around concurrent MPUserDefaults access.

Overview
Prevents a background-entry crash caused by concurrent NSUserDefaults access by moving MPApplication_PRIVATE updateLastUseDate: in MPStateMachine’s handleApplicationDidEnterBackground: onto the SDK’s serial messageQueue (capturing _launchDate into a local before dispatch).

Adds new unit tests that stress concurrent reads/writes to MPUserDefaults during background-like workloads and verifies updateLastUseDate:nil persists @0 rather than crashing.

Written by Cursor Bugbot for commit 55c51fe. This will update automatically on new commits. Configure here.

@github-actions
Copy link
Copy Markdown

📦 SDK Size Impact Report

Measures how much the SDK adds to an app's size (with-SDK minus without-SDK).

Metric Target Branch This PR Change
App Bundle Impact 1.82 MB 1.82 MB +N/A
Executable Impact 896 bytes 896 bytes +N/A
XCFramework Size 9.49 MB 9.49 MB -4 KB

➡️ SDK size impact change is minimal.

Raw measurements

Target branch (main):

{"baseline_app_size_kb":84,"baseline_executable_size_bytes":75464,"with_sdk_app_size_kb":1944,"with_sdk_executable_size_bytes":76360,"sdk_impact_kb":1860,"sdk_executable_impact_bytes":896,"xcframework_size_kb":9720}

This PR:

{"baseline_app_size_kb":84,"baseline_executable_size_bytes":75464,"with_sdk_app_size_kb":1944,"with_sdk_executable_size_bytes":76360,"sdk_impact_kb":1860,"sdk_executable_impact_bytes":896,"xcframework_size_kb":9716}

@nickolas-dimitrakas nickolas-dimitrakas self-assigned this Mar 23, 2026
@nickolas-dimitrakas nickolas-dimitrakas requested review from BrandonStalnaker and jamesnrokt and removed request for jamesnrokt March 23, 2026 20:27
@nickolas-dimitrakas nickolas-dimitrakas merged commit ba76afc into main Mar 23, 2026
27 of 28 checks passed
@nickolas-dimitrakas nickolas-dimitrakas deleted the fix/serialize-updateLastUseDate-on-messageQueue branch March 23, 2026 21:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants