Skip to main content

gonzalo_parse_worker/
gonzalo-parse-worker.rs

1//! Parse worker: a subprocess that reads source and writes the parsed
2//! [`CodeGraph`], one request per line. It exists to contain tree-sitter — the
3//! grammars are C and can `abort()` on malformed input, which is uncatchable in
4//! Rust; running parsing here means such a crash kills only this worker, not the
5//! daemon (see [`gonzalo_parse::ParserPool`]).
6//!
7//! ## Protocol (newline-delimited JSON, one exchange per line)
8//! - **Request**: a JSON [`ParseRequest`] (`{ language, source }`). JSON escapes
9//!   newlines, so a whole file is exactly one line.
10//! - **Response**: a JSON [`CodeGraph`] on one line.
11//!
12//! ## Fault injection (tests only)
13//! Two env-gated sentinels give deterministic stand-ins for grammar
14//! pathologies, used to test pool respawn/timeout. Unset in production, so
15//! neither is a live code path:
16//! - `GONZALO_PARSE_CRASH_TOKEN` — a request whose `source` equals it `abort()`s
17//!   the worker (a grammar crash).
18//! - `GONZALO_PARSE_HANG_TOKEN` — a request whose `source` equals it blocks
19//!   forever (a grammar hang).
20
21use gonzalo_graph::{EXTRACTION_VERSION, build};
22use gonzalo_parse::ParseRequest;
23use std::io::{BufRead, Write};
24
25fn main() {
26    // `--extraction-version`: print the format this worker produces and exit.
27    // The indexer is a separate binary and cannot otherwise tell whether the
28    // worker it found agrees with it about what a parse records (#228).
29    if std::env::args().nth(1).as_deref() == Some("--extraction-version") {
30        println!("{EXTRACTION_VERSION}");
31        return;
32    }
33
34    let crash_token = std::env::var("GONZALO_PARSE_CRASH_TOKEN").ok();
35    let hang_token = std::env::var("GONZALO_PARSE_HANG_TOKEN").ok();
36    let stdin = std::io::stdin();
37    let mut stdout = std::io::stdout();
38
39    for line in stdin.lock().lines() {
40        let Ok(line) = line else { break };
41        if line.is_empty() {
42            continue;
43        }
44        let Ok(request) = serde_json::from_str::<ParseRequest>(&line) else {
45            // A malformed request frame is a protocol error; skip it rather than
46            // die (the pool would otherwise see a spurious worker death).
47            continue;
48        };
49
50        if crash_token.as_deref() == Some(request.source.as_str()) {
51            std::process::abort();
52        }
53        if hang_token.as_deref() == Some(request.source.as_str()) {
54            std::thread::sleep(std::time::Duration::from_secs(3600));
55        }
56
57        let graph = build(request.language, &request.source);
58        let encoded = serde_json::to_string(&graph).expect("CodeGraph serializes");
59        if writeln!(stdout, "{encoded}").is_err() || stdout.flush().is_err() {
60            break; // parent closed the pipe
61        }
62    }
63}