Skip to content

Conversation

@youknowone
Copy link
Member

@youknowone youknowone commented Dec 11, 2025

Summary by CodeRabbit

  • New Features
    • Added faulthandler feature for diagnostic output on fatal errors
    • Multiple activation methods: use -X faulthandler command-line flag, set PYTHONFAULTHANDLER environment variable, or automatic activation in development mode

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 11, 2025

Walkthrough

This pull request introduces Python faulthandler support to RustPython, including a restructured signal handling implementation with per-signal handler management, frame snapshot capabilities, and integration with the VM startup sequence via CLI options and environment variables.

Changes

Cohort / File(s) Summary
Dictionary/Configuration
.cspell.dict/python-more.txt
Added PYTHONFAULTHANDLER token to spell-check dictionary
Faulthandler Core Implementation
crates/stdlib/src/faulthandler.rs
Restructured fault handling with per-signal FaultHandler structs (Unix/Windows variants), new FatalErrorState for global management, signal-safe I/O utilities (puts, dump_hexadecimal, write_thread_id, dump_frame), frame snapshot mechanism, and unified faulthandler_fatal_error handler that replaces ad-hoc signal handling
VM Configuration
crates/vm/src/vm/setting.rs
Added faulthandler: bool field to Settings struct with false default
Runtime Integration
src/lib.rs
Added conditional initialization to import and enable Python faulthandler at startup when enabled in VM settings
CLI & Environment Settings
src/settings.rs
Added support for -X faulthandler CLI flag and PYTHONFAULTHANDLER environment variable; enabled faulthandler automatically in dev mode

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50–70 minutes

  • faulthandler.rs: Extensive structural refactoring with new handler state management, per-signal structs, frame snapshot mechanism, and platform-specific implementations (Unix/Windows)—requires careful verification of signal handling logic and state preservation/restoration
  • Integration points: Review initialization order in lib.rs and ensure settings propagate correctly from CLI/environment through VM configuration
  • Platform-specific code: Verify Unix and Windows conditional compilation blocks are logically sound
  • Signal-safe utilities: Confirm signal-safe I/O helpers (puts, dump_hexadecimal, etc.) are correctly implemented

Possibly related PRs

  • Faulthandler #6400: Also modifies crates/stdlib/src/faulthandler.rs and expands the faulthandler API surface (enable/disable/dump/handler state management), indicating this may be a continuation or related enhancement

Poem

🐰 With signals caught and frames in flight,
Our tiny rabbit keeps the code tight,
From CLI flags to handlers so true,
Fault-stopping magic—hop, we're through!

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Faulthandler' is a single word that is vague and lacks specificity about the actual changes being made. Improve the title to be more descriptive, such as 'Add faulthandler support to RustPython runtime' or 'Implement Python faulthandler module integration'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 89.29% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment on lines 33 to 78
/// faulthandler_handlers[]
/// Number of fatal signals
#[cfg(unix)]
const FAULTHANDLER_NSIGNALS: usize = 5;
#[cfg(windows)]
const FAULTHANDLER_NSIGNALS: usize = 3;

// CPython uses static arrays for signal handlers which requires mutable static access.
// This is safe because:
// 1. Signal handlers run in a single-threaded context (from the OS perspective)
// 2. FAULTHANDLER_HANDLERS is only modified during enable/disable operations
// 3. This matches CPython's faulthandler.c implementation
#[allow(static_mut_refs)]
#[cfg(unix)]
static mut FAULTHANDLER_HANDLERS: [FaultHandler; FAULTHANDLER_NSIGNALS] = unsafe {
[
FaultHandler {
signum: libc::SIGBUS,
enabled: false,
name: "Bus error",
previous: std::mem::zeroed(),
},
FaultHandler {
signum: libc::SIGILL,
enabled: false,
name: "Illegal instruction",
previous: std::mem::zeroed(),
},
FaultHandler {
signum: libc::SIGFPE,
enabled: false,
name: "Floating-point exception",
previous: std::mem::zeroed(),
},
FaultHandler {
signum: libc::SIGABRT,
enabled: false,
name: "Aborted",
previous: std::mem::zeroed(),
},
FaultHandler {
signum: libc::SIGSEGV,
enabled: false,
name: "Segmentation fault",
previous: std::mem::zeroed(),
},
]
};

#[allow(static_mut_refs)]
#[cfg(windows)]
static mut FAULTHANDLER_HANDLERS: [FaultHandler; FAULTHANDLER_NSIGNALS] = [
FaultHandler {
signum: libc::SIGFPE,
enabled: false,
name: "Floating-point exception",
previous: 0,
},
FaultHandler {
signum: libc::SIGABRT,
enabled: false,
name: "Aborted",
previous: 0,
},
FaultHandler {
signum: libc::SIGSEGV,
enabled: false,
name: "Segmentation fault",
previous: 0,
},
];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I was workshopping this on my own, prior to #6400, I came up with something similar to this, finding out from the libc repository that SIGILL does exist on Windows. Would this work?

Suggested change
/// faulthandler_handlers[]
/// Number of fatal signals
#[cfg(unix)]
const FAULTHANDLER_NSIGNALS: usize = 5;
#[cfg(windows)]
const FAULTHANDLER_NSIGNALS: usize = 3;
// CPython uses static arrays for signal handlers which requires mutable static access.
// This is safe because:
// 1. Signal handlers run in a single-threaded context (from the OS perspective)
// 2. FAULTHANDLER_HANDLERS is only modified during enable/disable operations
// 3. This matches CPython's faulthandler.c implementation
#[allow(static_mut_refs)]
#[cfg(unix)]
static mut FAULTHANDLER_HANDLERS: [FaultHandler; FAULTHANDLER_NSIGNALS] = unsafe {
[
FaultHandler {
signum: libc::SIGBUS,
enabled: false,
name: "Bus error",
previous: std::mem::zeroed(),
},
FaultHandler {
signum: libc::SIGILL,
enabled: false,
name: "Illegal instruction",
previous: std::mem::zeroed(),
},
FaultHandler {
signum: libc::SIGFPE,
enabled: false,
name: "Floating-point exception",
previous: std::mem::zeroed(),
},
FaultHandler {
signum: libc::SIGABRT,
enabled: false,
name: "Aborted",
previous: std::mem::zeroed(),
},
FaultHandler {
signum: libc::SIGSEGV,
enabled: false,
name: "Segmentation fault",
previous: std::mem::zeroed(),
},
]
};
#[allow(static_mut_refs)]
#[cfg(windows)]
static mut FAULTHANDLER_HANDLERS: [FaultHandler; FAULTHANDLER_NSIGNALS] = [
FaultHandler {
signum: libc::SIGFPE,
enabled: false,
name: "Floating-point exception",
previous: 0,
},
FaultHandler {
signum: libc::SIGABRT,
enabled: false,
name: "Aborted",
previous: 0,
},
FaultHandler {
signum: libc::SIGSEGV,
enabled: false,
name: "Segmentation fault",
previous: 0,
},
];
// CPython uses static arrays for signal handlers which requires mutable static access.
// This is safe because:
// 1. Signal handlers run in a single-threaded context (from the OS perspective)
// 2. FAULTHANDLER_HANDLERS is only modified during enable/disable operations
// 3. This matches CPython's faulthandler.c implementation
#[allow(static_mut_refs)]
static mut FAULTHANDLER_HANDLERS: [FaultHandler; if cfg!(unix) {5} else {4}] = unsafe {
[
#[cfg(unix)]
FaultHandler {
signum: libc::SIGBUS,
enabled: false,
name: "Bus error",
previous: std::mem::zeroed(),
},
FaultHandler {
signum: libc::SIGILL,
enabled: false,
name: "Illegal instruction",
previous: std::mem::zeroed(),
},
FaultHandler {
signum: libc::SIGFPE,
enabled: false,
name: "Floating-point exception",
previous: std::mem::zeroed(),
},
FaultHandler {
signum: libc::SIGABRT,
enabled: false,
name: "Aborted",
previous: std::mem::zeroed(),
},
FaultHandler {
signum: libc::SIGSEGV,
enabled: false,
name: "Segmentation fault",
previous: std::mem::zeroed(),
},
]
};
/// faulthandler_handlers[]
/// Number of fatal signals
const FAULTHANDLER_NSIGNALS: usize = FAULTHANDLER_HANDLERS.len();

You might also want a FaultHandler::default() implementation to same some repetition in the future.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! applied it

@youknowone youknowone force-pushed the faulthandler branch 2 times, most recently from 32c6419 to 7442ff1 Compare December 11, 2025 16:49
@youknowone youknowone marked this pull request as ready for review December 11, 2025 23:58
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
crates/stdlib/src/faulthandler.rs (1)

59-99: Consider future migration from static mut pattern.

The current implementation mirrors CPython's approach with static mut for signal handlers, which is acceptable for this use case. The #[allow(static_mut_refs)] at the module level (line 3) acknowledges this. However, static_mut_refs is being deprecated in Rust.

For future consideration, you could explore using std::cell::UnsafeCell with a wrapper type or std::sync::OnceLock for the parts that are only written during enable/disable. This is not blocking for this PR since it matches CPython's design.

src/lib.rs (1)

190-198: Consider logging faulthandler initialization failures.

The initialization correctly runs after site import, matching CPython's startup sequence. However, silently ignoring failures with let _ = may hide configuration issues. Consider logging a warning on failure:

     // Enable faulthandler if -X faulthandler, PYTHONFAULTHANDLER or -X dev is set
     // _PyFaulthandler_Init()
     if vm.state.settings.faulthandler {
-        let _ = vm.run_code_string(
+        if let Err(e) = vm.run_code_string(
             vm.new_scope_with_builtins(),
             "import faulthandler; faulthandler.enable()",
             "<faulthandler>".to_owned(),
-        );
+        ) {
+            warn!("Failed to enable faulthandler: {:?}", e);
+        }
     }
📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d34b2cf and ac33c4a.

⛔ Files ignored due to path filters (2)
  • Lib/test/test_cmd_line.py is excluded by !Lib/**
  • Lib/test/test_faulthandler.py is excluded by !Lib/**
📒 Files selected for processing (5)
  • .cspell.dict/python-more.txt (1 hunks)
  • crates/stdlib/src/faulthandler.rs (5 hunks)
  • crates/vm/src/vm/setting.rs (2 hunks)
  • src/lib.rs (1 hunks)
  • src/settings.rs (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.rs: Follow the default rustfmt code style by running cargo fmt to format Rust code
Always run clippy to lint Rust code (cargo clippy) before completing tasks and fix any warnings or lints introduced by changes
Follow Rust best practices for error handling and memory management
Use the macro system (pyclass, pymodule, pyfunction, etc.) when implementing Python functionality in Rust

Files:

  • src/lib.rs
  • crates/vm/src/vm/setting.rs
  • src/settings.rs
  • crates/stdlib/src/faulthandler.rs
🧠 Learnings (2)
📚 Learning: 2025-06-27T14:47:28.810Z
Learnt from: moreal
Repo: RustPython/RustPython PR: 5847
File: vm/src/stdlib/stat.rs:547-567
Timestamp: 2025-06-27T14:47:28.810Z
Learning: In RustPython's stat module implementation, platform-specific constants like SF_SUPPORTED and SF_SYNTHETIC should be conditionally declared only for the platforms where they're available (e.g., macOS), following CPython's approach of optional declaration using #ifdef checks rather than providing fallback values for other platforms.

Applied to files:

  • src/lib.rs
  • crates/stdlib/src/faulthandler.rs
📚 Learning: 2025-06-27T14:47:28.810Z
Learnt from: moreal
Repo: RustPython/RustPython PR: 5847
File: vm/src/stdlib/stat.rs:547-567
Timestamp: 2025-06-27T14:47:28.810Z
Learning: In RustPython's stat module implementation, platform-specific constants like SF_SUPPORTED and SF_SYNTHETIC should be conditionally declared only for the platforms where they're available (e.g., macOS), following CPython's approach of optional declaration rather than providing fallback values for other platforms.

Applied to files:

  • src/lib.rs
  • crates/stdlib/src/faulthandler.rs
🧬 Code graph analysis (1)
src/settings.rs (1)
src/interpreter.rs (1)
  • settings (59-62)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Run snippets and cpython tests (ubuntu-latest)
  • GitHub Check: Run snippets and cpython tests (macos-latest)
  • GitHub Check: Run snippets and cpython tests (windows-latest)
  • GitHub Check: Check Rust code with clippy
🔇 Additional comments (17)
.cspell.dict/python-more.txt (1)

173-173: LGTM!

The dictionary entry for PYTHONFAULTHANDLER is correctly added in alphabetical order, matching the new environment variable support in settings.

crates/stdlib/src/faulthandler.rs (12)

17-57: LGTM!

The platform-specific FaultHandler structs are well-designed with appropriate storage for previous handlers (sigaction on Unix, sighandler_t on Windows). The const fn new() constructors enable compile-time initialization of the static arrays.


114-146: LGTM!

The FrameSnapshot design with fixed-size arrays is correct for signal-safe contexts where heap allocation is forbidden. The sizing (256 bytes for filename, 128 for funcname, 100 max frames) provides reasonable limits.


148-219: LGTM!

The signal-safe output functions correctly avoid heap allocation and use raw libc::write. Ignoring the return value is acceptable in signal handler context where there's limited recovery options. The platform-specific length type casting (u32 on Windows, usize on Unix) correctly handles the libc::write signature differences.


221-282: LGTM!

The thread ID retrieval is platform-appropriate (pthread_self on Unix, GetCurrentThreadId on Windows). The hex width calculation using size_of::<usize>() * 2 correctly adapts to 32-bit vs 64-bit platforms.


284-319: Note: all_threads parameter is unused.

The _all_threads parameter is ignored. This is acceptable for an initial implementation since dumping all threads' tracebacks from a signal handler context is complex (requires thread enumeration and accessing other threads' frame stacks).


401-419: LGTM!

The enable function correctly stores configuration in FATAL_ERROR state before installing signal handlers, with proper error handling if installation fails.


446-536: LGTM!

The signal handler implementation correctly:

  1. Saves/restores errno for async-signal-safety
  2. Disables the handler before dumping to prevent recursion
  3. Re-raises the signal to allow default handling or chaining
  4. Special-cases SIGSEGV on Windows to match CPython behavior

The iter_mut() on FAULTHANDLER_HANDLERS in signal context is covered by the module-level safety comment.


538-596: LGTM!

The enable implementations correctly use platform-appropriate APIs (sigaction with SA_NODEFER on Unix, signal on Windows). The early return when already enabled prevents duplicate handler registration.


598-629: LGTM!

The disable implementation correctly uses atomic swap to get previous state while disabling. The Python-exposed disable() function returns the previous enabled state, matching CPython's API.


953-970: LGTM!

The check_signum validation correctly prevents user signal registration for fatal signals (which should use enable() instead) and validates the signal number range.


1079-1093: Verify the infinite loop for Windows SIGSEGV.

The infinite loop around libc::raise(libc::SIGSEGV) on Windows is unusual. Is this intentional to work around Windows signal delivery behavior? If so, a brief comment explaining why would be helpful.

             #[cfg(windows)]
             {
                 // On Windows, we need to raise SIGSEGV multiple times
+                // because Windows signal handling may not immediately terminate
                 loop {
                     unsafe {
                         libc::raise(libc::SIGSEGV);
                     }
                 }
             }

1169-1208: LGTM!

The Windows-specific exception constants and _raise_exception function provide test parity with CPython's faulthandler module.

crates/vm/src/vm/setting.rs (2)

22-24: LGTM!

The faulthandler field is correctly added as an ungated boolean with appropriate documentation referencing both -X faulthandler and PYTHONFAULTHANDLER environment variable.


162-162: LGTM!

Default initialization to false is correct—faulthandler should be explicitly opt-in.

src/settings.rs (2)

270-270: LGTM!

The -X faulthandler option is correctly handled in the implementation options parser, setting settings.faulthandler = true.


293-300: LGTM!

The faulthandler configuration correctly:

  1. Preserves CLI setting with logical OR for environment variable
  2. Enables faulthandler in dev mode, matching CPython's -X dev behavior

@youknowone youknowone merged commit 2ff1fba into RustPython:main Dec 12, 2025
13 checks passed
@youknowone youknowone deleted the faulthandler branch December 12, 2025 00:24
@youknowone youknowone mentioned this pull request Dec 24, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants