Preserve original stack traces in cross-project panic handling#3726
Preserve original stack traces in cross-project panic handling#3726
Conversation
When a panic occurs in a cross-project goroutine, wrap the recovered panic value and its stack trace in a core.PanicWithStack struct. When re-panicking, use this struct so the server's recover method can extract and report the original stack trace instead of the re-panic site's stack. Agent-Logs-Url: https://github.com/microsoft/typescript-go/sessions/3e8c5719-fcb6-448b-b817-c628dd2c0bc6 Co-authored-by: DanielRosenwasser <972891+DanielRosenwasser@users.noreply.github.com>
Agent-Logs-Url: https://github.com/microsoft/typescript-go/sessions/3e8c5719-fcb6-448b-b817-c628dd2c0bc6 Co-authored-by: DanielRosenwasser <972891+DanielRosenwasser@users.noreply.github.com>
|
@copilot the problem is includes what's reported in telemetry.
|
Adds a test demonstrating what the sanitized telemetry stack trace looks like when a panic occurs in a cross-project worker goroutine and is preserved via PanicWithStack. The sanitized output shows the actual crash site (e.g., checker.checkExpression) and full call chain through provideSymbolsAndEntries and handleCrossProject, rather than just showing the re-panic location. Agent-Logs-Url: https://github.com/microsoft/typescript-go/sessions/a16961db-d2a6-42e8-b21a-c00c664561b0 Co-authored-by: DanielRosenwasser <972891+DanielRosenwasser@users.noreply.github.com>
Done in 5cdf72f. Here's what the telemetry stack trace will look like with the fix: Before (re-panic site only, context lost): After (original crash site preserved via The |
|
@copilot can we avoid re-reporting parts of the stack? Right now the stack looks like |
…-B duplication NewPanicWithStack now trims debug.Stack()/deferred-recovery/panic() frames from the captured stack so telemetry only shows the actual crash site frames. The sanitizer is updated to handle stacks without the runtime/debug.Stack() marker (since those frames are trimmed). Added unit tests for trimPanicRecoveryFrames. Agent-Logs-Url: https://github.com/microsoft/typescript-go/sessions/13260bed-9e38-4ff0-8fe9-141e0b8a2228 Co-authored-by: DanielRosenwasser <972891+DanielRosenwasser@users.noreply.github.com>
... Fixed in 70f8be4. |
There was a problem hiding this comment.
Pull request overview
This PR improves panic diagnostics in the language server’s cross-project request handling by preserving the original crash-site stack trace across goroutine recovery/re-panics, ensuring logs and telemetry reflect where the panic actually occurred (rather than the re-panic site).
Changes:
- Introduces
core.PanicWithStackand stack-frame trimming (NewPanicWithStack/trimPanicRecoveryFrames) to retain the original panic stack. - Updates cross-project goroutine recovery to capture and re-panic with the preserved stack, and updates
Server.recoverto unwrap and report the original stack in logs/telemetry. - Extends stack sanitization to handle already-trimmed stacks and adds unit/baseline tests for trimming and sanitization.
Show a summary per file
| File | Description |
|---|---|
internal/core/panic.go |
Adds PanicWithStack wrapper and recovery-frame trimming for preserved panic stacks. |
internal/core/panic_test.go |
Adds unit tests for trimPanicRecoveryFrames. |
internal/ls/crossproject.go |
Captures panics in worker goroutines as PanicWithStack and re-panics after RunAndWait. |
internal/lsp/server.go |
Unwraps PanicWithStack in request recovery to log/report the original stack trace. |
internal/lsp/stack_sanitizer.go |
Allows sanitizing stacks that don’t start at runtime/debug.Stack() (trimmed stacks). |
internal/lsp/stack_sanitizer_test.go |
Adds a baseline test covering sanitized cross-project preserved stacks. |
testdata/baselines/reference/lsp/stackSanitizer/crossProjectPanicStackTrace.md |
New reference baseline for cross-project preserved-stack sanitization output. |
Copilot's findings
- Files reviewed: 7/7 changed files
- Comments generated: 1
| // Process existing known projects first | ||
| wg.RunAndWait() | ||
| // No need to use mu here since we are not in parallel at this point | ||
| if panicsOccured != nil { | ||
| panic(fmt.Sprintf("Panics occurred during cross-project handling: %v", panicsOccured)) | ||
| if panicsOccurred != nil { | ||
| panic(panicsOccurred[0]) | ||
| } |
handleCrossProjectcatches panics in worker goroutines and re-panics afterRunAndWait, but the re-panic creates a new stack starting atcrossproject.go:210. The actual crash site stack is lost from logs and telemetry.Changes:
core.PanicWithStackstruct to wrap a recovered panic value with its original stack tracecore.NewPanicWithStack()constructor that captures the stack and strips recovery infrastructure frames (debug.Stack, deferred recovery function, panic runtime) to avoid duplicating frames in the output*PanicWithStackin cross-project goroutine recovery instead of formatted stringsServer.recover, unwrapPanicWithStackto use the original stack for logging and telemetry reportingsanitizeStackTraceto handle trimmed stacks that no longer start withruntime/debug.Stack()trimPanicRecoveryFramesTestSanitizedCrossProjectPanicStackTrace) demonstrating what the sanitized telemetry output looks like for cross-project panicsBefore: telemetry/logs show stack starting at the re-panic site (
handleCrossProject).After: telemetry/logs show only the frames from the actual crash site onward, without duplicating recovery infrastructure: