diff --git a/CRASH_INVESTIGATION.md b/CRASH_INVESTIGATION.md new file mode 100644 index 00000000..60ac95b0 --- /dev/null +++ b/CRASH_INVESTIGATION.md @@ -0,0 +1,126 @@ +# Crash Investigation - October 10, 2025 + +## Summary +App crashed after 9.5 hours of runtime with SIGABRT on ThreadPoolServiceThread while running under Rosetta translation. + +## Root Cause +**Rosetta Translation Instability** - Running x86_64 build on Apple Silicon via Rosetta, likely triggered by: +- Native module (simple-git) incompatibility with Rosetta +- Resource accumulation from git operations over time +- Thread pool exhaustion + +## Crash Details +- **Exception**: EXC_CRASH (SIGABRT) +- **Termination**: Namespace ROSETTA, Code 0 +- **Thread**: ThreadPoolServiceThread (#2) +- **Runtime**: ~9.5 hours (started 05:03:12, crashed 14:52:22) +- **App Version**: 0.1.10 +- **OS**: macOS 15.5 (24F74) on Apple M1 Pro +- **Architecture**: x86_64 (via Rosetta translation) ⚠️ + +## Fixes Applied + +### 1. Resource Leak Prevention +- Added git instance caching to prevent creating new instances for each operation +- Added cleanup method to properly dispose of git instances on app quit +- Added timeout for update checks (5 minutes) to prevent hanging operations + +### 2. Recommended Actions + +#### High Priority +1. **Use ARM64 Build**: Ensure users on Apple Silicon use the native arm64 build + ```bash + # Check current architecture + file /Applications/Bottleneck.app/Contents/MacOS/Bottleneck + ``` + +2. **Update Electron**: Consider upgrading from v27.3.11 to latest stable + ```bash + npm install electron@latest + ``` + +3. **Monitor simple-git**: Consider adding process monitoring + ```typescript + // In git.ts, add logging for operations + async clone(repoUrl: string, localPath?: string): Promise { + console.log(`[Git] Starting clone: ${repoUrl}`); + try { + const result = await git.clone(repoUrl, targetPath); + console.log(`[Git] Clone completed: ${repoUrl}`); + return result; + } catch (error) { + console.error(`[Git] Clone failed: ${repoUrl}`, error); + throw error; + } + } + ``` + +#### Medium Priority +1. **Add Crash Reporting**: Integrate Sentry or similar +2. **Add Memory Monitoring**: Track memory usage over time +3. **Add Process Monitoring**: Monitor child processes from simple-git + +#### Low Priority +1. **Consider alternative git implementation**: Try using native child_process for git commands +2. **Add health checks**: Periodic memory/resource health checks + +## Monitoring Script + +To monitor for similar issues, check: +```bash +# Check running architecture +ps aux | grep Bottleneck + +# Monitor file descriptors +lsof -p | wc -l + +# Monitor threads +ps -M + +# Check for Rosetta processes +ps aux | grep oahd +``` + +## Prevention + +### Build Configuration +Ensure your build process creates proper universal binaries: +```json +{ + "mac": { + "target": { + "target": "default", + "arch": ["arm64", "x64"] + } + } +} +``` + +### Distribution +Consider distributing separate builds: +- `Bottleneck-arm64.dmg` for Apple Silicon +- `Bottleneck-x64.dmg` for Intel Macs + +### Testing +Test both architectures before release: +```bash +# Build and test +npm run dist + +# Test arm64 +open release/mac-arm64/Bottleneck.app + +# Test x64 under Rosetta +arch -x86_64 open release/mac/Bottleneck.app +``` + +## Related Issues +- Electron running under Rosetta: https://github.com/electron/electron/issues?q=rosetta +- simple-git memory leaks: https://github.com/steveukx/git-js/issues?q=memory + +## Next Steps +1. ✅ Apply code fixes (done) +2. ⏳ Rebuild application +3. ⏳ Test for 24+ hours +4. ⏳ Monitor crash reports +5. ⏳ Consider adding telemetry diff --git a/src/main/git.ts b/src/main/git.ts index 5ce3465c..e1ef5e61 100644 --- a/src/main/git.ts +++ b/src/main/git.ts @@ -27,8 +27,19 @@ export interface DiffResult { } export class GitOperations { + private gitInstances: Map = new Map(); + private getGit(repoPath: string): SimpleGit { - return simpleGit(repoPath); + // Reuse git instances to prevent resource leaks + if (!this.gitInstances.has(repoPath)) { + this.gitInstances.set(repoPath, simpleGit(repoPath)); + } + return this.gitInstances.get(repoPath)!; + } + + // Add cleanup method + cleanup(): void { + this.gitInstances.clear(); } private getReposPath(): string { diff --git a/src/main/index.ts b/src/main/index.ts index 5ee77fcf..dafe3eb5 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -201,7 +201,12 @@ app.whenReady().then(async () => { ? autoUpdater.checkForUpdatesAndNotify() : autoUpdater.checkForUpdates(); - checkPromise + // Add timeout to prevent hanging update checks + const timeoutPromise = new Promise((_, reject) => { + setTimeout(() => reject(new Error('Update check timed out after 5 minutes')), 5 * 60 * 1000); + }); + + Promise.race([checkPromise, timeoutPromise]) .catch((error) => { console.error(`Failed ${reason} update check:`, error); }) @@ -317,6 +322,10 @@ app.on("before-quit", () => { clearInterval(periodicUpdateInterval); periodicUpdateInterval = null; } + // Clean up git operations to prevent resource leaks + if (gitOps) { + gitOps.cleanup(); + } }); // IPC Handlers