isLowPowerModeEnabled causes a deadlock on iOS 15

Originator:ncreated
Number:rdar://FB9741207 Date Originated:Nov 5, 2020
Status:Open Resolved:
Product:iOS Product Version:15
Classification: Reproducible:Very rare
 
We work on a SDK and our users report unfrequent crashes (on iOS15, 15.1 and 15.2) in the code that can be simplified to:

```
observer = NotificationCenter.default
    .addObserver(
        forName: .NSProcessInfoPowerStateDidChange,
        object: nil,
        queue: .main
    ) { notification in
        guard let processInfo = notification.object as? ProcessInfo else {
            return
        }
        
        let lmpe = processInfo.isLowPowerModeEnabled // CRASH on _os_unfair_lock_recursive_abort in libsystem_platform.dylib

        print(lmpe)
    }
```

In some circumstances, getting `.isLowPowerModeEnabled` from `ProcessInfo` **within the `. NSProcessInfoPowerStateDidChange` notification callback** causes a dead lock in `libsystem_platform.dylib` and leads to `_os_unfair_lock_recursive_abort` crash.

Unfortunately, I didn’t succeed in reproducing this issue locally and capturing Apple’s crash report, so I can only rely on backtraces reported by our users (below). Some of them include `WebCore` symbols, which could be a potential external factor causing this crash. I also found this recent change to Low Power Mode management in WebKit (dated Dec 2020), but I’m not sure if this is relevant: https://trac.webkit.org/changeset/270406/webkit.

```
Crashed: com.apple.main-thread
0  libsystem_platform.dylib       0x60c0 _os_unfair_lock_recursive_abort + 36
1  libsystem_platform.dylib       0xa10 _os_unfair_lock_lock_slow + 304
2  Foundation                     0x29730 -[NSProcessInfo(NSProcessInfoHardwareState) isLowPowerModeEnabled] + 68
3  Datadog                        0x2a680 closure #1 in LowPowerModeMonitor.init(initialProcessInfo:notificationCenter:) + 140 (MobileDevice.swift:140)
4  Datadog                        0x2a720 thunk for @escaping @callee_guaranteed (@in_guaranteed Notification) -> () + 4339345184 (<compiler-generated>:4339345184)
5  Foundation                     0x355b0 -[__NSObserver _doit:] + 348
6  CoreFoundation                 0x2aee8 __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 28
7  CoreFoundation                 0xc6b9c ___CFXRegistrationPost_block_invoke + 52
8  CoreFoundation                 0x99f54 _CFXRegistrationPost + 456
9  CoreFoundation                 0x40d54 _CFXNotificationPost + 716
10 Foundation                     0x1b028 -[NSNotificationCenter postNotificationName:object:userInfo:] + 96
11 Foundation                     0x939d4 NSProcessInfoNotifyPowerState + 188
12 Foundation                     0x29768 -[NSProcessInfo(NSProcessInfoHardwareState) isLowPowerModeEnabled] + 124
13 WebCore                        0x9118 <redacted> + 96
14 WebCore                        0xa8516c WebCore::LowPowerModeNotifier::LowPowerModeNotifier(WTF::Function<void (bool)>&&) + 52
15 WebCore                        0x19aac4c WebCore::Page::Page(WebCore::PageConfiguration&&) + 1848
16 WebKitLegacy                   0x3e34 -[WebView(WebPrivate) _commonInitializationWithFrameName:groupName:] + 3060
17 WebKitLegacy                   0x3214 -[WebView(WebPrivate) _initWithFrame:frameName:groupName:] + 116
18 UIFoundation                   0xdd028 -[NSHTMLReader _loadUsingWebKit] + 832
19 Foundation                     0x3de0c __NSThreadPerformPerform + 232
20 CoreFoundation                 0xbb030 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 28
21 CoreFoundation                 0xcbcf0 __CFRunLoopDoSource0 + 208
22 CoreFoundation                 0x5ff8 __CFRunLoopDoSources0 + 268
23 CoreFoundation                 0xb804 __CFRunLoopRun + 820
24 CoreFoundation                 0x1f3c8 CFRunLoopRunSpecific + 600
25 GraphicsServices               0x138c GSEventRunModal + 164
26 UIKitCore                      0x51b060 -[UIApplication _run] + 1100
27 UIKitCore                      0x298b8c UIApplicationMain + 2124
```

```
Crashed: com.apple.main-thread
0  libsystem_platform.dylib       0x8e4c _os_unfair_lock_recursive_abort + 36
1  libsystem_platform.dylib       0x17e4 _os_unfair_lock_lock_slow + 324
2  Foundation                     0x27b08 -[NSProcessInfo(NSProcessInfoHardwareState) isLowPowerModeEnabled] + 64
3  Datadog                        0x2a680 closure #1 in LowPowerModeMonitor.init(initialProcessInfo:notificationCenter:) + 140 (MobileDevice.swift:140)
4  Datadog                        0x2a720 thunk for @escaping @callee_guaranteed (@in_guaranteed Notification) -> () + 4374603552 (<compiler-generated>:4374603552)
5  Foundation                     0x335f8 -[__NSObserver _doit:] + 316
6  CoreFoundation                 0x28c5c __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 20
7  CoreFoundation                 0xbd564 ___CFXRegistrationPost_block_invoke + 48
8  CoreFoundation                 0x92b44 _CFXRegistrationPost + 416
9  CoreFoundation                 0x3d764 _CFXNotificationPost + 696
10 Foundation                     0x19c98 -[NSNotificationCenter postNotificationName:object:userInfo:] + 92
11 Foundation                     0x8d998 NSProcessInfoNotifyPowerState + 184
12 Foundation                     0x27b40 -[NSProcessInfo(NSProcessInfoHardwareState) isLowPowerModeEnabled] + 120
13 Datadog                        0x2a680 closure #1 in LowPowerModeMonitor.init(initialProcessInfo:notificationCenter:) + 140 (MobileDevice.swift:140)
14 Datadog                        0x2a720 thunk for @escaping @callee_guaranteed (@in_guaranteed Notification) -> () + 4374603552 (<compiler-generated>:4374603552)
15 Foundation                     0x335f8 -[__NSObserver _doit:] + 316
16 CoreFoundation                 0x28c5c __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 20
17 CoreFoundation                 0xbd564 ___CFXRegistrationPost_block_invoke + 48
18 Foundation                     0x4e21c __NSBLOCKOPERATION_IS_CALLING_OUT_TO_A_BLOCK__ + 16
19 Foundation                     0x5f6f4 -[NSBlockOperation main] + 100
20 Foundation                     0x39ddc __NSOPERATION_IS_INVOKING_MAIN__ + 20
21 Foundation                     0x49f1c -[NSOperation start] + 780
22 Foundation                     0x4d314 __NSOPERATIONQUEUE_IS_STARTING_AN_OPERATION__ + 20
23 Foundation                     0x5aa2c __NSOQSchedule_f + 180
24 libdispatch.dylib              0x632ec _dispatch_call_block_and_release + 24
25 libdispatch.dylib              0x642f0 _dispatch_client_callout + 16
26 libdispatch.dylib              0x109a0 _dispatch_main_queue_callback_4CF$VARIANT$mp + 936
27 CoreFoundation                 0x4d7f8 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 12
28 CoreFoundation                 0xb0f8 __CFRunLoopRun + 2528
29 CoreFoundation                 0x1dd8c CFRunLoopRunSpecific + 572
30 GraphicsServices               0x19a0 GSEventRunModal + 160
31 UIKitCore                      0x4edfa8 -[UIApplication _run] + 1080
32 UIKitCore                      0x28222c UIApplicationMain + 2060
```

```
Thread 0 [Crashed]:

0    libsystem_platform.dylib                 0x1f15920c0     _os_unfair_lock_recursive_abort + 36
1    libsystem_platform.dylib                 0x1f158ca10     _os_unfair_lock_lock_slow + 303
2    Foundation                               0x183211730     -[NSProcessInfo(NSProcessInfoHardwareState) isLowPowerModeEnabled] + 67
3    Datadog                                  0x102bdfa28     closure #1 in LowPowerModeMonitor.init(MobileDevice.swift:140)
4    Datadog                                  0x102bdfac8     thunk for @escaping @callee_guaranteed (<compiler-generated>:0)
5    Foundation                               0x18321d5b0     -[__NSObserver _doit:] + 347
6    CoreFoundation                           0x1819e8ee8     _CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 27
7    CoreFoundation                           0x181a84b9c     __CFXRegistrationPost_block_invoke + 51
8    CoreFoundation                           0x181a57f54     _CFXRegistrationPost + 455
9    CoreFoundation                           0x1819fed54     _CFXNotificationPost + 715
10   Foundation                               0x183203028     -[NSNotificationCenter postNotificationName:object:userInfo:] + 95
11   Foundation                               0x18327b9d4     NSProcessInfoNotifyPowerState + 187
12   Foundation                               0x183211768     -[NSProcessInfo(NSProcessInfoHardwareState) isLowPowerModeEnabled] + 123
13   WebCore                                  0x190a48118     -[WebLowPowerModeObserver initWithNotifier:] + 95
14   WebCore                                  0x1914c416c     WebCore::LowPowerModeNotifier::LowPowerModeNotifier(WTF::Function<void (bool)>&&) + 51
15   WebCore                                  0x1923e9c4c     WebCore::Page::Page(WebCore::PageConfiguration&&) + 1847
16   WebKitLegacy                             0x1a550de34     -[WebView(WebPrivate) _commonInitializationWithFrameName:groupName:] + 3059
17   WebKitLegacy                             0x1a550d214     -[WebView(WebPrivate) _initWithFrame:frameName:groupName:] + 115
18   UIFoundation                             0x18c7b9028     -[NSHTMLReader _loadUsingWebKit] + 831
19   UIFoundation                             0x18c7ba15c     -[NSHTMLReader attributedString] + 31
20   UIFoundation                             0x18c7734e8     _NSReadAttributedStringFromURLOrData + 8419
21   UIFoundation                             0x18c771378     -[NSAttributedString(NSAttributedStringUIFoundationAdditions) initWithData:options:documentAttributes:error:] + 155
```

All crashes occur on iOS 15 (so far we received reports for iOS 15, 15.1 and 15.2).
Similar feedback has already been reported in FB9661108.

---

**Please list the steps you took to reproduce the issue:**
With my best efforts, I couldn’t reproduce it locally. I only rely on crash reports reported by our customers.

**What did you expect to happen?**
Obtaining `ProcessInfo.processInfo.isLowPowerModeEnabled` should be thread safe no matter of surrounding code. Reading it from `.NSProcessInfoPowerStateDidChange` notification callback should not lead to crash.

**What actually happened?**
Sometimes, reading `.isLowPowerModeEnabled` from `ProcessInfo` **within the `. NSProcessInfoPowerStateDidChange` notification callback** causes a dead lock in `libsystem_platform.dylib` and leads to `_os_unfair_lock_recursive_abort` crash.

Comments

The status of this issue was updated with: `Recent Similar Reports:Less than 10 Resolution:Potential fix identified - For a future OS update `


Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!