Verified Commit 1ca434a8 authored by Michael Usachenko's avatar Michael Usachenko Committed by GitLab
Browse files

fix(code-graph): v2 pipeline hardening — error propagation, stack guards, tracing

parent 1269c2e7
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -68,6 +68,7 @@ engine:
        respect_gitignore: true
        worker_threads: 0
        max_concurrent_languages: 0
        per_file_timeout_ms: 5000
    namespace-deletion:
      concurrency_group: code
      max_attempts: 1  # re-dispatched daily, no need to retry
+11 −0
Original line number Diff line number Diff line
@@ -56,6 +56,7 @@
              "max_concurrent_languages": 0,
              "max_file_size_bytes": 5000000,
              "max_files": 1000000,
              "per_file_timeout_ms": 5000,
              "respect_gitignore": true,
              "worker_threads": 0
            },
@@ -337,6 +338,13 @@
          "default": 1000000,
          "minimum": 0
        },
        "per_file_timeout_ms": {
          "description": "Global per-file resolution timeout in milliseconds.\nApplied to all languages unless the language's own DSL rules\nspecify a different value. 0 = no global timeout.",
          "type": "integer",
          "format": "uint64",
          "default": 5000,
          "minimum": 0
        },
        "respect_gitignore": {
          "type": "boolean",
          "default": true
@@ -378,6 +386,7 @@
            "max_concurrent_languages": 0,
            "max_file_size_bytes": 5000000,
            "max_files": 1000000,
            "per_file_timeout_ms": 5000,
            "respect_gitignore": true,
            "worker_threads": 0
          }
@@ -449,6 +458,7 @@
                "max_concurrent_languages": 0,
                "max_file_size_bytes": 5000000,
                "max_files": 1000000,
                "per_file_timeout_ms": 5000,
                "respect_gitignore": true,
                "worker_threads": 0
              },
@@ -637,6 +647,7 @@
              "max_concurrent_languages": 0,
              "max_file_size_bytes": 5000000,
              "max_files": 1000000,
              "per_file_timeout_ms": 5000,
              "respect_gitignore": true,
              "worker_threads": 0
            },
+1 −0
Original line number Diff line number Diff line
@@ -681,6 +681,7 @@ impl LanguageSpec {
        if stacker::remaining_stack().unwrap_or(usize::MAX)
            < crate::legacy::parser::MINIMUM_STACK_REMAINING
        {
            tracing::warn!("stack limit reached during AST walk, subtree truncated");
            return;
        }

+96 −0
Original line number Diff line number Diff line
use std::fmt;

/// Errors from the v2 code-graph pipeline.
///
/// Covers infrastructure failures (thread pools, I/O), per-file
/// processing errors, and graph invariant violations.
#[derive(Debug)]
pub enum CodeGraphError {
    /// A file could not be read from disk.
    FileRead {
        path: String,
        source: std::io::Error,
    },

    /// A file could not be parsed (tree-sitter / OXC / Prism failure).
    ParseFailed { path: String, message: String },

    /// A rayon thread pool could not be created (OS resource limits).
    ThreadPoolCreation {
        language: String,
        source: rayon::ThreadPoolBuildError,
    },

    /// The sentinel watchdog thread could not be spawned.
    SentinelSpawn { source: std::io::Error },

    /// A graph node was expected to be a Definition but wasn't.
    UnexpectedNodeType { expected: &'static str, got: String },

    /// Arrow conversion failed for a graph.
    ArrowConversion { message: String },

    /// A sink write failed.
    SinkWrite { table: String, message: String },

    /// Generic internal error with context.
    Internal { context: String, message: String },
}

impl fmt::Display for CodeGraphError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::FileRead { path, source } => {
                write!(f, "failed to read {path}: {source}")
            }
            Self::ParseFailed { path, message } => {
                write!(f, "failed to parse {path}: {message}")
            }
            Self::ThreadPoolCreation { language, source } => {
                write!(f, "failed to create thread pool for {language}: {source}")
            }
            Self::SentinelSpawn { source } => {
                write!(f, "failed to spawn sentinel thread: {source}")
            }
            Self::UnexpectedNodeType { expected, got } => {
                write!(f, "expected {expected} node, got {got}")
            }
            Self::ArrowConversion { message } => {
                write!(f, "arrow conversion failed: {message}")
            }
            Self::SinkWrite { table, message } => {
                write!(f, "sink write to {table} failed: {message}")
            }
            Self::Internal { context, message } => {
                write!(f, "{context}: {message}")
            }
        }
    }
}

impl std::error::Error for CodeGraphError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            Self::FileRead { source, .. } => Some(source),
            Self::SentinelSpawn { source, .. } => Some(source),
            Self::ThreadPoolCreation { source, .. } => Some(source),
            _ => None,
        }
    }
}

impl CodeGraphError {
    /// Returns a stable stage label for metrics recording.
    pub fn stage(&self) -> &'static str {
        match self {
            Self::FileRead { .. } => "file_read",
            Self::ParseFailed { .. } => "parse",
            Self::ThreadPoolCreation { .. } => "thread_pool",
            Self::SentinelSpawn { .. } => "sentinel",
            Self::UnexpectedNodeType { .. } => "graph_node",
            Self::ArrowConversion { .. } => "arrow_conversion",
            Self::SinkWrite { .. } => "sink_write",
            Self::Internal { .. } => "internal",
        }
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -60,7 +60,7 @@ impl LanguagePipeline for JsPipeline {

        if !errors.is_empty() {
            for error in &errors {
                log::warn!("[v2-js] skipped {}: {}", error.file_path, error.error);
                tracing::warn!(path = %error.file_path, error = %error.error, "js: skipped file");
            }
        }
        Ok(())
Loading