Skip to content
Closed
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
237 changes: 237 additions & 0 deletions sentry-react-native/5561/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
import React, { useEffect, useState } from 'react';
import {
SafeAreaView,
ScrollView,
StatusBar,
StyleSheet,
Text,
View,
Button,
} from 'react-native';
import * as Sentry from '@sentry/react-native';

// Counter to track getRandomValues calls
let getRandomValuesCallCount = 0;
const callStacks: string[] = [];

// Monkey-patch crypto.getRandomValues to count calls and capture stack traces
const originalGetRandomValues = global.crypto?.getRandomValues;
if (originalGetRandomValues) {
global.crypto.getRandomValues = function(...args: any[]) {
getRandomValuesCallCount++;

// Capture stack trace to see where calls are coming from
const stack = new Error().stack || '';
const relevantStack = stack
.split('\n')
.slice(2, 6) // Skip first 2 lines (Error and this function)
.map(line => line.trim())
.join('\n ');

if (getRandomValuesCallCount <= 5 || getRandomValuesCallCount % 20 === 0) {
console.log(`[getRandomValues] Call #${getRandomValuesCallCount}`);
console.log(` Stack:\n ${relevantStack}`);
}

callStacks.push(relevantStack);

return originalGetRandomValues.apply(this, args);
};
}

// Initialize Sentry
Sentry.init({
dsn: process.env.SENTRY_DSN || '', // Set SENTRY_DSN env var or leave empty for testing
environment: __DEV__ ? 'development' : 'production',

// Keep disabled to avoid attaching stacktraces to non-exception events
attachStacktrace: false,

// Generic targets
tracePropagationTargets: ['localhost', 'httpbin.org'],

// Keep the same behavior as the original wrapper
tracesSampleRate: 1,

integrations: [
Sentry.reactNativeTracingIntegration?.({ idleTimeoutMs: 1500 }),
].filter(Boolean),
});

function App(): React.JSX.Element {
const [callCount, setCallCount] = useState(0);
const [fetchStatus, setFetchStatus] = useState('');

useEffect(() => {
// Update call count every 100ms
const interval = setInterval(() => {
setCallCount(getRandomValuesCallCount);
}, 100);

// Automatically run a test after 2 seconds
const timeout = setTimeout(() => {
console.log('[Auto-test] Running automatic fetch test...');
makeRequest();
}, 2000);

return () => {
clearInterval(interval);
clearTimeout(timeout);
};
}, []);

const makeRequest = async () => {
// Reset counter before the request
const beforeCount = getRandomValuesCallCount;
const stacksBefore = callStacks.length;
console.log(`\n[Test] Starting fetch request. Current getRandomValues calls: ${beforeCount}`);
setFetchStatus('Making request...');

try {
// Make a simple HTTP request
const response = await fetch('https://httpbin.org/get');
const data = await response.json();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unused variable data from response parsing

Low Severity

The data variable is assigned from response.json() but never used anywhere in the code. This dead store adds unnecessary code and could confuse future maintainers about whether the parsed response data is needed. If the JSON parsing is intentional to simulate a complete request lifecycle, the result can be discarded without assignment, or the parsing can be removed entirely.

Fix in Cursor Fix in Web


const afterCount = getRandomValuesCallCount;
const callsDuringRequest = afterCount - beforeCount;

console.log(`\n[Test] Fetch completed. getRandomValues calls during this request: ${callsDuringRequest}`);

// Analyze the call stacks to find patterns
const stacksDuringRequest = callStacks.slice(stacksBefore);
const uniqueStacks = new Set(stacksDuringRequest);

console.log(`\n[Analysis] Unique call patterns: ${uniqueStacks.size}`);
console.log(`[Analysis] Total calls: ${callsDuringRequest}`);
console.log(`[Analysis] Duplicated calls: ${callsDuringRequest - uniqueStacks.size}`);

// Show a few unique stack patterns
let i = 0;
for (const stack of uniqueStacks) {
if (i < 3) {
console.log(`\n[Pattern ${i + 1}]:\n ${stack}`);
}
i++;
}

setFetchStatus(`Request completed. getRandomValues calls: ${callsDuringRequest} (${uniqueStacks.size} unique patterns)`);
} catch (error) {
console.error('[Test] Fetch error:', error);
const afterCount = getRandomValuesCallCount;
const callsDuringRequest = afterCount - beforeCount;
setFetchStatus(`Request failed. getRandomValues calls: ${callsDuringRequest}`);
}
};

return (
<SafeAreaView style={styles.container}>
<StatusBar barStyle="dark-content" />
<ScrollView contentContainerStyle={styles.scrollView}>
<View style={styles.content}>
<Text style={styles.title}>
Sentry React Native getRandomValues Test
</Text>

<Text style={styles.description}>
This app demonstrates the excessive getRandomValues calls issue.
Each fetch request should create ~2-3 spans but may trigger 90+ getRandomValues calls.
</Text>

<View style={styles.statsBox}>
<Text style={styles.statsTitle}>
Total getRandomValues calls:
</Text>
<Text style={styles.statsValue}>{callCount}</Text>
</View>

{fetchStatus ? (
<View style={styles.statusBox}>
<Text style={styles.statusText}>{fetchStatus}</Text>
</View>
) : null}

<Button title="Make HTTP Request" onPress={makeRequest} />

<View style={styles.instructions}>
<Text style={styles.instructionsTitle}>Instructions:</Text>
<Text style={styles.instructionsText}>
1. Open React Native debugger or Metro logs{'\n'}
2. Click "Make HTTP Request"{'\n'}
3. Check the console for getRandomValues call counts{'\n'}
4. Expected: ~2-5 calls per request{'\n'}
5. Actual: ~90+ calls per request
</Text>
</View>
</View>
</ScrollView>
</SafeAreaView>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
scrollView: {
flexGrow: 1,
},
content: {
padding: 20,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 10,
textAlign: 'center',
},
description: {
fontSize: 14,
color: '#666',
marginBottom: 20,
textAlign: 'center',
},
statsBox: {
backgroundColor: '#f0f0f0',
padding: 20,
borderRadius: 10,
marginBottom: 20,
alignItems: 'center',
},
statsTitle: {
fontSize: 16,
marginBottom: 10,
},
statsValue: {
fontSize: 48,
fontWeight: 'bold',
color: '#e74c3c',
},
statusBox: {
backgroundColor: '#e8f4f8',
padding: 15,
borderRadius: 8,
marginBottom: 20,
},
statusText: {
fontSize: 14,
color: '#2c3e50',
},
instructions: {
marginTop: 30,
padding: 15,
backgroundColor: '#fff3cd',
borderRadius: 8,
},
instructionsTitle: {
fontSize: 16,
fontWeight: 'bold',
marginBottom: 10,
},
instructionsText: {
fontSize: 14,
lineHeight: 20,
},
});

export default App;
143 changes: 143 additions & 0 deletions sentry-react-native/5561/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# Reproduction for sentry-react-native#5561

**Issue:** https://github.com/getsentry/sentry-react-native/issues/5561

## Description

This reproduction demonstrates excessive `getRandomValues` calls when making fetch requests in a React Native app with Sentry tracing enabled. The issue reports ~90 calls to `getRandomValues` per HTTP request, which is excessive compared to the expected 2-3 spans created per request.

The root cause appears to be in the default parameter evaluation of `generateSentryTraceHeader` and `generateTraceparentHeader` functions in `@sentry/core/build/esm/utils/tracing.js`:

```javascript
function generateSentryTraceHeader(
traceId = generateTraceId(), // ⚠️ Evaluated every time, even when traceId is passed
spanId = generateSpanId(), // ⚠️ Evaluated every time, even when spanId is passed
sampled,
) {
// ...
}
```

## Environment

- React Native: 0.81.5
- @sentry/react-native: 7.2.0
- react-native-get-random-values: 2.0.0
- Node.js: 24.10.0 (or compatible)
- Hermes: Enabled
- New Architecture: Enabled

## Steps to Reproduce

### Prerequisites

This reproduction requires a full React Native environment setup. If you don't have one ready:
- Follow the [React Native Environment Setup guide](https://reactnative.dev/docs/environment-setup)
- Ensure you have Xcode (for iOS) or Android Studio (for Android) installed
- Have CocoaPods installed for iOS development

### Running the Reproduction

1. Install dependencies:
```bash
npm install
# or
yarn install
```

2. Initialize the React Native project structure (if not already present):
```bash
npx @react-native-community/cli init SentryReproApp --directory . --skip-install
```

3. Install iOS pods (iOS only):
```bash
cd ios && pod install && cd ..
```

4. Run the app:
```bash
# For iOS
npm run ios
# or
yarn ios

# For Android
npm run android
# or
yarn android
```

4. Once the app launches:
- Observe the initial `getRandomValues` call count (displayed in the app)
- Click the "Make HTTP Request" button
- Check the console logs for detailed call counts
- Observe the number of calls made during the single fetch request

5. Check Metro bundler console or React Native debugger for logs like:
```
[Test] Starting fetch request. Current getRandomValues calls: X
[getRandomValues] Call #X+1
[getRandomValues] Call #X+2
...
[getRandomValues] Call #X+90
[Test] Fetch completed. getRandomValues calls during this request: ~90
```

## Expected Behavior

Each fetch request should generate ~2-5 calls to `getRandomValues`:
- 1 call for transaction/trace ID
- 1 call for parent span ID
- 1-2 calls for child span IDs (e.g., http.client span)
- Maybe 1-2 additional calls for internal span management

Total: ~2-5 calls per request

## Actual Behavior

Each fetch request generates ~90+ calls to `getRandomValues`, which is approximately 18-45x more than expected. This causes performance concerns, especially in apps with frequent network requests.

## Notes

- The reproduction includes a monkey-patched `crypto.getRandomValues` to count and log all calls
- The counter displays in real-time in the app UI
- Console logs show the exact number of calls per request
- The issue persists even with an empty DSN (tracing is still active)
- Disabling Sentry completely eliminates the excessive calls

## Alternative: Create from Scratch

If you prefer to start fresh with the official React Native CLI:

```bash
# Create a new React Native project
npx @react-native-community/cli@latest init TestProject --version 0.81.5

# Install Sentry
cd TestProject
npm install @sentry/react-native@7.2.0 react-native-get-random-values@2.0.0

# Replace App.tsx and index.js with the files from this reproduction
# Then follow steps 3-5 above
```

## Environment Variables

You can optionally set a Sentry DSN to see the traces in the Sentry UI:

```bash
export SENTRY_DSN="your-dsn-here"
```

However, the issue reproduces with an empty DSN as well since the tracing instrumentation is still active.

## Testing Status

⚠️ **Note**: This reproduction requires a full React Native environment with native iOS or Android setup. The issue cannot be easily reproduced in a Node.js-only environment due to React Native's native dependencies.

To test this reproduction:
1. Set up a React Native development environment
2. Initialize the native project structure
3. Run on an iOS simulator or Android emulator
4. Observe the excessive `getRandomValues` calls in the Metro console
4 changes: 4 additions & 0 deletions sentry-react-native/5561/app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "SentryReproApp",
"displayName": "Sentry Repro 5561"
}
3 changes: 3 additions & 0 deletions sentry-react-native/5561/babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
presets: ['@react-native/babel-preset'],
};
6 changes: 6 additions & 0 deletions sentry-react-native/5561/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import 'react-native-get-random-values';
import { AppRegistry } from 'react-native';
import App from './App';
import { name as appName } from './app.json';

AppRegistry.registerComponent(appName, () => App);
Loading