fix(crashlog): build the crash context outside the lock - #210
Conversation
The context provider is the GUI's own code, and it ran while the crash logger held its only lock. An invalid form field made the provider record its own ValueError into that lock on the same thread, and a worker's Tk read waited for a main loop that was waiting on the lock. Either way every later report from every thread queued behind it: the window froze and STOP did nothing. - crashlog: de-duplicate under the lock, build the context outside it, insert under the lock again with a second look (a concurrent record of the same new fault merges into one). A thread-local guard records a fault the provider raises itself without asking the provider again. - gui/crash: the report reads plain data only - a raw form copy taken on the main thread each tick, and the last counter snapshot. An invalid field is reported as settings_error instead of being recorded. - Four tests and four mutation entries, all caught. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI (base), Organization UI (inherited) Review profile: ASSERTIVE Plan: Advanced Run ID: 📒 Files selected for processing (5)
Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review. 📜 Recent review details⏰ Context from checks skipped due to timeout. (9)
🧰 Additional context used📓 Path-based instructions (15)Applies to text shown to the user (labels, buttons, tooltips, placeholders, dialogs, errors, status messages, empty states, translations).⚙️ CodeRabbit configuration file Files:
Verify tests check real behavior and would fail if the implementation were broken.⚙️ CodeRabbit configuration file Files:
Domain: network condition simulator (latency, loss, throttling, disconnects) built on WinDivert via PyDivert, with a Tkinter GUI and a CLI over one engine.⚙️ CodeRabbit configuration file Files:
These are end-user desktop applications.⚙️ CodeRabbit configuration file Files:
Performance is a known weak spot of these projects.⚙️ CodeRabbit configuration file Files:
Applies only to code that builds or styles a GUI.⚙️ CodeRabbit configuration file Files:
User-facing changelog.⚙️ CodeRabbit configuration file Files:
SECURITY, HIGH PRIORITY.⚙️ CodeRabbit configuration file Files:
These apps are QA/developer tools.⚙️ CodeRabbit configuration file Files:
Check that documentation matches the actual code in this PR: commands, flags, config keys, file paths, build steps and examples must exist.⚙️ CodeRabbit configuration file Files:
Python code.⚙️ CodeRabbit configuration file Files:
All code in this repository is written by an AI coding agent (Claude Code).⚙️ CodeRabbit configuration file Files:
Source excerpt: **Flat hyphen only.**📄 CodeRabbit inference engine (.github/claude-review-rules.md) Files:
No hardcoded UI styling: Only if the PR adds or changes GUI code (XAML, Slint, Fyne, Tkinter, WPF code-behind): warn if new or changed UI code sets colors, fonts, font sizes, margins, paddings, sizes or corner radii as literal values on ind...📄 CodeRabbit inference engine (Custom checks) Files:
Source excerpt: **Anything visible from outside goes in the changelog.**📄 CodeRabbit inference engine (.github/claude-review-rules.md) Files:
🔇 Additional comments (4)
📝 WalkthroughWalkthroughCrash reports now use cached GUI settings rather than reading form widgets during failure handling. The crash logger runs context providers outside its lock, detects provider re-entry, and rechecks fingerprints to deduplicate concurrent reports. ChangesCrash reporting
Priority: ➖ Normal Estimated code review effort: 3 (Moderate) | ~20 minutes Change: Bug fix · Severity of issue fixed: Medium Suggested labels: Merge Risk: ⚪ Minimal · up to No actionable merge-blocking defect is established. The change is mergeable after normal checks. Security Architecture ReviewSecurity architecture risk: 🔵 Low · up to The change addresses a program-wide logging deadlock without adding an identified external entry point or verified security flaw. Crash reports can now include invalid form values, so the contents and protection of local crash files remain relevant. Retained concerns Security review detailsSecurity Blast Radius
Trust Boundaries and Controls
Resilience and Maintainability Implications
🚥 Pre-merge checks | ✅ 14✅ Passed checks (14 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary
The crash logger could deadlock the whole program.
crashlog._recordbuilt the report context while holding its only lock, and the context provider is the GUI's own code (gui/crash.context). Both ways it deadlocked were reproduced before the fix:ValueErrorand passed it tocrashlog.note(). That call went back intorecord()and tried to take the lock the same thread already held. The thread hung, and every later report from every thread queued behind it, the main thread included.tk.StringVar, which waits for the Tk main loop, while the main loop was waiting on the logger's lock.The WinDivert handle still closes in both cases: the stop path closes it first and the 10 s stall check fires. The damage was a frozen window (STOP did nothing), and a watchdog whose own crash-log calls queue behind the wedged lock.
Changes
crashlog.py_count_again) and nothing is written to disk twice._collect_context: a fault the provider records itself gets the base context only (context_provider_skipped: "re-entered"). Without the guard, provider -> note -> provider would recurse even with the lock out of the way.set_context_providernow state the provider's contract: plain data, no GUI toolkit calls, no lock a recording thread may hold. A stale sentence claiming the CLI sets a provider is removed; only the GUI does.gui/crash.pycontextreads plain data only:leave_breadcrumbon the main thread every tick, kept in aWeakKeyDictionaryin this module.Appis exactly on both class ratchets, so the copy is not stored onApp.app.last_snapshot, the same numbers one tick old, read without the engine lock.settings_errorplus the rawforminstead of recording a fault.contextis still recorded (crashlog.quiet). That is safe now, because no lock is held and the provider is not asked again.Tests
New in
tests/test_crashlog.py. Each test swaps in a private lock, so a regression fails that one test instead of hanging the suite.test_a_provider_that_records_a_fault_of_its_own_does_not_wedge_the_loggertest_two_threads_can_build_their_context_at_the_same_time: a barrier inside the provider, passable only when both threads are in it at once.test_the_same_new_fault_from_two_threads_at_once_is_one_recordtest_a_gui_crash_report_never_reads_tk_off_the_main_thread: the real App on the fake Tk, an invalid field, and a worker writing a report.Mutation proof: four new entries in
tests/test_mutation_registry.py, all four caught:All 12
crashlog/crash:entries were run and caught. Themove_to_endanchor moved into the helper, so its indentation was updated.Run locally:
ruffandmypyon the changed modules;Not run locally: the full suite (it runs here on Linux and Windows).
🤖 Generated with Claude Code
Summary by CodeRabbit