Summary
When an HTTP request completes successfully while the app is transitioning to background, the corresponding http.client span is recorded with a cancelled status and an inflated duration. The breadcrumb for the same request correctly shows the actual response time and a 200 status, creating a misleading discrepancy in traces.
Root cause
When the app backgrounds, cancelInBackground() in onSpanEndUtils.ts triggers the idle span's cleanup path in idleSpan.ts. Any child span that is still recording at that point is force-ended with:
childSpan.setStatus({ code: SPAN_STATUS_ERROR, message: 'cancelled' });
childSpan.end(endTimestamp); // parent's cancellation timestamp, not XHR completion time
The child http.client span is assigned the parent's cancellation timestamp as its end time, not the actual time the XHR completed. The breadcrumb, which is recorded by the XHR instrumentation at readyState === 4, captures the real response time — hence the divergence.
On iOS the race is more pronounced: the JS thread can be suspended after the inactive AppState event, so the 5-second IOS_INACTIVE_CANCEL_DELAY_MS timer fires on JS resume, potentially long after the HTTP call finished and the breadcrumb was recorded.
Expected behavior
The end timestamp of a child http.client span should reflect the earlier of:
- The actual XHR completion time (if the response was received before cancellation), or
- The parent's cancellation timestamp.
Capping child span end times at the XHR completion time when available would prevent inflated durations and the misleading cancelled status for requests that succeeded.
Versions affected
Confirmed with RN SDK v8.4.0. Likely present in earlier versions that use the idle span + cancelInBackground mechanism.
Workaround
None known at this time.
Action taken on behalf of Aidan Landen.
Summary
When an HTTP request completes successfully while the app is transitioning to background, the corresponding
http.clientspan is recorded with acancelledstatus and an inflated duration. The breadcrumb for the same request correctly shows the actual response time and a 200 status, creating a misleading discrepancy in traces.Root cause
When the app backgrounds,
cancelInBackground()inonSpanEndUtils.tstriggers the idle span's cleanup path inidleSpan.ts. Any child span that is still recording at that point is force-ended with:The child
http.clientspan is assigned the parent's cancellation timestamp as its end time, not the actual time the XHR completed. The breadcrumb, which is recorded by the XHR instrumentation atreadyState === 4, captures the real response time — hence the divergence.On iOS the race is more pronounced: the JS thread can be suspended after the
inactiveAppState event, so the 5-secondIOS_INACTIVE_CANCEL_DELAY_MStimer fires on JS resume, potentially long after the HTTP call finished and the breadcrumb was recorded.Expected behavior
The end timestamp of a child
http.clientspan should reflect the earlier of:Capping child span end times at the XHR completion time when available would prevent inflated durations and the misleading
cancelledstatus for requests that succeeded.Versions affected
Confirmed with RN SDK v8.4.0. Likely present in earlier versions that use the idle span +
cancelInBackgroundmechanism.Workaround
None known at this time.
Action taken on behalf of Aidan Landen.