Skip to content

bench(interpreter): add fork_for_serving benchmark#147

Merged
humancto merged 9 commits into
mainfrom
bench/fork-for-serving
May 3, 2026
Merged

bench(interpreter): add fork_for_serving benchmark#147
humancto merged 9 commits into
mainfrom
bench/fork-for-serving

Conversation

@humancto
Copy link
Copy Markdown
Owner

@humancto humancto commented May 3, 2026

Summary

  • Adds a Criterion benchmark for Interpreter::fork_for_serving() with empty and closure-heavy fixtures.
  • Wires the benchmark into CI as a visibility-only job and removes the loose wall-clock unit test.
  • Records the roadmap-loop plan under .planning/.

Test plan

  • cargo fmt -- --check
  • cargo test
  • cargo run -- --allow-run test
  • cargo run -- run examples/hello.fg
  • cargo run -- run examples/functional.fg
  • cargo test --all-features
  • cargo build --release
  • cargo bench --bench fork_for_serving -- --sample-size 10 --warm-up-time 1 --measurement-time 1

Notes

  • cargo run -- test without --allow-run fails the shell builtin tests by design because shell execution is permission-gated.
  • The short local Criterion smoke run executed successfully, though it reported small regressions against an existing local baseline.

Made with Cursor

Summary by CodeRabbit

  • Tests

    • Added comprehensive closure-isolation tests for forks and replaced an old timing test with a benchmark-backed approach.
    • Made multiple filesystem/path tests platform-independent, added helpers for embedding paths, and limited one symlink test to Unix.
  • Chores

    • CI now runs a new performance benchmark job and updated the test-run invocation; benchmarks collect visibility-only performance data.
    • Bench build profile configured for release-like benchmarking.
  • Style

    • Added Clippy lint suppressions.
  • Documentation

    • Added a planning document describing the benchmarking approach.

Track per-request interpreter fork cost with Criterion fixtures for both empty and closure-heavy templates, and surface the benchmark in CI.

Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 3, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a Criterion benchmark target for Interpreter::fork_for_serving with CI bench job, replaces an old timing test with detailed closure-isolation tests, makes many path-related tests platform-portable, adds small Clippy suppressions, and adds a planning document. (50 words)

Changes

Criterion Benchmark + Test Maintenance

Layer / File(s) Summary
Bench profile & dev-deps
Cargo.toml
Adds [profile.bench] opt-level = 3, adds criterion = { version = "0.7.0", features = ["html_reports"] } as a dev-dependency, and registers [[bench]] fork_for_serving (harness = false).
Benchmark implementation
benches/fork_for_serving.rs
Adds parse_and_run() and fixture_with_closures() helpers and two Criterion benchmarks measuring Interpreter::fork_for_serving() (empty and closures fixtures) using black_box.
Tests: closure isolation & portability changes
src/interpreter/tests.rs, src/package.rs, src/stdlib/*, src/vm/schedule_watch_tests.rs, src/publish.rs
Removes old fork_for_serving_is_under_50ms timing test; adds closure-isolation tests and helpers (template_with_mutable_closure, forge_string_literal_path, unique_temp_path, toml_path); updates many filesystem/path tests to use temp-dir-backed, escaped paths; marks a symlink test #[cfg(unix)].
Lint suppressions
src/lib.rs, src/vm/jit/runtime.rs
Adds #[allow(clippy::not_unsafe_ptr_arg_deref)] to exported entrypoint and #![allow(...)] crate-level in runtime.rs to suppress a Clippy lint; no signature or runtime behavior changes.
CI wiring
.github/workflows/ci.yml
Adds fork-benchmark job on ubuntu-latest running cargo bench --bench fork_for_serving with warm-up/measurement/sample settings; updates forge-tests invocation to include --allow-run.
Plan / Documentation
.planning/fork-for-serving-benchmark.plan.md
Adds a plan describing the benchmark goal (issue #112), implementation steps, CI visibility-only run, tests, and rollback steps.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Poem

🐰 I hopped through benches, tests, and CI,
Forked small runtimes so closures won’t fly,
Criterion counts each tiny spring,
Temp paths escaped, the runners sing,
Rabbit nods: compile, bench, and try.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: adding a Criterion benchmark for fork_for_serving.
Description check ✅ Passed The description covers the core changes, includes a comprehensive test plan with checked items, and references the related plan file, though it deviates from the template structure.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bench/fork-for-serving

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

Archith and others added 4 commits May 3, 2026 08:38
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Copy link
Copy Markdown

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/interpreter/tests.rs`:
- Around line 804-815: The test uses a fixed temp filename
("forge_test_exists.txt") via the path variable which can collide across
parallel tests; change the test to generate a unique temp file per run (e.g.,
append a UUID/timestamp or use a temp-file helper) before calling
forge_string_literal_path(&path) so each invocation gets its own path; apply the
same change for the other test files referenced (the forge_copy_test* and
forge_csv_test.csv usages) so all tests create per-test unique names and avoid
global temp_dir() filename collisions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a8bb903d-121b-4c1c-b13f-dd0e723dcc4e

📥 Commits

Reviewing files that changed from the base of the PR and between 6d08852 and 2b26f45.

📒 Files selected for processing (5)
  • src/interpreter/tests.rs
  • src/package.rs
  • src/stdlib/fs.rs
  • src/stdlib/path_module.rs
  • src/vm/schedule_watch_tests.rs
✅ Files skipped from review due to trivial changes (1)
  • src/stdlib/path_module.rs

Comment thread src/interpreter/tests.rs Outdated
Co-authored-by: Cursor <cursoragent@cursor.com>
Copy link
Copy Markdown

@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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/interpreter/tests.rs`:
- Around line 25-27: forge_string_literal_path currently only escapes
backslashes which allows double quotes in temp paths to produce invalid Forge
string literals; update the function (forge_string_literal_path) to also escape
double-quote characters when producing the string literal (e.g., first replace
backslashes with \\ then replace " with \") so any embedded " in the path is
escaped before embedding into Forge source.
- Around line 29-35: The function unique_temp_path can still collide when
SystemTime has coarse resolution; add an in-process monotonic counter to the
path to guarantee uniqueness across rapid calls. Introduce a static AtomicU64
(e.g., STATIC_COUNTER) and fetch_add(1, Ordering::Relaxed) inside
unique_temp_path, then include that counter value in the formatted filename
alongside pid and nanos so each call always gets a unique PathBuf; keep the
existing nanos code as-is and only append the counter to the join format.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f9be59b6-24cf-4f4a-8314-380ff8e53bb3

📥 Commits

Reviewing files that changed from the base of the PR and between 2b26f45 and 22d7dc6.

📒 Files selected for processing (1)
  • src/interpreter/tests.rs

Comment thread src/interpreter/tests.rs
Comment on lines +25 to +27
fn forge_string_literal_path(path: &std::path::Path) -> String {
path.to_string_lossy().replace('\\', "\\\\")
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Escape " as well when embedding paths into Forge string literals.

forge_string_literal_path currently escapes \ but not ". A temp path containing quotes will produce invalid Forge source.

Suggested fix
 fn forge_string_literal_path(path: &std::path::Path) -> String {
-    path.to_string_lossy().replace('\\', "\\\\")
+    path.to_string_lossy()
+        .replace('\\', "\\\\")
+        .replace('"', "\\\"")
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fn forge_string_literal_path(path: &std::path::Path) -> String {
path.to_string_lossy().replace('\\', "\\\\")
}
fn forge_string_literal_path(path: &std::path::Path) -> String {
path.to_string_lossy()
.replace('\\', "\\\\")
.replace('"', "\\\"")
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/interpreter/tests.rs` around lines 25 - 27, forge_string_literal_path
currently only escapes backslashes which allows double quotes in temp paths to
produce invalid Forge string literals; update the function
(forge_string_literal_path) to also escape double-quote characters when
producing the string literal (e.g., first replace backslashes with \\ then
replace " with \") so any embedded " in the path is escaped before embedding
into Forge source.

Comment thread src/interpreter/tests.rs
Comment on lines +29 to +35
fn unique_temp_path(name: &str) -> std::path::PathBuf {
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos())
.unwrap_or(0);
std::env::temp_dir().join(format!("forge_{}_{}_{}", name, std::process::id(), nanos))
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

unique_temp_path can still collide under coarse clock resolution.

Using only {pid}_{nanos} may duplicate when multiple calls land in the same timestamp bucket. Add a monotonic in-process counter to remove that flake vector.

Suggested fix
+use std::sync::atomic::{AtomicU64, Ordering};
+
+static TEMP_PATH_SEQ: AtomicU64 = AtomicU64::new(0);
+
 fn unique_temp_path(name: &str) -> std::path::PathBuf {
     let nanos = std::time::SystemTime::now()
         .duration_since(std::time::UNIX_EPOCH)
         .map(|d| d.as_nanos())
         .unwrap_or(0);
-    std::env::temp_dir().join(format!("forge_{}_{}_{}", name, std::process::id(), nanos))
+    let seq = TEMP_PATH_SEQ.fetch_add(1, Ordering::Relaxed);
+    std::env::temp_dir().join(format!(
+        "forge_{}_{}_{}_{}",
+        name,
+        std::process::id(),
+        nanos,
+        seq
+    ))
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fn unique_temp_path(name: &str) -> std::path::PathBuf {
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos())
.unwrap_or(0);
std::env::temp_dir().join(format!("forge_{}_{}_{}", name, std::process::id(), nanos))
}
use std::sync::atomic::{AtomicU64, Ordering};
static TEMP_PATH_SEQ: AtomicU64 = AtomicU64::new(0);
fn unique_temp_path(name: &str) -> std::path::PathBuf {
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos())
.unwrap_or(0);
let seq = TEMP_PATH_SEQ.fetch_add(1, Ordering::Relaxed);
std::env::temp_dir().join(format!(
"forge_{}_{}_{}_{}",
name,
std::process::id(),
nanos,
seq
))
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/interpreter/tests.rs` around lines 29 - 35, The function unique_temp_path
can still collide when SystemTime has coarse resolution; add an in-process
monotonic counter to the path to guarantee uniqueness across rapid calls.
Introduce a static AtomicU64 (e.g., STATIC_COUNTER) and fetch_add(1,
Ordering::Relaxed) inside unique_temp_path, then include that counter value in
the formatted filename alongside pid and nanos so each call always gets a unique
PathBuf; keep the existing nanos code as-is and only append the counter to the
join format.

Archith and others added 3 commits May 3, 2026 09:34
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@humancto humancto merged commit 909a81d into main May 3, 2026
12 checks passed
@humancto humancto deleted the bench/fork-for-serving branch May 3, 2026 17:06
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.

1 participant