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

RUM-3284 Extension background upload #1803

Merged
merged 6 commits into from
May 8, 2024
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- [FEATURE] Support WebView recording in Session Replay. See [#1776][]
- [IMPROVEMENT] Add `isInitialized` and `stopInstance` methods to ObjC API. See [#1800][]
- [IMPROVEMENT] Add `addUserExtraInfo` method to ObjC API. See [#1799][]
- [FIX] Add background upload capability to extensions in order to mitigate `0xdead10cc` crash. See [#1803][]

# 2.10.1 / 02-05-2024

Expand Down Expand Up @@ -655,6 +656,7 @@ Release `2.0` introduces breaking changes. Follow the [Migration Guide](MIGRATIO
[#1798]: https://github.com/DataDog/dd-sdk-ios/pull/1798
[#1776]: https://github.com/DataDog/dd-sdk-ios/pull/1776
[#1721]: https://github.com/DataDog/dd-sdk-ios/pull/1721
[#1803]: https://github.com/DataDog/dd-sdk-ios/pull/1803
[#1807]: https://github.com/DataDog/dd-sdk-ios/pull/1807
[@00fa9a]: https://github.com/00FA9A
[@britton-earnin]: https://github.com/Britton-Earnin
Expand Down
18 changes: 12 additions & 6 deletions Datadog/Datadog.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -592,8 +592,10 @@
A7B932FC2B1F6A0A00AE6477 /* SRDataModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7B932F82B1F6A0A00AE6477 /* SRDataModels.swift */; };
A7B932FD2B1F6A0A00AE6477 /* EnrichedResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7B932F92B1F6A0A00AE6477 /* EnrichedResource.swift */; };
A7B932FE2B1F6A0A00AE6477 /* SRDataModels+UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7B932FA2B1F6A0A00AE6477 /* SRDataModels+UIKit.swift */; };
A7C816AB2A98CEBA00BF097B /* UIKitBackgroundTaskCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7C816AA2A98CEBA00BF097B /* UIKitBackgroundTaskCoordinatorTests.swift */; };
A7C816AC2A98CEBA00BF097B /* UIKitBackgroundTaskCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7C816AA2A98CEBA00BF097B /* UIKitBackgroundTaskCoordinatorTests.swift */; };
A7CA21802BEBB1E800732571 /* AppBackgroundTaskCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7CA217F2BEBB1E800732571 /* AppBackgroundTaskCoordinatorTests.swift */; };
A7CA21812BEBB1E800732571 /* AppBackgroundTaskCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7CA217F2BEBB1E800732571 /* AppBackgroundTaskCoordinatorTests.swift */; };
A7CA21832BEBB2E200732571 /* ExtensionBackgroundTaskCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7CA21822BEBB2E200732571 /* ExtensionBackgroundTaskCoordinatorTests.swift */; };
A7CA21842BEBB2E200732571 /* ExtensionBackgroundTaskCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7CA21822BEBB2E200732571 /* ExtensionBackgroundTaskCoordinatorTests.swift */; };
A7D9528A2B28BD94004C79B1 /* ResourceProcessorSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7D952892B28BD94004C79B1 /* ResourceProcessorSpy.swift */; };
A7D9528C2B28C18D004C79B1 /* ResourceProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7D9528B2B28C18D004C79B1 /* ResourceProcessorTests.swift */; };
A7DA18042AB0C91200F76337 /* DDUIKitRUMViewsPredicateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7DA18022AB0C8A700F76337 /* DDUIKitRUMViewsPredicateTests.swift */; };
Expand Down Expand Up @@ -2574,7 +2576,8 @@
A7B932F82B1F6A0A00AE6477 /* SRDataModels.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SRDataModels.swift; sourceTree = "<group>"; };
A7B932F92B1F6A0A00AE6477 /* EnrichedResource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnrichedResource.swift; sourceTree = "<group>"; };
A7B932FA2B1F6A0A00AE6477 /* SRDataModels+UIKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SRDataModels+UIKit.swift"; sourceTree = "<group>"; };
A7C816AA2A98CEBA00BF097B /* UIKitBackgroundTaskCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitBackgroundTaskCoordinatorTests.swift; sourceTree = "<group>"; };
A7CA217F2BEBB1E800732571 /* AppBackgroundTaskCoordinatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppBackgroundTaskCoordinatorTests.swift; sourceTree = "<group>"; };
A7CA21822BEBB2E200732571 /* ExtensionBackgroundTaskCoordinatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExtensionBackgroundTaskCoordinatorTests.swift; sourceTree = "<group>"; };
A7D952892B28BD94004C79B1 /* ResourceProcessorSpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResourceProcessorSpy.swift; sourceTree = "<group>"; };
A7D9528B2B28C18D004C79B1 /* ResourceProcessorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResourceProcessorTests.swift; sourceTree = "<group>"; };
A7DA18022AB0C8A700F76337 /* DDUIKitRUMViewsPredicateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DDUIKitRUMViewsPredicateTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -4127,14 +4130,15 @@
61133C2E2423990D00786299 /* Upload */ = {
isa = PBXGroup;
children = (
A7CA21822BEBB2E200732571 /* ExtensionBackgroundTaskCoordinatorTests.swift */,
A7CA217F2BEBB1E800732571 /* AppBackgroundTaskCoordinatorTests.swift */,
61133C2F2423990D00786299 /* DataUploadWorkerTests.swift */,
61133C302423990D00786299 /* DataUploadConditionsTests.swift */,
61133C312423990D00786299 /* DataUploadDelayTests.swift */,
61DA20EF26C40121004AFE6D /* DataUploadStatusTests.swift */,
61133C322423990D00786299 /* DataUploaderTests.swift */,
61133C342423990D00786299 /* URLSessionClientTests.swift */,
61133C332423990D00786299 /* RequestBuilderTests.swift */,
A7C816AA2A98CEBA00BF097B /* UIKitBackgroundTaskCoordinatorTests.swift */,
);
path = Upload;
sourceTree = "<group>";
Expand Down Expand Up @@ -7723,7 +7727,6 @@
61B8BA91281812F60068AFF4 /* KronosInternetAddressTests.swift in Sources */,
614798962A459AA80095CB02 /* DDTraceTests.swift in Sources */,
D25085102976E30000E931C3 /* DatadogRemoteFeatureMock.swift in Sources */,
A7C816AB2A98CEBA00BF097B /* UIKitBackgroundTaskCoordinatorTests.swift in Sources */,
6167E6DD2B811A8300C3CA2D /* AppHangsMonitoringTests.swift in Sources */,
612C13D02AA772FA0086B5D1 /* SRRequestMatcher.swift in Sources */,
61A1A44929643254007909E7 /* DatadogCoreProxy.swift in Sources */,
Expand Down Expand Up @@ -7832,8 +7835,10 @@
D2B3F052282E827700C2B5EE /* DDHTTPHeadersWriter+apiTests.m in Sources */,
D20605B92875729E0047275C /* ContextValuePublisherMock.swift in Sources */,
D24C9C4D29A7BA3F002057CF /* LogsMocks.swift in Sources */,
A7CA21832BEBB2E200732571 /* ExtensionBackgroundTaskCoordinatorTests.swift in Sources */,
61B5E42B26DFC433000B0A5F /* DDNSURLSessionDelegate+apiTests.m in Sources */,
6167E7062B82A9FD00C3CA2D /* GeneratingBacktraceTests.swift in Sources */,
A7CA21802BEBB1E800732571 /* AppBackgroundTaskCoordinatorTests.swift in Sources */,
D20605C52875895E0047275C /* KronosClockMock.swift in Sources */,
61133C642423990D00786299 /* LoggerTests.swift in Sources */,
6134CDB12A691E850061CCD9 /* CoreMetricsTests.swift in Sources */,
Expand Down Expand Up @@ -8933,7 +8938,6 @@
D24C9C7229A7D57A002057CF /* DirectoriesMock.swift in Sources */,
61DA8CB3286215DE0074A606 /* CryptographyTests.swift in Sources */,
D2CB6F0427C520D400A62B57 /* DDTracerTests.swift in Sources */,
A7C816AC2A98CEBA00BF097B /* UIKitBackgroundTaskCoordinatorTests.swift in Sources */,
D24C9C6129A7CB0C002057CF /* DatadogLogsFeatureTests.swift in Sources */,
D29A9FCF29DDC4BC005C54A4 /* RUMFeatureMocks.swift in Sources */,
D22743DE29DEB8B5001A7EF9 /* VitalInfoSamplerTests.swift in Sources */,
Expand Down Expand Up @@ -8977,6 +8981,7 @@
D2CB6F3227C520D400A62B57 /* JSONDataMatcher.swift in Sources */,
6136CB4B2A69C29C00AC265D /* FilesOrchestrator+MetricsTests.swift in Sources */,
D25085112976E30000E931C3 /* DatadogRemoteFeatureMock.swift in Sources */,
A7CA21842BEBB2E200732571 /* ExtensionBackgroundTaskCoordinatorTests.swift in Sources */,
D2CB6F3327C520D400A62B57 /* FilesOrchestratorTests.swift in Sources */,
D2FB1258292E0F10005B13F8 /* TrackingConsentPublisherTests.swift in Sources */,
D2CB6F3B27C520D400A62B57 /* NSURLSessionBridge.m in Sources */,
Expand All @@ -8997,6 +9002,7 @@
D22743EC29DEC9E6001A7EF9 /* Casting+RUM.swift in Sources */,
D2CB6F4D27C520D400A62B57 /* DataUploadStatusTests.swift in Sources */,
D2CB6F4F27C520D400A62B57 /* RetryingTests.swift in Sources */,
A7CA21812BEBB1E800732571 /* AppBackgroundTaskCoordinatorTests.swift in Sources */,
D2CB6F5027C520D400A62B57 /* DDDatadogTests.swift in Sources */,
D2B3F0452823EE8400C2B5EE /* TLVBlockTests.swift in Sources */,
D2CB6F5327C520D400A62B57 /* DirectoryTests.swift in Sources */,
Expand Down
8 changes: 7 additions & 1 deletion DatadogCore/Sources/Core/DatadogCore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ internal final class DatadogCore {
/// Flag defining if background tasks are enabled.
internal let backgroundTasksEnabled: Bool

/// Flag defining if the SDK is run from an extension.
internal let isRunFromExtension: Bool

/// Maximum number of batches per upload.
internal let maxBatchesPerUpload: Int

Expand All @@ -92,7 +95,8 @@ internal final class DatadogCore {
contextProvider: DatadogContextProvider,
applicationVersion: String,
maxBatchesPerUpload: Int,
backgroundTasksEnabled: Bool
backgroundTasksEnabled: Bool,
isRunFromExtension: Bool = false
) {
self.directory = directory
self.dateProvider = dateProvider
Expand All @@ -102,6 +106,7 @@ internal final class DatadogCore {
self.contextProvider = contextProvider
self.maxBatchesPerUpload = maxBatchesPerUpload
self.backgroundTasksEnabled = backgroundTasksEnabled
self.isRunFromExtension = isRunFromExtension
self.applicationVersionPublisher = ApplicationVersionPublisher(version: applicationVersion)
self.consentPublisher = TrackingConsentPublisher(consent: initialConsent)

Expand Down Expand Up @@ -258,6 +263,7 @@ extension DatadogCore: DatadogCoreProtocol {
performance: performancePreset,
backgroundTasksEnabled: backgroundTasksEnabled,
maxBatchesPerUpload: maxBatchesPerUpload,
isRunFromExtension: isRunFromExtension,
telemetry: telemetry
)

Expand Down
6 changes: 3 additions & 3 deletions DatadogCore/Sources/Core/Storage/Directories.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ internal extension CoreDirectory {
///
/// - Parameters:
/// - osDirectory: the root OS directory (`/Library/Caches`) to create core directory inside.
/// - instancenName: The core instance name.
/// - instanceName: The core instance name.
/// - site: The cor instance site.
init(in osDirectory: Directory, instancenName: String, site: DatadogSite) throws {
let sdkInstanceUUID = sha256("\(instancenName)\(site)")
init(in osDirectory: Directory, instanceName: String, site: DatadogSite) throws {
let sdkInstanceUUID = sha256("\(instanceName)\(site)")
let path = "com.datadoghq/v2/\(sdkInstanceUUID)"

self.init(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ internal protocol UIKitAppBackgroundTaskCoordinator {

extension UIApplication: UIKitAppBackgroundTaskCoordinator {}

internal class UIKitBackgroundTaskCoordinator: BackgroundTaskCoordinator {
internal class AppBackgroundTaskCoordinator: BackgroundTaskCoordinator {
private let app: UIKitAppBackgroundTaskCoordinator?

@ReadWriteLock
Expand Down Expand Up @@ -60,4 +60,38 @@ internal class UIKitBackgroundTaskCoordinator: BackgroundTaskCoordinator {
self.currentTaskId = nil
}
}

/// Bridge protocol that matches `UIApplication` interface for background tasks. Allows easier testablity.
internal protocol ProcessInfoActivityCoordinator {
func beginActivity(options: ProcessInfo.ActivityOptions, reason: String) -> any NSObjectProtocol
func endActivity(_ activity: any NSObjectProtocol)
}

extension ProcessInfo: ProcessInfoActivityCoordinator {}

internal class ExtensionBackgroundTaskCoordinator: BackgroundTaskCoordinator {
private let processInfo: ProcessInfoActivityCoordinator

@ReadWriteLock
private var currentActivity: NSObjectProtocol?

internal init(
processInfo: ProcessInfoActivityCoordinator = ProcessInfo()
) {
self.processInfo = processInfo
}

internal func beginBackgroundTask() {
endBackgroundTask()
currentActivity = processInfo.beginActivity(options: [.background], reason: "Datadog SDK background upload")
}
Comment on lines +84 to +87
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question/ Because beginBackgroundTask() is called right before the SDK starts uploading data, how does it correspond to the explanation of 0xdead10cc?

(...) Make this request well before starting to write to the file in order to complete those operations and relinquish the lock before the app suspends.

Apple suggestion is to wrap "file write" operations with .beginActivity() and .endActivity(). What we seem to do instead is wrapping data uploads. Considering that upload starts after the file was read and it has nothing to do with file writes (which are decoupled SDK routine), I'm not seeing clearly how this solves the 0xdead10cc problem.

Shouldn't we instead use .beginActivity() and .endActivity() when writing to files, instead of when performing data upload 🤔💭?


internal func endBackgroundTask() {
guard let currentActivity = currentActivity else {
return
}
processInfo.endActivity(currentActivity)
self.currentActivity = nil
}
}
#endif
13 changes: 10 additions & 3 deletions DatadogCore/Sources/Core/Upload/FeatureUpload.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ internal struct FeatureUpload {
performance: PerformancePreset,
backgroundTasksEnabled: Bool,
maxBatchesPerUpload: Int,
isRunFromExtension: Bool,
telemetry: Telemetry
) {
let uploadQueue = DispatchQueue(
Expand All @@ -34,9 +35,15 @@ internal struct FeatureUpload {
)

#if canImport(UIKit)
let backgroundTaskCoordinator = backgroundTasksEnabled
? UIKitBackgroundTaskCoordinator()
: nil
let backgroundTaskCoordinator: BackgroundTaskCoordinator?
switch (backgroundTasksEnabled, isRunFromExtension) {
case (true, false):
backgroundTaskCoordinator = AppBackgroundTaskCoordinator()
case (true, true):
backgroundTaskCoordinator = ExtensionBackgroundTaskCoordinator()
case (false, _):
backgroundTaskCoordinator = nil
}
#else
let backgroundTaskCoordinator: BackgroundTaskCoordinator? = nil
#endif
Expand Down
6 changes: 4 additions & 2 deletions DatadogCore/Sources/Datadog.swift
Original file line number Diff line number Diff line change
Expand Up @@ -430,12 +430,13 @@ public enum Datadog {
uploadFrequency: debug ? .frequent : configuration.uploadFrequency,
bundleType: bundleType
)
let isRunFromExtension = bundleType == .iOSAppExtension

// Set default `DatadogCore`:
let core = DatadogCore(
directory: try CoreDirectory(
in: configuration.systemDirectory(),
instancenName: instanceName,
instanceName: instanceName,
site: configuration.site
),
dateProvider: configuration.dateProvider,
Expand Down Expand Up @@ -466,7 +467,8 @@ public enum Datadog {
),
applicationVersion: applicationVersion,
maxBatchesPerUpload: configuration.batchProcessingLevel.maxBatchesPerUpload,
backgroundTasksEnabled: configuration.backgroundTasksEnabled
backgroundTasksEnabled: configuration.backgroundTasksEnabled,
isRunFromExtension: isRunFromExtension
)

core.telemetry.configuration(
Expand Down
4 changes: 2 additions & 2 deletions DatadogCore/Tests/Datadog/Core/DirectoriesTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class DirectoriesTests: XCTestCase {
let coreDirectories = try fixtures.map { instancenName, site, _ in
try CoreDirectory(
in: directory,
instancenName: instancenName,
instanceName: instancenName,
site: site
)
}
Expand All @@ -65,7 +65,7 @@ class DirectoriesTests: XCTestCase {
let coreDirectories = try (0..<50).map { index in
try CoreDirectory(
in: directory,
instancenName: .mockRandom(among: .alphanumerics, length: 31) + "\(index)",
instanceName: .mockRandom(among: .alphanumerics, length: 31) + "\(index)",
site: .mockRandom()
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import XCTest
import DatadogInternal
@testable import DatadogCore

class UIKitBackgroundTaskCoordinatorTests: XCTestCase {
class AppBackgroundTaskCoordinatorTests: XCTestCase {
var appSpy: AppSpy?
var coordinator: UIKitBackgroundTaskCoordinator?
var coordinator: AppBackgroundTaskCoordinator?

override func setUp() {
super.setUp()
appSpy = AppSpy()
coordinator = UIKitBackgroundTaskCoordinator(
coordinator = AppBackgroundTaskCoordinator(
app: appSpy
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2019-Present Datadog, Inc.
*/

import XCTest
import DatadogInternal
@testable import DatadogCore

class ExtensionBackgroundTaskCoordinatorTests: XCTestCase {
var processInfoSpy: ProcessInfoSpy! // swiftlint:disable:this implicitly_unwrapped_optional
var coordinator: ExtensionBackgroundTaskCoordinator! // swiftlint:disable:this implicitly_unwrapped_optional

override func setUp() {
super.setUp()
processInfoSpy = ProcessInfoSpy()
coordinator = ExtensionBackgroundTaskCoordinator(
processInfo: processInfoSpy
)
}

func testBeginBackgroundTask() {
coordinator?.beginBackgroundTask()

XCTAssertEqual(processInfoSpy?.beginBackgroundTaskCalled, true)
XCTAssertEqual(processInfoSpy?.endBackgroundTaskCalled, false)
}

func testEndBackgroundTask() throws {
coordinator?.beginBackgroundTask()
coordinator?.endBackgroundTask()

XCTAssertEqual(processInfoSpy?.beginBackgroundTaskCalled, true)
XCTAssertEqual(processInfoSpy?.endBackgroundTaskCalled, true)
}

func testEndBackgroundTaskNotCalledWhenNotBegan() throws {
coordinator?.endBackgroundTask()

XCTAssertEqual(processInfoSpy?.beginBackgroundTaskCalled, false)
XCTAssertEqual(processInfoSpy?.endBackgroundTaskCalled, false)
}

func testBeginEndsPreviousTask() throws {
coordinator?.beginBackgroundTask()
coordinator?.beginBackgroundTask()

XCTAssertEqual(processInfoSpy?.beginBackgroundTaskCalled, true)
XCTAssertEqual(processInfoSpy?.endBackgroundTaskCalled, true)
}
}

class ProcessInfoSpy: ProcessInfoActivityCoordinator {
var beginBackgroundTaskCalled = false
var endBackgroundTaskCalled = false

func beginActivity(options: ProcessInfo.ActivityOptions, reason: String) -> any NSObjectProtocol {
beginBackgroundTaskCalled = true
return NSObject()
}

func endActivity(_ activity: any NSObjectProtocol) {
endBackgroundTaskCalled = true
}
}