@@ -65,6 +65,8 @@ public final class ActivityLifecycleIntegration
6565 static final String APP_START_COLD = "app.start.cold" ;
6666 static final String TTID_OP = "ui.load.initial_display" ;
6767 static final String TTFD_OP = "ui.load.full_display" ;
68+ static final String APP_START_EXTENDED_OP = "app.start.extended_app_start" ;
69+ static final String APP_START_EXTENDED_DESC = "Extended App Start" ;
6870 static final long TTFD_TIMEOUT_MILLIS = 25000 ;
6971 // If a headless app start and the following activity's ui.load are more than this far apart, they
7072 // are treated as unrelated and not connected into the same trace.
@@ -277,6 +279,13 @@ private void startTracing(final @NotNull Activity activity) {
277279 appStartTransactionOptions .setStartTimestamp (appStartTime );
278280 appStartTransactionOptions .setAppStartTransaction (appStartSamplingDecision != null );
279281 appStartTransactionOptions .setOrigin (APP_START_TRACE_ORIGIN );
282+ // When the app start is being extended, hold the transaction open until the extended span
283+ // finishes (or the deadline forces it).
284+ if (AppStartMetrics .getInstance ().isExtendedAppStartPending ()) {
285+ appStartTransactionOptions .setWaitForChildren (true );
286+ appStartTransactionOptions .setDeadlineTimeout (
287+ deadlineTimeoutMillis <= 0 ? null : deadlineTimeoutMillis );
288+ }
280289
281290 appStartTransaction =
282291 scopes .startTransaction (
@@ -291,6 +300,7 @@ private void startTracing(final @NotNull Activity activity) {
291300 if (appStartReason != null ) {
292301 appStartTransaction .setData (APP_START_REASON_DATA , appStartReason );
293302 }
303+ materializeExtendedAppStart (appStartTransaction );
294304 }
295305
296306 // Continue either the foreground app.start above or an earlier headless app.start.
@@ -349,6 +359,9 @@ && isWithinAppStartContinuationWindow(ttidStartTime)) {
349359 spanOptions );
350360
351361 finishAppStartSpan ();
362+ // The ui.load transaction already waits for children + has a deadline, so parenting the
363+ // extended span here keeps it open until the extension finishes.
364+ materializeExtendedAppStart (appStartSpan );
352365 }
353366 }
354367 final @ NotNull ISpan ttidSpan =
@@ -400,6 +413,30 @@ private void setSpanOrigin(final @NotNull SpanOptions spanOptions) {
400413 spanOptions .setOrigin (TRACE_ORIGIN );
401414 }
402415
416+ /**
417+ * Materializes a pending extended app start span as a child of {@code parent}. The parent's
418+ * transaction must have {@code waitForChildren} + a deadline set so it stays open until the
419+ * extension finishes (or the deadline forces it).
420+ */
421+ private void materializeExtendedAppStart (final @ NotNull ISpan parent ) {
422+ final @ NotNull AppStartMetrics metrics = AppStartMetrics .getInstance ();
423+ final @ Nullable ExtendedAppStartSpan ext = metrics .getPendingExtendedAppStartSpan ();
424+ if (ext == null ) {
425+ return ;
426+ }
427+ final SpanOptions spanOptions = new SpanOptions ();
428+ setSpanOrigin (spanOptions );
429+ final @ NotNull ISpan child =
430+ parent .startChild (
431+ APP_START_EXTENDED_OP ,
432+ APP_START_EXTENDED_DESC ,
433+ ext .getStartDate (),
434+ Instrumenter .SENTRY ,
435+ spanOptions );
436+ ext .materialize (child );
437+ metrics .markExtendedAppStartMaterialized ();
438+ }
439+
403440 /**
404441 * Whether the ui.load starting at {@code uiLoadStartTime} is close enough in time to the headless
405442 * app start to belong to the same trace. If they are more than {@link
@@ -998,6 +1035,13 @@ private void onHeadlessAppStart() {
9981035 txnOptions .setBindToScope (false );
9991036 txnOptions .setStartTimestamp (startTime );
10001037 txnOptions .setOrigin (APP_START_TRACE_ORIGIN );
1038+ // When the app start is being extended, hold the headless transaction open until the extended
1039+ // span finishes (or the deadline forces it).
1040+ if (metrics .isExtendedAppStartPending ()) {
1041+ txnOptions .setWaitForChildren (true );
1042+ final long deadlineTimeoutMillis = options .getDeadlineTimeout ();
1043+ txnOptions .setDeadlineTimeout (deadlineTimeoutMillis <= 0 ? null : deadlineTimeoutMillis );
1044+ }
10011045
10021046 final @ NotNull TransactionContext txnContext =
10031047 new TransactionContext (
@@ -1020,6 +1064,7 @@ private void onHeadlessAppStart() {
10201064 // time to continue this trace.
10211065 metrics .setAppStartEndTime (endTime );
10221066
1067+ materializeExtendedAppStart (transaction );
10231068 transaction .finish (SpanStatus .OK , endTime );
10241069 }
10251070}
0 commit comments