getsentry / getsentry/sentry-javascript
Span timestamps trust performance.timeOrigin without the drift guard from #2590 — drifting monotonic clocks produce past-dated (dropped) transactions
- 主要语言
- TypeScript
- 星标
- 8.7k
- 派生
- 1.8k
- 平均合并
- 1 天 17 小时
- 30 天内合并 PR
- 515
描述
## Summary
`createUnixTimestampInSecondsFunc()` in `@sentry/core` (`packages/core/src/utils/time.ts`) builds the span-timestamp clock as:
```ts
const timeOrigin = performance.timeOrigin;
return () => (timeOrigin + withRandomSafeContext(() => performance.now())) / ONE_SECOND_IN_MS;
```
It trusts `performance.timeOrigin` **unconditionally**, with a standing `// TODO: This does not account for the case where the monotonic clock that powers performance.now() drifts from the wall clock ...`.
Meanwhile `getBrowserTimeOrigin()` (added for #2590 / PR #3356) **does** validate the origin against `Date.now()` with a 5-minute threshold before trusting it — but that guarded value feeds `browserPerformanceTimeOrigin`, **not** the span-timestamp function above. So the class of bug #2590 set out to fix still reaches production through the span-timestamp path. (Still the case on `develop` as of this writing.)
## Impact
When `performance.timeOrigin + performance.now()` drifts from `Date.now()` — the monotonic clock stops while the device/computer sleeps, long-lived process, backgrounded tab (the scenarios #2590 itself described) — **every span/transaction is timestamped in the past**. Ingest accepts the envelope (HTTP 200) and the backend then silently drops/hides it, so tracing appears "broken" with no client-side error. Errors and sessions (which use `dateTimestampInSeconds` / `Date.now()`) are unaffected, which makes this especially hard to diagnose.
## Real-world reproduction (React Native 0.86)
On React Native 0.86 (iOS), `performance.now()` advances only while the process/host is awake while `performance.timeOrigin` stays fixed wall-clock, so on a long-lived process the sum drifts behind `Date.now()` by the accumulated sleep. Measured in-app:
```
performance.timeOrigin ≈ 2026-07-06 (fixed)
performance.now() ≈ 63 h
timeOrigin + performance.now() ≈ 2026-07-10 ← ~7 days behind
Date.now() ≈ 2026-07-17
```
Every navigation / HTTP / app-start transaction was stamped ~7 days in the past and dropped, while errors were fine. Versions: `@sentry/react-native` 8.7.0, `@sentry/core` 10.47.0.
We worked around it app-side by forcing `@sentry/core` onto its `Date.now()` fallback (shadowing `performance.timeOrigin` to `0` when drift is detected). The underlying RN clock behavior is tracked at react/react-native#57595 — but the SDK could be made resilient to *any* drifting platform clock, which is what this issue is about.
## Suggested fix
Apply the drift check that `getBrowserTimeOrigin()` already implements to the span-timestamp path: in `createUnixTimestampInSecondsFunc()`, if `|timeOrigin + performance.now() - Date.now()|` exceeds a threshold, fall back to `dateTimestampInSeconds` (or re-anchor the origin). That closes the gap left open after #2590 / #3356 and makes span timestamps robust to sleep/drift on every platform, not just RN.
## Environment
- `@sentry/core` 10.47.0 (via `@sentry/react-native` 8.7.0); the span-timestamp path is unchanged on `develop`.
- Trigger platform here: React Native 0.86 / iOS — but not RN-specific in principle; any runtime whose monotonic clock drifts from wall time is affected.
Related: #2590, #3356; react/react-native#57595.
贡献指南
评估
这个 Issue 还没有评估数据。