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}