-
-
Notifications
You must be signed in to change notification settings - Fork 0
Reproduction for sentry-react-native#5561 #16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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(); | ||
|
|
||
| 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; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| { | ||
| "name": "SentryReproApp", | ||
| "displayName": "Sentry Repro 5561" | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| module.exports = { | ||
| presets: ['@react-native/babel-preset'], | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused variable
datafrom response parsingLow Severity
The
datavariable is assigned fromresponse.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.