From e8e81ab42a4b9f542e872b95cc01485e5e03905d Mon Sep 17 00:00:00 2001 From: Sara Susano Date: Wed, 29 Apr 2026 14:45:29 +0200 Subject: [PATCH 1/5] RUM-8054: Fix background session precondition for refresh and new session flows --- .../Scopes/RUMApplicationScope.swift | 40 +++- .../Scopes/RUMApplicationScopeTests.swift | 191 ++++++++++++++++++ 2 files changed, 224 insertions(+), 7 deletions(-) diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMApplicationScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMApplicationScope.swift index f11ee8f1a3..3fc4cae506 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMApplicationScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMApplicationScope.swift @@ -217,9 +217,10 @@ internal class RUMApplicationScope: RUMScope, RUMContextProvider { if context.applicationStateHistory.currentState == .background { switch context.launchInfo.launchReason { - case .userLaunch: startPrecondition = .userAppLaunch // UISceneDelegate-based apps always start in background - case .backgroundLaunch: startPrecondition = .backgroundLaunch - case .prewarming: startPrecondition = .prewarm + case .userLaunch: + startPrecondition = .userAppLaunch // UISceneDelegate-based apps always start in background + case .backgroundLaunch, .prewarming: + startPrecondition = preconditionForNewBackgroundSession(context: context) default: dependencies.telemetry.error("Creating initial session in background with unexpected launch reason: \(context.launchInfo.launchReason)") } @@ -246,12 +247,16 @@ internal class RUMApplicationScope: RUMScope, RUMContextProvider { private func refresh(expiredSession: RUMSessionScope, on command: RUMCommand, context: DatadogContext, writer: Writer) -> RUMSessionScope { var startPrecondition: RUMSessionPrecondition? = nil - if lastSessionEndReason == .timeOut { + // If the launch reason is uncertain (e.g. on tvOS), the background check falls through to the end-reason logic. + if context.applicationStateHistory.currentState == .background, + let backgroundPrecondition = preconditionForNewBackgroundSession(context: context) { + startPrecondition = backgroundPrecondition + } else if lastSessionEndReason == .timeOut { startPrecondition = .inactivityTimeout } else if lastSessionEndReason == .maxDuration { startPrecondition = .maxDuration } else { - dependencies.telemetry.error("Failed to determine session precondition for REFRESHED session with end reason: \(lastSessionEndReason?.rawValue ?? "unknown"))") + dependencies.telemetry.error("Failed to determine session precondition for REFRESHED session with end reason: \(lastSessionEndReason?.rawValue ?? "unknown")") } let refreshingInForeground = context.applicationStateHistory.currentState == .active @@ -278,14 +283,18 @@ internal class RUMApplicationScope: RUMScope, RUMContextProvider { private func startNewSession(on command: RUMCommand, context: DatadogContext, writer: Writer) { var startPrecondition: RUMSessionPrecondition? = nil - if lastSessionEndReason == .stopAPI { + // If the launch reason is uncertain (e.g. on tvOS), the background check falls through to the end-reason logic. + if context.applicationStateHistory.currentState == .background, + let backgroundPrecondition = preconditionForNewBackgroundSession(context: context) { + startPrecondition = backgroundPrecondition + } else if lastSessionEndReason == .stopAPI { startPrecondition = .explicitStop } else if lastSessionEndReason == .timeOut { startPrecondition = .inactivityTimeout } else if lastSessionEndReason == .maxDuration { startPrecondition = .maxDuration } else { - dependencies.telemetry.error("Failed to determine session precondition for NEW session with end reason: \(lastSessionEndReason?.rawValue ?? "unknown"))") + dependencies.telemetry.error("Failed to determine session precondition for NEW session with end reason: \(lastSessionEndReason?.rawValue ?? "unknown")") } if didCreateInitialSessionCount > 0 { // Sanity check @@ -351,4 +360,21 @@ internal class RUMApplicationScope: RUMScope, RUMContextProvider { writer: writer ) } + + private func preconditionForNewBackgroundSession(context: DatadogContext) -> RUMSessionPrecondition? { + switch context.launchInfo.launchReason { + case .backgroundLaunch: + return .backgroundLaunch + case .prewarming: + return .prewarm + case .userLaunch: + // Normal: a user-launched process went to background. Caller uses end-reason-based precondition. + return nil + default: + dependencies.telemetry.error( + "Starting session in background with unexpected launch reason: \(context.launchInfo.launchReason)" + ) + return nil + } + } } diff --git a/DatadogRUM/Tests/RUMMonitor/Scopes/RUMApplicationScopeTests.swift b/DatadogRUM/Tests/RUMMonitor/Scopes/RUMApplicationScopeTests.swift index 6e05647e5a..25cc269c06 100644 --- a/DatadogRUM/Tests/RUMMonitor/Scopes/RUMApplicationScopeTests.swift +++ b/DatadogRUM/Tests/RUMMonitor/Scopes/RUMApplicationScopeTests.swift @@ -484,4 +484,195 @@ class RUMApplicationScopeTests: XCTestCase { // Then XCTAssertEqual(scope.activeSession?.context.sessionPrecondition, .explicitStop) } + + func testGivenInactiveSession_whenNewOneIsStartedInBackground_itSetsBackgroundLaunchPrecondition() { + // Given + var currentTime: Date = .mockDecember15th2019At10AMUTC() + let sdkContext: DatadogContext = .mockWith(sdkInitDate: currentTime) + let scope = createRUMApplicationScope( + dependencies: .mockWith(samplingRate: 100), + sdkContext: sdkContext + ) + + // When + currentTime.addTimeInterval(RUMSessionScope.Constants.sessionTimeoutDuration) + let backgroundContext: DatadogContext = .mockWith( + launchInfo: .mockWith( + launchReason: .backgroundLaunch, + processLaunchDate: .mockDecember15th2019At10AMUTC() + ), + applicationStateHistory: .mockAppInBackground(since: currentTime) + ) + _ = scope.process( + command: RUMCommandMock(time: currentTime, isUserInteraction: true), + context: backgroundContext, + writer: writer + ) + + // Then + XCTAssertEqual(scope.activeSession?.context.sessionPrecondition, .backgroundLaunch) + } + + func testGivenExpiredSession_whenNewOneIsStartedInBackground_itSetsBackgroundLaunchPrecondition() { + // Given + let initialTime: Date = .mockDecember15th2019At10AMUTC() + var currentTime: Date = initialTime + let sdkContext: DatadogContext = .mockWith(sdkInitDate: currentTime) + let scope = createRUMApplicationScope( + dependencies: .mockWith(samplingRate: 100), + sdkContext: sdkContext + ) + + // Keep session active without exceeding maxDuration — stop one step before it would expire + while currentTime.addingTimeInterval(RUMSessionScope.Constants.sessionTimeoutDuration - 1) < initialTime.addingTimeInterval(RUMSessionScope.Constants.sessionMaxDuration) { + currentTime.addTimeInterval(RUMSessionScope.Constants.sessionTimeoutDuration - 1) + _ = scope.process( + command: RUMCommandMock(time: currentTime, isUserInteraction: true), + context: sdkContext, + writer: writer + ) + } + + // When - advance past maxDuration without triggering inactivity timeout, then send in background + currentTime.addTimeInterval(RUMSessionScope.Constants.sessionTimeoutDuration - 1) + let backgroundContext: DatadogContext = .mockWith( + launchInfo: .mockWith( + launchReason: .backgroundLaunch, + processLaunchDate: .mockDecember15th2019At10AMUTC() + ), + applicationStateHistory: .mockAppInBackground(since: currentTime) + ) + _ = scope.process( + command: RUMCommandMock(time: currentTime, isUserInteraction: true), + context: backgroundContext, + writer: writer + ) + + // Then + XCTAssertEqual(scope.activeSession?.context.sessionPrecondition, .backgroundLaunch) + } + + func testGivenStoppedSession_whenNewOneIsStartedInBackground_itSetsBackgroundLaunchPrecondition() { + // Given + var currentTime: Date = .mockDecember15th2019At10AMUTC() + let sdkContext: DatadogContext = .mockWith(sdkInitDate: currentTime) + let scope = createRUMApplicationScope( + dependencies: .mockWith(samplingRate: 100), + sdkContext: sdkContext + ) + + currentTime.addTimeInterval(1) + _ = scope.process(command: RUMStopSessionCommand(time: currentTime), context: sdkContext, writer: writer) + + // When + currentTime.addTimeInterval(1) + let backgroundContext: DatadogContext = .mockWith( + launchInfo: .mockWith( + launchReason: .backgroundLaunch, + processLaunchDate: .mockDecember15th2019At10AMUTC() + ), + applicationStateHistory: .mockAppInBackground(since: currentTime) + ) + _ = scope.process( + command: RUMAddUserActionCommand.mockWith(time: currentTime), + context: backgroundContext, + writer: writer + ) + + // Then + XCTAssertEqual(scope.activeSession?.context.sessionPrecondition, .backgroundLaunch) + } + + func testGivenInactiveSession_whenNewOneIsStartedInBackgroundWithPrewarming_itSetsPrewarmPrecondition() { + // Given + var currentTime: Date = .mockDecember15th2019At10AMUTC() + let sdkContext: DatadogContext = .mockWith(sdkInitDate: currentTime) + let scope = createRUMApplicationScope( + dependencies: .mockWith(samplingRate: 100), + sdkContext: sdkContext + ) + + // When + currentTime.addTimeInterval(RUMSessionScope.Constants.sessionTimeoutDuration) + let backgroundContext: DatadogContext = .mockWith( + launchInfo: .mockWith( + launchReason: .prewarming, + processLaunchDate: .mockDecember15th2019At10AMUTC() + ), + applicationStateHistory: .mockAppInBackground(since: currentTime) + ) + _ = scope.process( + command: RUMCommandMock(time: currentTime, isUserInteraction: true), + context: backgroundContext, + writer: writer + ) + + // Then + XCTAssertEqual(scope.activeSession?.context.sessionPrecondition, .prewarm) + } + + func testGivenStoppedSession_whenNewOneIsStartedInBackgroundWithPrewarming_itSetsPrewarmPrecondition() { + // Given + var currentTime: Date = .mockDecember15th2019At10AMUTC() + let sdkContext: DatadogContext = .mockWith(sdkInitDate: currentTime) + let scope = createRUMApplicationScope( + dependencies: .mockWith(samplingRate: 100), + sdkContext: sdkContext + ) + + currentTime.addTimeInterval(1) + _ = scope.process(command: RUMStopSessionCommand(time: currentTime), context: sdkContext, writer: writer) + + // When + currentTime.addTimeInterval(1) + let backgroundContext: DatadogContext = .mockWith( + launchInfo: .mockWith( + launchReason: .prewarming, + processLaunchDate: .mockDecember15th2019At10AMUTC() + ), + applicationStateHistory: .mockAppInBackground(since: currentTime) + ) + _ = scope.process( + command: RUMAddUserActionCommand.mockWith(time: currentTime), + context: backgroundContext, + writer: writer + ) + + // Then + XCTAssertEqual(scope.activeSession?.context.sessionPrecondition, .prewarm) + } + + func testGivenUserLaunchedApp_whenSessionTimesOutInBackground_itSetsInactivityTimeoutPrecondition() { + // Given - app launched by user, session becomes inactive + var currentTime: Date = .mockDecember15th2019At10AMUTC() + let sdkContext: DatadogContext = .mockWith( + sdkInitDate: currentTime, + launchInfo: .mockWith(launchReason: .userLaunch) + ) + let featureScope = FeatureScopeMock() + let scope = createRUMApplicationScope( + dependencies: .mockWith(featureScope: featureScope, samplingRate: 100), + sdkContext: sdkContext + ) + + // When - session times out while app is in background + currentTime.addTimeInterval(RUMSessionScope.Constants.sessionTimeoutDuration) + let backgroundContext: DatadogContext = .mockWith( + launchInfo: .mockWith( + launchReason: .userLaunch, + processLaunchDate: .mockDecember15th2019At10AMUTC() + ), + applicationStateHistory: .mockAppInBackground(since: currentTime) + ) + _ = scope.process( + command: RUMCommandMock(time: currentTime, isUserInteraction: true), + context: backgroundContext, + writer: writer + ) + + // Then - end-reason-based precondition is used, not backgroundLaunch + XCTAssertEqual(scope.activeSession?.context.sessionPrecondition, .inactivityTimeout) + // And no error telemetry is fired for .userLaunch in background (it is a valid scenario) + XCTAssertNil(featureScope.telemetryMock.messages.firstError()) + } } From de6bb192e868fdf9950abeabcbb77d06bc0661bd Mon Sep 17 00:00:00 2001 From: Sara Susano Date: Wed, 29 Apr 2026 14:53:12 +0200 Subject: [PATCH 2/5] RUM-8054: Fix trailing whitespace lint violation --- DatadogRUM/Sources/RUMMonitor/Scopes/RUMApplicationScope.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMApplicationScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMApplicationScope.swift index 3fc4cae506..5e08382e77 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMApplicationScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMApplicationScope.swift @@ -360,7 +360,7 @@ internal class RUMApplicationScope: RUMScope, RUMContextProvider { writer: writer ) } - + private func preconditionForNewBackgroundSession(context: DatadogContext) -> RUMSessionPrecondition? { switch context.launchInfo.launchReason { case .backgroundLaunch: From 2c7165c8384d2bd26f791c8304f30168aa615111 Mon Sep 17 00:00:00 2001 From: Sara Susano Date: Wed, 29 Apr 2026 15:26:24 +0200 Subject: [PATCH 3/5] RUM-8054: Address review feedback --- .../Sources/RUMMonitor/Scopes/RUMApplicationScope.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMApplicationScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMApplicationScope.swift index 5e08382e77..33bc9b15a0 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMApplicationScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMApplicationScope.swift @@ -221,7 +221,7 @@ internal class RUMApplicationScope: RUMScope, RUMContextProvider { startPrecondition = .userAppLaunch // UISceneDelegate-based apps always start in background case .backgroundLaunch, .prewarming: startPrecondition = preconditionForNewBackgroundSession(context: context) - default: + @unknown default: dependencies.telemetry.error("Creating initial session in background with unexpected launch reason: \(context.launchInfo.launchReason)") } } else { @@ -247,7 +247,7 @@ internal class RUMApplicationScope: RUMScope, RUMContextProvider { private func refresh(expiredSession: RUMSessionScope, on command: RUMCommand, context: DatadogContext, writer: Writer) -> RUMSessionScope { var startPrecondition: RUMSessionPrecondition? = nil - // If the launch reason is uncertain (e.g. on tvOS), the background check falls through to the end-reason logic. + // If no background-session precondition can be derived for the current context, fall through to the end-reason logic. if context.applicationStateHistory.currentState == .background, let backgroundPrecondition = preconditionForNewBackgroundSession(context: context) { startPrecondition = backgroundPrecondition @@ -283,7 +283,7 @@ internal class RUMApplicationScope: RUMScope, RUMContextProvider { private func startNewSession(on command: RUMCommand, context: DatadogContext, writer: Writer) { var startPrecondition: RUMSessionPrecondition? = nil - // If the launch reason is uncertain (e.g. on tvOS), the background check falls through to the end-reason logic. + // If no background-session precondition can be derived for the current context, fall through to the end-reason logic. if context.applicationStateHistory.currentState == .background, let backgroundPrecondition = preconditionForNewBackgroundSession(context: context) { startPrecondition = backgroundPrecondition @@ -370,7 +370,7 @@ internal class RUMApplicationScope: RUMScope, RUMContextProvider { case .userLaunch: // Normal: a user-launched process went to background. Caller uses end-reason-based precondition. return nil - default: + @unknown default: dependencies.telemetry.error( "Starting session in background with unexpected launch reason: \(context.launchInfo.launchReason)" ) From a18d1c53e32f41f4167f9d658f7a59027c08a219 Mon Sep 17 00:00:00 2001 From: Sara Susano Date: Wed, 29 Apr 2026 15:46:35 +0200 Subject: [PATCH 4/5] RUM-8054: Address review feedback --- .../Tests/RUMMonitor/Scopes/RUMApplicationScopeTests.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/DatadogRUM/Tests/RUMMonitor/Scopes/RUMApplicationScopeTests.swift b/DatadogRUM/Tests/RUMMonitor/Scopes/RUMApplicationScopeTests.swift index 25cc269c06..6178e47da4 100644 --- a/DatadogRUM/Tests/RUMMonitor/Scopes/RUMApplicationScopeTests.swift +++ b/DatadogRUM/Tests/RUMMonitor/Scopes/RUMApplicationScopeTests.swift @@ -497,6 +497,7 @@ class RUMApplicationScopeTests: XCTestCase { // When currentTime.addTimeInterval(RUMSessionScope.Constants.sessionTimeoutDuration) let backgroundContext: DatadogContext = .mockWith( + sdkInitDate: .mockDecember15th2019At10AMUTC(), launchInfo: .mockWith( launchReason: .backgroundLaunch, processLaunchDate: .mockDecember15th2019At10AMUTC() @@ -536,6 +537,7 @@ class RUMApplicationScopeTests: XCTestCase { // When - advance past maxDuration without triggering inactivity timeout, then send in background currentTime.addTimeInterval(RUMSessionScope.Constants.sessionTimeoutDuration - 1) let backgroundContext: DatadogContext = .mockWith( + sdkInitDate: .mockDecember15th2019At10AMUTC(), launchInfo: .mockWith( launchReason: .backgroundLaunch, processLaunchDate: .mockDecember15th2019At10AMUTC() @@ -567,6 +569,7 @@ class RUMApplicationScopeTests: XCTestCase { // When currentTime.addTimeInterval(1) let backgroundContext: DatadogContext = .mockWith( + sdkInitDate: .mockDecember15th2019At10AMUTC(), launchInfo: .mockWith( launchReason: .backgroundLaunch, processLaunchDate: .mockDecember15th2019At10AMUTC() @@ -595,6 +598,7 @@ class RUMApplicationScopeTests: XCTestCase { // When currentTime.addTimeInterval(RUMSessionScope.Constants.sessionTimeoutDuration) let backgroundContext: DatadogContext = .mockWith( + sdkInitDate: .mockDecember15th2019At10AMUTC(), launchInfo: .mockWith( launchReason: .prewarming, processLaunchDate: .mockDecember15th2019At10AMUTC() @@ -626,6 +630,7 @@ class RUMApplicationScopeTests: XCTestCase { // When currentTime.addTimeInterval(1) let backgroundContext: DatadogContext = .mockWith( + sdkInitDate: .mockDecember15th2019At10AMUTC(), launchInfo: .mockWith( launchReason: .prewarming, processLaunchDate: .mockDecember15th2019At10AMUTC() @@ -658,6 +663,7 @@ class RUMApplicationScopeTests: XCTestCase { // When - session times out while app is in background currentTime.addTimeInterval(RUMSessionScope.Constants.sessionTimeoutDuration) let backgroundContext: DatadogContext = .mockWith( + sdkInitDate: .mockDecember15th2019At10AMUTC(), launchInfo: .mockWith( launchReason: .userLaunch, processLaunchDate: .mockDecember15th2019At10AMUTC() From e8cfc1e20dcb2d9306cca77f54ddb4d7fe346de7 Mon Sep 17 00:00:00 2001 From: Sara Susano Date: Wed, 29 Apr 2026 17:13:03 +0200 Subject: [PATCH 5/5] RUM-8054: Fix background session precondition and update integration tests --- Datadog/IntegrationUnitTests/RUM/RUMSessionStopTests.swift | 2 +- Datadog/IntegrationUnitTests/RUM/RUMSessionTimeOutTests.swift | 2 +- .../Sources/RUMMonitor/Scopes/RUMApplicationScope.swift | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Datadog/IntegrationUnitTests/RUM/RUMSessionStopTests.swift b/Datadog/IntegrationUnitTests/RUM/RUMSessionStopTests.swift index 04263a9af9..5f20a7eb51 100644 --- a/Datadog/IntegrationUnitTests/RUM/RUMSessionStopTests.swift +++ b/Datadog/IntegrationUnitTests/RUM/RUMSessionStopTests.swift @@ -762,7 +762,7 @@ class RUMSessionStopTests: RUMSessionTestsBase { XCTAssertNil(session2.timeToInitialDisplay) DDAssertEqual(session2.sessionStartDate, processLaunchDate + timeToSDKInit + dt1 + dt2 + dt3 + dt4, accuracy: accuracy) DDAssertEqual(session2.duration, dt5, accuracy: accuracy) - XCTAssertEqual(session2.sessionPrecondition, .explicitStop) + XCTAssertEqual(session2.sessionPrecondition, given == given1 ? .backgroundLaunch : .prewarm) XCTAssertEqual(session2.views.count, 1) XCTAssertEqual(session2.views[0].name, backgroundViewName) DDAssertEqual(session2.views[0].duration, dt5, accuracy: accuracy) diff --git a/Datadog/IntegrationUnitTests/RUM/RUMSessionTimeOutTests.swift b/Datadog/IntegrationUnitTests/RUM/RUMSessionTimeOutTests.swift index d2499a0c68..bf0806942c 100644 --- a/Datadog/IntegrationUnitTests/RUM/RUMSessionTimeOutTests.swift +++ b/Datadog/IntegrationUnitTests/RUM/RUMSessionTimeOutTests.swift @@ -750,7 +750,7 @@ class RUMSessionTimeOutTests: RUMSessionTestsBase { XCTAssertNil(session2.timeToInitialDisplay) DDAssertEqual(session2.sessionStartDate, processLaunchDate + timeToSDKInit + dt1 + dt2 + sessionTimeoutDuration + dt3, accuracy: accuracy) DDAssertEqual(session2.duration, dt4, accuracy: accuracy) - XCTAssertEqual(session2.sessionPrecondition, .inactivityTimeout) + XCTAssertEqual(session2.sessionPrecondition, given == given1 ? .backgroundLaunch : .prewarm) XCTAssertEqual(session2.views.count, 1) XCTAssertEqual(session2.views[0].name, backgroundViewName) DDAssertEqual(session2.views[0].duration, dt4, accuracy: accuracy) diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMApplicationScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMApplicationScope.swift index 33bc9b15a0..9254f6d1a4 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMApplicationScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMApplicationScope.swift @@ -247,7 +247,7 @@ internal class RUMApplicationScope: RUMScope, RUMContextProvider { private func refresh(expiredSession: RUMSessionScope, on command: RUMCommand, context: DatadogContext, writer: Writer) -> RUMSessionScope { var startPrecondition: RUMSessionPrecondition? = nil - // If no background-session precondition can be derived for the current context, fall through to the end-reason logic. + // If the app is in background, use the background-aware precondition; otherwise fall through to the end-reason logic. if context.applicationStateHistory.currentState == .background, let backgroundPrecondition = preconditionForNewBackgroundSession(context: context) { startPrecondition = backgroundPrecondition @@ -283,7 +283,7 @@ internal class RUMApplicationScope: RUMScope, RUMContextProvider { private func startNewSession(on command: RUMCommand, context: DatadogContext, writer: Writer) { var startPrecondition: RUMSessionPrecondition? = nil - // If no background-session precondition can be derived for the current context, fall through to the end-reason logic. + // If the app is in background, use the background-aware precondition; otherwise fall through to the end-reason logic. if context.applicationStateHistory.currentState == .background, let backgroundPrecondition = preconditionForNewBackgroundSession(context: context) { startPrecondition = backgroundPrecondition