|
| 1 | +--- |
| 2 | +title: Hang Detection |
| 3 | +description: Automatically detect and monitor main thread hangs in your iOS app with Embrace |
| 4 | +sidebar_position: 7 |
| 5 | +--- |
| 6 | + |
| 7 | +# Hang Detection |
| 8 | + |
| 9 | +The Embrace SDK automatically monitors your application's main thread for app hangs, providing visibility into UI freezes and unresponsive behavior that can frustrate users. |
| 10 | + |
| 11 | +## What are Hangs? |
| 12 | + |
| 13 | +A hang occurs when your app's main thread (UI thread) is blocked for too long, preventing the app from responding to user interactions. During a hang, your app may appear frozen—buttons don't respond, animations stop, and the UI becomes unresponsive. |
| 14 | + |
| 15 | +Common causes of hangs include: |
| 16 | + |
| 17 | +- Performing heavy computations on the main thread |
| 18 | +- Synchronous network requests |
| 19 | +- Large file I/O operations |
| 20 | +- Inefficient database queries |
| 21 | +- Deadlocks or race conditions |
| 22 | +- Slow third-party SDK initialization |
| 23 | + |
| 24 | +Even hangs as short as 250 milliseconds can be noticeable to users and negatively impact the user experience. |
| 25 | + |
| 26 | +For more information about understanding and improving hangs in iOS apps, see Apple's documentation: |
| 27 | + |
| 28 | +- [Understanding Hangs in Your App](https://developer.apple.com/documentation/xcode/understanding-hangs-in-your-app) |
| 29 | +- [Improving App Responsiveness](https://developer.apple.com/documentation/xcode/improving-app-responsiveness) |
| 30 | + |
| 31 | +## How Hang Detection Works |
| 32 | + |
| 33 | +The SDK automatically monitors the main thread using a dedicated watchdog thread. When the main thread is blocked for longer than 249 milliseconds (Apple's recommended threshold), a hang is detected and reported with: |
| 34 | + |
| 35 | +- Duration of the hang |
| 36 | +- Stack traces to identify the blocking code (when enabled) |
| 37 | +- Associated session and user context |
| 38 | + |
| 39 | +## Key Benefits |
| 40 | + |
| 41 | +- Automatically detect UI freezes and unresponsive behavior |
| 42 | +- Identify code causing main thread blockages |
| 43 | +- Track hang frequency and duration |
| 44 | +- No code changes required |
| 45 | + |
| 46 | +## Configuration |
| 47 | + |
| 48 | +Hang detection is enabled by default. You can customize settings during development or testing by implementing a custom `EmbraceConfigurable`: |
| 49 | + |
| 50 | +```swift |
| 51 | +import EmbraceIO |
| 52 | +import EmbraceConfiguration |
| 53 | + |
| 54 | +// Create a custom configuration class |
| 55 | +class CustomConfig: EmbraceConfigurable { |
| 56 | + // Customize hang detection limits |
| 57 | + var hangLimits = HangLimits( |
| 58 | + hangPerSession: 200, // Max hangs to capture per session |
| 59 | + samplesPerHang: 5 // Max stack trace samples per hang |
| 60 | + ) |
| 61 | + |
| 62 | + // Required EmbraceConfigurable properties with defaults |
| 63 | + var isSDKEnabled: Bool = true |
| 64 | + // ... add all other configs here |
| 65 | + |
| 66 | + func update(completion: (Bool, (any Error)?) -> Void) { |
| 67 | + completion(false, nil) |
| 68 | + } |
| 69 | +} |
| 70 | + |
| 71 | +// Initialize Embrace with custom configuration |
| 72 | +let options = Embrace.Options( |
| 73 | + appId: "YOUR_APP_ID", |
| 74 | + platform: .default |
| 75 | +) |
| 76 | + |
| 77 | +do { |
| 78 | + try Embrace.setup(options: options) |
| 79 | + try Embrace.client?.start() |
| 80 | +} catch { |
| 81 | + print("Failed to setup Embrace: \(error)") |
| 82 | +} |
| 83 | +``` |
| 84 | + |
| 85 | +:::info |
| 86 | +In production, hang detection is typically controlled via Embrace's remote configuration system. |
| 87 | +::: |
| 88 | + |
| 89 | +### Configuration Options |
| 90 | + |
| 91 | +#### HangLimits |
| 92 | + |
| 93 | +- **`hangPerSession`** (default: 200): Maximum hangs to capture per session. Set to `0` to disable hang detection. |
| 94 | + |
| 95 | +- **`samplesPerHang`** (default: 0): Number of stack trace samples to capture during a hang. Default `0` means no stack traces are captured. Increase to 5-10 when debugging to see how the stack evolves over time. |
| 96 | + |
| 97 | +:::info |
| 98 | +When attached to a debugger, hang detection is off. If you wish to enable it, set the `EMBAllowWatchdogInDebugger` environment variable to `1`. |
| 99 | +::: |
| 100 | + |
| 101 | +### Hang Detection Threshold |
| 102 | + |
| 103 | +The SDK uses a fixed threshold of **249 milliseconds** based on Apple's recommendations. This threshold captures hangs that are noticeable to users and cannot be customized. |
| 104 | + |
| 105 | +## Data Captured |
| 106 | + |
| 107 | +For each hang, the SDK captures: |
| 108 | + |
| 109 | +- Start time and duration |
| 110 | +- Stack traces (when `samplesPerHang > 0`) |
| 111 | +- Session context |
| 112 | + |
| 113 | +:::warning dSYM Upload Required |
| 114 | +Upload dSYM files to see symbolicated stack traces. See [dSYM Upload Guide](../getting-started/dsym-upload.md). |
| 115 | +::: |
| 116 | + |
| 117 | +## How Hangs Appear in OpenTelemetry |
| 118 | + |
| 119 | +Hangs are reported as OpenTelemetry spans with the name `emb-thread-blockage`. Stack trace samples (when enabled) are attached as span events. |
| 120 | + |
| 121 | +## Integration with Other Features |
| 122 | + |
| 123 | +Hangs are automatically correlated with: |
| 124 | + |
| 125 | +- Sessions and user identification |
| 126 | +- Active views |
| 127 | +- Network requests |
| 128 | +- Crashes |
| 129 | + |
| 130 | +## Best Practices |
| 131 | + |
| 132 | +### Default Settings |
| 133 | + |
| 134 | +The default configuration (`hangPerSession: 200`, `samplesPerHang: 0`) is optimized for production with minimal overhead. |
| 135 | + |
| 136 | +### Common Hang Sources |
| 137 | + |
| 138 | +Common code patterns that cause hangs: |
| 139 | + |
| 140 | +```swift |
| 141 | +// Bad: Synchronous network request on main thread |
| 142 | +let data = try Data(contentsOf: url) // Blocks until download completes |
| 143 | + |
| 144 | +// Good: Async network request |
| 145 | +URLSession.shared.dataTask(with: url) { data, response, error in |
| 146 | + // Handle response on background thread |
| 147 | +}.resume() |
| 148 | + |
| 149 | +// Bad: Heavy computation on main thread |
| 150 | +let result = processLargeDataset(data) // Blocks UI |
| 151 | + |
| 152 | +// Good: Background computation |
| 153 | +DispatchQueue.global(qos: .userInitiated).async { |
| 154 | + let result = processLargeDataset(data) |
| 155 | + DispatchQueue.main.async { |
| 156 | + // Update UI with result |
| 157 | + } |
| 158 | +} |
| 159 | + |
| 160 | +// Bad: Synchronous database query on main thread |
| 161 | +let users = try context.fetch(fetchRequest) // May block for large result sets |
| 162 | + |
| 163 | +// Good: Async database query |
| 164 | +context.perform { |
| 165 | + let users = try? context.fetch(fetchRequest) |
| 166 | + // Process results on background context |
| 167 | +} |
| 168 | +``` |
| 169 | + |
| 170 | +### Reducing Hangs |
| 171 | + |
| 172 | +- Move heavy work off the main thread |
| 173 | +- Use async/await or GCD for long-running operations |
| 174 | +- Optimize rendering and view hierarchy complexity |
| 175 | +- Profile with Instruments to identify bottlenecks |
| 176 | + |
| 177 | +## Disabling Hang Detection |
| 178 | + |
| 179 | +Set `hangPerSession: 0` in your configuration to disable hang detection. |
| 180 | + |
| 181 | +## Troubleshooting |
| 182 | + |
| 183 | +### Not Seeing Hang Data |
| 184 | + |
| 185 | +- Verify `hangPerSession > 0` |
| 186 | +- Confirm SDK is properly initialized |
| 187 | +- Test with `Thread.sleep(forTimeInterval: 0.5)` on the main thread |
| 188 | +- Check sessions are uploading successfully |
| 189 | + |
| 190 | +### Stack Traces Not Symbolicated |
| 191 | + |
| 192 | +Ensure dSYM files are uploaded for your app version. See [dSYM Upload Guide](../getting-started/dsym-upload.md). |
| 193 | + |
| 194 | +## Related Documentation |
| 195 | + |
| 196 | +- [Performance Monitoring](../manual-instrumentation/performance-monitoring.md) - Manual performance instrumentation |
| 197 | +- [Session Reporting](../getting-started/session-reporting.md) - Understanding session data |
| 198 | +- [dSYM Upload](../getting-started/dsym-upload.md) - Symbolication setup |
| 199 | +- [Configuration Options](../getting-started/configuration-options.md) - Complete SDK configuration |
| 200 | +- [View Tracking](./view-uikit-tracking.md) - Correlate hangs with specific views |
0 commit comments