fix: LSP git diff subprocess timeout (#13)#16
Conversation
Fix #13: LSP git diff subprocess timeout - Changed run_git_diff() to use spawn() + try_wait() polling loop with a 10-second timeout instead of blocking Command::output() - Spawns git diff process, polls try_wait() every 100ms - After 10s, kills the child process and returns an error - No new dependencies needed Also fixes a race condition in cmd_validate test by adding ENV_LOCK.
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
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 |
| fn cmd_validate_accepts_valid_globs_and_strict_no_warnings() { | ||
| // Acquire ENV_LOCK to prevent DIFFGUARD_TEST_FORCE_COMPILE_ERROR from | ||
| // leaking from cmd_validate_forced_compile_error when tests run in parallel. | ||
| let _guard = ENV_LOCK.lock().unwrap(); |
Check failure
Code scanning / diffguard
Avoid unwrap/expect in production code. Error
There was a problem hiding this comment.
Code Review
This pull request introduces a 10-second timeout for the git diff command in the LSP server and adds a mutex lock to a CLI test to prevent environment variable leakage during parallel execution. Feedback focuses on the git diff implementation, noting that the child process should capture output to avoid corrupting the LSP stream, must be reaped after being killed to prevent zombie processes, and should avoid blocking the main thread or deadlocking on pipe buffers.
| let output = command.output().context("run git diff")?; | ||
| // Spawn with a 10-second timeout to avoid blocking the LSP indefinitely | ||
| const GIT_DIFF_TIMEOUT: Duration = Duration::from_secs(10); | ||
| let mut child = command.spawn().context("spawn git diff")?; |
There was a problem hiding this comment.
Using command.spawn() without capturing output causes the child process to inherit the LSP server's standard streams. Since the LSP communicates with the client via stdout, any output from git diff will be written directly to the communication channel, corrupting the protocol stream and likely causing the client to disconnect. You must configure the command to capture stdout and stderr using Stdio::piped().
let mut child = command
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()
.context("spawn git diff")?;| if Instant::now() >= deadline { | ||
| let _ = child.kill(); | ||
| bail!("git diff timed out after {}s", GIT_DIFF_TIMEOUT.as_secs()); | ||
| } |
There was a problem hiding this comment.
Failing to reap a killed process leaves a 'zombie' in the process table. In a long-running LSP server, this can lead to resource exhaustion over time. It is important to call child.wait() after child.kill() to ensure the process is properly reaped by the operating system.
| if Instant::now() >= deadline { | |
| let _ = child.kill(); | |
| bail!("git diff timed out after {}s", GIT_DIFF_TIMEOUT.as_secs()); | |
| } | |
| if Instant::now() >= deadline { | |
| let _ = child.kill(); | |
| let _ = child.wait(); | |
| bail!("git diff timed out after {}s", GIT_DIFF_TIMEOUT.as_secs()); | |
| } |
| let output = loop { | ||
| match child.try_wait() { | ||
| Ok(Some(_)) => { | ||
| // Process has exited; wait_with_output() returns immediately | ||
| break child.wait_with_output().context("wait for git diff")?; | ||
| } | ||
| Ok(None) => { | ||
| // Still running | ||
| if Instant::now() >= deadline { | ||
| let _ = child.kill(); | ||
| bail!("git diff timed out after {}s", GIT_DIFF_TIMEOUT.as_secs()); | ||
| } | ||
| thread::sleep(Duration::from_millis(100)); | ||
| } | ||
| Err(e) => bail!("checking git diff status: {e}"), | ||
| } | ||
| }; |
There was a problem hiding this comment.
This polling loop blocks the main LSP message processing thread for up to 10 seconds, making the server unresponsive to other requests (such as cancellations) during this time. Furthermore, if the git diff output exceeds the system's pipe buffer size (often 64KB), the child process will block indefinitely because the loop does not read from the pipes while waiting. This will eventually trigger the timeout and cause the operation to fail even if it would have succeeded otherwise. Consider offloading the process execution to a separate thread and using a channel with recv_timeout to handle the result safely.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f749b1b44d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| let output = command.output().context("run git diff")?; | ||
| // Spawn with a 10-second timeout to avoid blocking the LSP indefinitely | ||
| const GIT_DIFF_TIMEOUT: Duration = Duration::from_secs(10); | ||
| let mut child = command.spawn().context("spawn git diff")?; |
There was a problem hiding this comment.
Pipe git diff output before waiting
run_git_diff now uses command.spawn() without configuring stdio, so git inherits the LSP process stdout/stderr. In Rust this means wait_with_output() returns empty buffers, so successful diffs are seen as empty and diagnostics are suppressed, and the raw unified diff is emitted onto the server’s stdout stream (which can corrupt stdio-based LSP messaging) whenever a file has git changes.
Useful? React with 👍 / 👎.
fb13941
into
feat/v0.2-enhancements-v2
… analytics (#5) * Implement environment variable expansion for configuration files and add BDD integration tests for CLI workflows * Add conformance tests and schema validation for sensor report - Introduced new snapshots for sensor report findings and annotations. - Created a JSON schema for the sensor report format. - Implemented conformance tests to validate sensor report output against the schema. - Added tests for determinism, survivability, required fields, and vocabulary compliance. - Enhanced the test setup to create minimal git repositories and trigger findings. * feat: Add timing metrics to CheckReceipt and update related tests - Introduced `timing` field in `CheckReceipt` struct to capture performance metrics. - Updated tests in `schema.rs` and `properties.rs` to initialize `timing` with `None`. - Enhanced `RuleConfig` struct with `tags` field for better rule categorization. - Added multiple new rules across various languages (Rust, Python, JavaScript, Ruby, Java, C#) to enforce best practices and security. - Implemented SARIF integration in GitHub workflows for better visibility of findings. - Created Azure DevOps pipeline templates for diffguard to facilitate governance checks. * feat: Enhance configuration management and validation - Added support for environment variable expansion in configuration files. - Implemented a new `config_loader` module to handle includes and circular include detection. - Introduced `Validate` command to validate configuration files, checking for duplicate rule IDs and valid regex patterns. - Added `Test` command to run test cases defined in rule configurations. - Enhanced logging capabilities with `tracing` for better debugging and information output. - Updated schemas to include new fields for tags and timing metrics. - Improved integration tests for suppression directives and configuration loading. * feat: Update fingerprint computation to use full SHA-256 hash and enhance related tests * Add snapshot tests for JSON receipt and GitHub annotation formats - Implemented snapshot tests for various scenarios in JSON receipt output, including: - No findings - Warnings only - Errors only - Mixed severity findings - Findings with suppressions - Info-only findings - Findings without column info - Added snapshot tests for GitHub annotation format covering: - Empty annotations - Annotations with info, warning, and error findings - Annotations with multiple findings across different files - Annotations with special characters and deeply nested paths * Refactor verdict reasons handling in markdown rendering - Introduced a constant for renderable meta reasons to filter verdict reasons in markdown output. - Updated `render_markdown_for_receipt` to only display relevant meta reasons. - Modified tests to ensure non-meta reasons are excluded from markdown output. - Adjusted sensor report structure to include detailed reasons for capability status. - Updated JSON schema to reflect changes in sensor report structure. - Removed deprecated reasons from verdicts and ensured backward compatibility. - Added schema drift detection to maintain consistency between generated and contract schemas. * feat: Refactor error handling and improve JSON schema drift diagnostics * Refactor code structure for improved readability and maintainability * Refactor code structure for improved readability and maintainability * chore: remove accidental build artifacts and binaries * chore: reorder imports for consistency and readability across multiple files * chore: fmt, clippy fixes and unsafe blocks * chore: update authors and repository URLs in Cargo.toml * feat: add directory rule overrides and related tests - Introduced DirectoryRuleOverride struct for managing per-directory rule overrides from `.diffguard.toml`. - Implemented RuleOverrideMatcher for compiling and resolving effective overrides based on directory structure. - Added integration tests for directory overrides to validate behavior in various scenarios. - Created fuzz targets for config parsing, glob and regex pattern compilation, and rule matching to enhance robustness against edge cases. - Updated Cargo.toml with additional metadata for the xtask package. * feat: Enhance rule configuration and CLI options for multi-base diff checks - Added support for multi-base comparisons in the `--base` option, allowing multiple base refs to be specified. - Introduced `--diff-file` option to read unified diffs from a file or stdin. - Updated `README.md` and `diffguard.toml.example` to reflect new configuration options and advanced semantics. - Enhanced rule configuration with new fields: `match_mode`, `multiline`, `context_patterns`, `escalate_patterns`, and `depends_on`. - Improved validation logic for rules, including checks for context and escalation patterns. - Added per-rule hit statistics output option to the CLI. - Updated design and requirements documentation to include new features and behaviors. - Implemented changes in fuzz targets to accommodate new rule fields. - Created a sample commit-msg hook to enforce commit message standards and staged diff checks. * Implement LSP server for diffguard with document state management and code action support - Added server functionality in `server.rs` to handle LSP requests and notifications. - Implemented document state management to track changes and apply incremental updates. - Introduced code action capabilities to provide explanations and links to documentation for rules. - Created utility functions in `text.rs` for handling text changes, building diffs, and calculating changed lines. - Added tests for core functionalities including changed line detection and synthetic diff generation. * refactor(lsp): split into lib+bin targets, fix initialize handshake - Add [lib] and [[bin]] sections for integration testing support - Use initialize_start/initialize_finish to properly send server_info - Add diffguard-testkit and insta as dev-dependencies - Re-export server module from lib.rs * fix(xtask): cargo_bin_path fallback to target/debug when CARGO_BIN_EXE not set The conformance tests (survivability, tool_error_code) were failing when run via cargo test -p xtask because CARGO_BIN_EXE_diffguard is only set when diffguard is a direct dependency of the test binary. xtask depends on diffguard-types and diffguard-analytics but not diffguard itself. Fix: cargo_bin_path() now mirrors run_diffguard() by building the binary via ensure_diffguard_built() and resolving the path in target/debug/ when the env var isn't available. * fix: remove duplicate init_logging call (#10) * chore: add conveyor governance templates and CI gates - Issue templates (feature/bug) with scope and acceptance criteria - PR template with gate checklists (Designed/Proven/Hardened) - Default PR template for non-conveyor contributors - CI workflow: separate fmt/clippy/test jobs with named status checks - CI gate: issue linkage check (PR must reference an issue) - CI gate: branch naming convention (warns on non-standard names) - Branch protection: requires Format, Clippy, Test, Issue linked - CONTRIBUTING.md documenting the governance flow - CODEOWNERS for templates and CI * docs: add DESIGN.md — governance primitive layer positioning * fix: LSP git diff timeout and cmd_validate ENV_LOCK race (#16) Fix #13: LSP git diff subprocess timeout - Changed run_git_diff() to use spawn() + try_wait() polling loop with a 10-second timeout instead of blocking Command::output() - Spawns git diff process, polls try_wait() every 100ms - After 10s, kills the child process and returns an error - No new dependencies needed Also fixes a race condition in cmd_validate test by adding ENV_LOCK.
* chore: add flow/plan docs for PR #5 merge prep Archives the full reasoning trail for landing v0.2 enhancements: - Issue triage (#6-#13) - Fix PRs (#14, #15, #16) - Merge process, friction log, stats Index in .hermes/index.md for discoverability. * chore: consolidate agent context into .hermes/agent-context.md AGENTS.md, CLAUDE.md, GEMINI.md were 90% identical with minor wording differences. Canonicalized into single .hermes/agent-context.md with thin pointer files at root for agent discovery. Added .hermes/index.md entry for agent-context.md.
* chore: add flow/plan docs for PR #5 merge prep Archives the full reasoning trail for landing v0.2 enhancements: - Issue triage (#6-#13) - Fix PRs (#14, #15, #16) - Merge process, friction log, stats Index in .hermes/index.md for discoverability. * chore: consolidate agent context into .hermes/agent-context.md AGENTS.md, CLAUDE.md, GEMINI.md were 90% identical with minor wording differences. Canonicalized into single .hermes/agent-context.md with thin pointer files at root for agent discovery. Added .hermes/index.md entry for agent-context.md. * chore: add publish script and release notes for v0.2.0 * chore: clean up Cargo.toml package metadata for crates.io - Remove redundant exclude blocks (include is an allowlist, exclude is ignored) - Remove CLAUDE.md from include lists (thin pointers, not needed in packages) - Fix include/exclude conflict warning * chore: fix crate includes for crates.io, add publish workflow Remove ../../ paths from include fields (crates.io only packages crate-local files). Add GitHub Actions publish workflow triggered on v* tag push with verify → publish → release stages. * chore: remove include fields, let cargo use git-tracked defaults
Fix #13: LSP git diff subprocess timeout
Problem
Command::new("git").output()blocks the LSP server indefinitely on large repos. No timeout configured.Fix
Changed
run_git_diff()incrates/diffguard-lsp/src/server.rs:try_wait()in a loop (100ms intervals)std::process::Command,std::thread,std::time::Instant)Verification
Also included
Fixes a race condition in
cmd_validatetest by addingENV_LOCKto prevent parallel test interference