Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RUMM-1691 Reduce utilization of CTTelephonyNetworkInfo to mitigate CarrierInfoProvider crash #627

Merged
merged 5 commits into from
Oct 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 51 additions & 4 deletions Datadog/Example/Base.lproj/Main.storyboard
Original file line number Diff line number Diff line change
Expand Up @@ -320,28 +320,74 @@
<constraint firstAttribute="height" constant="44" id="qZn-EW-PqZ"/>
</constraints>
</stackView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Rlt-GG-uem" userLabel="Vertical gap 10">
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ghm-SJ-eBQ" userLabel="Vertical gap 10">
<rect key="frame" x="0.0" y="251" width="384" height="10"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstAttribute="height" constant="10" id="d0W-7M-BGO"/>
</constraints>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="STRESS TESTING:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hVf-hM-tBK">
<rect key="frame" x="0.0" y="261" width="384" height="13.5"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleCaption1"/>
<color key="textColor" systemColor="systemGrayColor"/>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="VDB-9q-Bc1" userLabel="Vertical gap 5">
<rect key="frame" x="0.0" y="274.5" width="384" height="5"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstAttribute="height" constant="5" id="ObB-P8-mKh"/>
</constraints>
</view>
<stackView opaque="NO" contentMode="scaleToFill" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="l6y-hy-nRu">
<rect key="frame" x="0.0" y="279.5" width="384" height="44"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="n8y-PP-kzI">
<rect key="frame" x="0.0" y="0.0" width="384" height="44"/>
<color key="backgroundColor" red="0.38823529410000002" green="0.17254901959999999" blue="0.65098039220000004" alpha="1" colorSpace="calibratedRGB"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<state key="normal" title="Keep sending ~500 logs/s for 10s">
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<color key="titleShadowColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</state>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="layer.masksToBounds" value="YES"/>
<userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
<integer key="value" value="7"/>
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
<connections>
<action selector="didTapStressTest:" destination="BYZ-38-t0r" eventType="touchUpInside" id="SzO-9c-7v5"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstAttribute="height" constant="44" id="VgF-xP-icc"/>
</constraints>
</stackView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Rlt-GG-uem" userLabel="Vertical gap 10">
<rect key="frame" x="0.0" y="323.5" width="384" height="10"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstAttribute="height" constant="10" id="oUy-Tr-5NI"/>
</constraints>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="CONSOLE OUTPUT:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="x1i-in-lYX">
<rect key="frame" x="0.0" y="261" width="384" height="13.5"/>
<rect key="frame" x="0.0" y="333.5" width="384" height="13.5"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleCaption1"/>
<color key="textColor" systemColor="systemGrayColor"/>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="MU9-DG-sTJ" userLabel="Vertical gap 10">
<rect key="frame" x="0.0" y="274.5" width="384" height="10"/>
<rect key="frame" x="0.0" y="347" width="384" height="10"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstAttribute="height" constant="10" id="P0L-Pu-XZ9"/>
</constraints>
</view>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" editable="NO" textAlignment="natural" selectable="NO" translatesAutoresizingMaskIntoConstraints="NO" id="2G0-OF-tGX">
<rect key="frame" x="0.0" y="284.5" width="384" height="459.5"/>
<rect key="frame" x="0.0" y="357" width="384" height="387"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<color key="textColor" systemColor="systemGrayColor"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/>
Expand All @@ -367,6 +413,7 @@
<outlet property="logServiceNameTextField" destination="fGc-5E-uEZ" id="15I-2q-gUt"/>
<outlet property="send10xButton" destination="u78-T2-8Dx" id="EK9-y6-WQK"/>
<outlet property="sendOnceButton" destination="gtC-h0-4xC" id="9Cc-de-Und"/>
<outlet property="stressTestButton" destination="n8y-PP-kzI" id="tKj-cc-sV9"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
Expand Down
41 changes: 41 additions & 0 deletions Datadog/Example/Debugging/DebugLoggingViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
*/

import UIKit
import Datadog

class DebugLoggingViewController: UIViewController {
@IBOutlet weak var logLevelSegmentedControl: UISegmentedControl!
@IBOutlet weak var logMessageTextField: UITextField!
@IBOutlet weak var logServiceNameTextField: UITextField!
@IBOutlet weak var sendOnceButton: UIButton!
@IBOutlet weak var send10xButton: UIButton!
@IBOutlet weak var stressTestButton: UIButton!
@IBOutlet weak var consoleTextView: UITextView!

struct StructError: Error {
Expand Down Expand Up @@ -78,4 +80,43 @@ class DebugLoggingViewController: UIViewController {
private func repeat10x(block: () -> Void) {
(0..<10).forEach { _ in block() }
}

// MARK: - Stress testing

var queues: [DispatchQueue] = []
var loggers: [Logger] = []

@IBAction func didTapStressTest(_ sender: Any) {
stressTestButton.disableFor(seconds: 10)

loggers = (0..<5).map { index in
return Logger.builder.set(loggerName: "stress-logger-\(index)")
.sendNetworkInfo(true)
.build()
}

queues = (0..<5).map { index in
return DispatchQueue(label: "com.datadoghq.example.stress-testing-queue\(index)")
}

let endDate = Date(timeIntervalSinceNow: 10) // 10s
zip(loggers, queues).forEach { logger, queue in
keepSendingLogs(on: queue, using: logger, every: 0.01, until: endDate)
}
}

private func keepSendingLogs(on queue: DispatchQueue, using logger: Logger, every timeInterval: TimeInterval, until endDate: Date) {
if Date() < endDate {
queue.asyncAfter(deadline: .now() + timeInterval) { [weak self] in
logger.debug(self?.randomLogMessage() ?? "")
self?.keepSendingLogs(on: queue, using: logger, every: timeInterval, until: endDate)
}
}
}

private let alphanumerics = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

private func randomLogMessage() -> String {
return String((0..<20).map { _ in alphanumerics.randomElement()! })
}
}
151 changes: 105 additions & 46 deletions Sources/Datadog/Core/System/CarrierInfoProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,88 +66,147 @@ extension CarrierInfo.RadioAccessTechnology {
}
}

/// An interface for the target-specific carrier info provider.
internal protocol WrappedCarrierInfoProvider {
var current: CarrierInfo? { get }
}

/// Platform-agnostic carrier info provider. It wraps the platform-specific provider inside.
internal class CarrierInfoProvider: CarrierInfoProviderType {
/// The `CarrierInfo` provider for the current platform.
private let wrappedProvider: WrappedCarrierInfoProvider
/// Publisher for notifying observers on `CarrierInfo` change.
private let publisher: ValuePublisher<CarrierInfo?>
private let wrappedProvider: CarrierInfoProviderType

convenience init() {
#if targetEnvironment(macCatalyst)
self.init(
wrappedProvider: MacCatalystCarrierInfoProvider()
)
self.init(
wrappedProvider: MacCatalystCarrierInfoProvider()
)
#else
self.init(
wrappedProvider: iOSCarrierInfoProvider(
networkInfo: CTTelephonyNetworkInfo()
)
)
if #available(iOS 12.0, *) {
self.init(wrappedProvider: iOS12CarrierInfoProvider(networkInfo: CTTelephonyNetworkInfo()))
} else {
self.init(wrappedProvider: iOS11CarrierInfoProvider(networkInfo: CTTelephonyNetworkInfo()))
}
#endif
}

init(wrappedProvider: WrappedCarrierInfoProvider) {
init(wrappedProvider: CarrierInfoProviderType) {
self.wrappedProvider = wrappedProvider
self.publisher = ValuePublisher(initialValue: nil)
}

var current: CarrierInfo? {
let nextValue = wrappedProvider.current
// `CarrierInfo` subscribers are notified as a side-effect of retrieving the
// current `CarrierInfo` value.
publisher.publishAsync(nextValue)
return nextValue
wrappedProvider.current
}

func subscribe<Observer: CarrierInfoObserver>(_ subscriber: Observer) where Observer.ObservedValue == CarrierInfo? {
publisher.subscribe(subscriber)
wrappedProvider.subscribe(subscriber)
}
}

#if targetEnvironment(macCatalyst)

internal struct MacCatalystCarrierInfoProvider: WrappedCarrierInfoProvider {
/// Carrier info is not supported on macCatalyst
/// Dummy provider for Mac Catalyst which doesn't support carrier info.
internal struct MacCatalystCarrierInfoProvider: CarrierInfoProviderType {
var current: CarrierInfo? { return nil }
func subscribe<Observer>(_ subscriber: Observer) where Observer: ValueObserver, Observer.ObservedValue == CarrierInfo? {}
}

#else

internal struct iOSCarrierInfoProvider: WrappedCarrierInfoProvider {
let networkInfo: CTTelephonyNetworkInfo
/// Carrier info provider for iOS 12 and above.
/// It reads `CarrierInfo?` from `CTTelephonyNetworkInfo` only when `CTCarrier` has changed (e.g. when the SIM card was swapped).
@available(iOS 12, *)
internal class iOS12CarrierInfoProvider: CarrierInfoProviderType {
private let networkInfo: CTTelephonyNetworkInfo
/// Publisher for notifying observers on `CarrierInfo` change.
private let publisher: ValuePublisher<CarrierInfo?>

var current: CarrierInfo? {
let carrier: CTCarrier?
let radioTechnology: String?
init(networkInfo: CTTelephonyNetworkInfo) {
self.networkInfo = networkInfo
self.publisher = ValuePublisher(
initialValue: iOS12CarrierInfoProvider.readCarrierInfo(
from: networkInfo,
cellularProviderKey: networkInfo.serviceCurrentRadioAccessTechnology?.keys.first
)
)

if #available(iOS 12, *) {
guard let cellularProviderKey = networkInfo.serviceCurrentRadioAccessTechnology?.keys.first else {
return nil
// The `serviceSubscriberCellularProvidersDidUpdateNotifier` block object executes on the default priority
// global dispatch queue when the user’s cellular provider information changes.
// This occurs, for example, if a user swaps the device’s SIM card with one from another provider, while the app is running.
// ref.: https://developer.apple.com/documentation/coretelephony/cttelephonynetworkinfo/3024512-servicesubscribercellularprovide
networkInfo.serviceSubscriberCellularProvidersDidUpdateNotifier = { [weak self] cellularProviderKey in
guard let strongSelf = self else {
return
}
radioTechnology = networkInfo.serviceCurrentRadioAccessTechnology?[cellularProviderKey]
carrier = networkInfo.serviceSubscriberCellularProviders?[cellularProviderKey]
} else {
radioTechnology = networkInfo.currentRadioAccessTechnology
carrier = networkInfo.subscriberCellularProvider

let carrierInfo = iOS12CarrierInfoProvider.readCarrierInfo(
from: strongSelf.networkInfo,
cellularProviderKey: cellularProviderKey
)

// On iOS12+ `CarrierInfo` subscribers are notified on actual change to cellular provider.
strongSelf.publisher.publishAsync(carrierInfo)
}
}

guard let radioAccessTechnology = radioTechnology,
let currentCTCarrier = carrier else {
return nil
private static func readCarrierInfo(from networkInfo: CTTelephonyNetworkInfo, cellularProviderKey: String?) -> CarrierInfo? {
guard let cellularProviderKey = cellularProviderKey,
let radioTechnology = networkInfo.serviceCurrentRadioAccessTechnology?[cellularProviderKey],
let carrier = networkInfo.serviceSubscriberCellularProviders?[cellularProviderKey] else {
return nil // the service is not registered on any network
}
return CarrierInfo(
carrierName: carrier.carrierName,
carrierISOCountryCode: carrier.isoCountryCode,
carrierAllowsVOIP: carrier.allowsVOIP,
radioAccessTechnology: .init(ctRadioAccessTechnologyConstant: radioTechnology)
)
}
ncreated marked this conversation as resolved.
Show resolved Hide resolved

var current: CarrierInfo? {
publisher.currentValue
}

func subscribe<Observer>(_ subscriber: Observer) where Observer: ValueObserver, Observer.ObservedValue == CarrierInfo? {
publisher.subscribe(subscriber)
}
}

/// Carrier info provider for iOS 11.
/// It reads `CarrierInfo?` from `CTTelephonyNetworkInfo` each time.
internal class iOS11CarrierInfoProvider: CarrierInfoProviderType {
private let networkInfo: CTTelephonyNetworkInfo
/// Publisher for notifying observers on `CarrierInfo` change.
private let publisher: ValuePublisher<CarrierInfo?>

init(networkInfo: CTTelephonyNetworkInfo) {
self.networkInfo = networkInfo
self.publisher = ValuePublisher(
initialValue: iOS11CarrierInfoProvider.readCarrierInfo(from: networkInfo)
)
}

private static func readCarrierInfo(from networkInfo: CTTelephonyNetworkInfo) -> CarrierInfo? {
guard let radioTechnology = networkInfo.currentRadioAccessTechnology,
let carrier = networkInfo.subscriberCellularProvider else {
return nil // the service is not registered on any network
}
return CarrierInfo(
carrierName: currentCTCarrier.carrierName,
carrierISOCountryCode: currentCTCarrier.isoCountryCode,
carrierAllowsVOIP: currentCTCarrier.allowsVOIP,
radioAccessTechnology: .init(ctRadioAccessTechnologyConstant: radioAccessTechnology)
carrierName: carrier.carrierName,
carrierISOCountryCode: carrier.isoCountryCode,
carrierAllowsVOIP: carrier.allowsVOIP,
radioAccessTechnology: .init(ctRadioAccessTechnologyConstant: radioTechnology)
)
}

var current: CarrierInfo? {
let carrierInfo = iOS11CarrierInfoProvider.readCarrierInfo(from: networkInfo)

// On iOS11 `CarrierInfo` subscribers are notified as a side-effect of pulling the
// current `CarrierInfo` value.
publisher.publishAsync(carrierInfo)

return carrierInfo
}

func subscribe<Observer>(_ subscriber: Observer) where Observer: ValueObserver, Observer.ObservedValue == CarrierInfo? {
publisher.subscribe(subscriber)
}
}

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ internal class iOS11NetworkConnectionInfoProvider: WrappedNetworkConnectionInfoP
}
}

// MARK: Conversion helpers
// MARK: - Conversion helpers

extension NetworkConnectionInfo.Reachability {
@available(iOS 12, *)
Expand Down
Loading