Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
154 commits
Select commit Hold shift + click to select a range
80cc878
Improve performance
owjs3901 Jun 10, 2026
f70607f
Fix version issue
owjs3901 Jun 10, 2026
102ea67
Fix java memory issue
owjs3901 Jun 10, 2026
13a6c7a
Fix data copy issue
owjs3901 Jun 10, 2026
13b69df
Rm dupl copy
owjs3901 Jun 10, 2026
34b22ca
Fix overflow issue
owjs3901 Jun 10, 2026
0b89530
Improve streaming
owjs3901 Jun 10, 2026
a1327b0
Improve macro
owjs3901 Jun 10, 2026
6c07dc2
Impl thread
owjs3901 Jun 10, 2026
0f7ff73
Add testcase
owjs3901 Jun 10, 2026
1047907
Split code
owjs3901 Jun 11, 2026
2bd284c
Optimize gen openapi
owjs3901 Jun 11, 2026
8126a49
Improve smart config
owjs3901 Jun 11, 2026
015a444
Improve jni
owjs3901 Jun 11, 2026
d1d4768
Add e2e test
owjs3901 Jun 12, 2026
3c8f677
Impl macro cache
owjs3901 Jun 12, 2026
36a183b
Add plugin
owjs3901 Jun 12, 2026
2067ac1
Impl smart mode
owjs3901 Jun 12, 2026
ca85a44
Rewrite
owjs3901 Jun 12, 2026
74d145a
Downgrade version
owjs3901 Jun 12, 2026
909fa07
Compress bytes
owjs3901 Jun 12, 2026
c25e432
Impl streaming
owjs3901 Jun 13, 2026
3fc91d4
Add memory tester
owjs3901 Jun 13, 2026
04182ce
Add memory tester
owjs3901 Jun 13, 2026
5e5d5e6
Fix block on
owjs3901 Jun 13, 2026
a188acb
Head writer
owjs3901 Jun 13, 2026
26386bd
Fix header callback
owjs3901 Jun 13, 2026
83d2d9a
Add fuzz test
owjs3901 Jun 13, 2026
de61faa
Refactor
owjs3901 Jun 15, 2026
0c33fc2
Impl error
owjs3901 Jun 15, 2026
c67b15a
Change default feature
owjs3901 Jun 15, 2026
338e2ad
Fix async issue
owjs3901 Jun 15, 2026
98242a5
Optimize WireHeaderReader
owjs3901 Jun 15, 2026
132d676
Add bench
owjs3901 Jun 15, 2026
7a84787
Refactor header parser
owjs3901 Jun 16, 2026
91d6943
Increase memory
owjs3901 Jun 16, 2026
c66cf52
Add testcase
owjs3901 Jun 16, 2026
40b8fac
Optimize
owjs3901 Jun 16, 2026
87ee44e
Add bench
owjs3901 Jun 16, 2026
af61a0a
Fix stream bugs
owjs3901 Jun 16, 2026
0290d4b
Improve wire
owjs3901 Jun 16, 2026
2e348e6
Improve inprocess
owjs3901 Jun 16, 2026
995081c
Update docs
owjs3901 Jun 16, 2026
a6643de
Improve
owjs3901 Jun 17, 2026
3f85700
Impl response body error
owjs3901 Jun 17, 2026
b2e0814
Cleanup code
owjs3901 Jun 17, 2026
ea85cf1
Fix schema skip issue
owjs3901 Jun 17, 2026
78922e4
Fix compile cache
owjs3901 Jun 17, 2026
43d13f4
Fix compile cache
owjs3901 Jun 17, 2026
c3eed9f
Fix
owjs3901 Jun 17, 2026
e8d5672
Improve
owjs3901 Jun 18, 2026
9567723
Improve
owjs3901 Jun 18, 2026
ed533b1
Improve multiple header
owjs3901 Jun 18, 2026
d2007d1
Cleanup code
owjs3901 Jun 18, 2026
f0a449f
Impl edit
owjs3901 Jun 18, 2026
f67b4df
Optimize macro
owjs3901 Jun 18, 2026
17d43b8
Optimize java
owjs3901 Jun 18, 2026
35c311b
Add benchmark for compile
owjs3901 Jun 19, 2026
19b260c
Check java buffer
owjs3901 Jun 19, 2026
c9867fc
Fix Streaming
owjs3901 Jun 19, 2026
b6cab63
Fix macro
owjs3901 Jun 19, 2026
4b84be9
Improve compile time
owjs3901 Jun 19, 2026
1d3b570
Fix warning
owjs3901 Jun 19, 2026
dd48f0a
Optimize jni
owjs3901 Jun 19, 2026
2b87a49
Optimize compile time
owjs3901 Jun 19, 2026
1e45119
Optimize compile time
owjs3901 Jun 19, 2026
abb449b
Split code
owjs3901 Jun 19, 2026
d7d0044
Split code
owjs3901 Jun 20, 2026
438e65f
improve
owjs3901 Jun 20, 2026
62e17b2
improve zero copy
owjs3901 Jun 20, 2026
ea28c80
fix bugs
owjs3901 Jun 20, 2026
090530d
Improve
owjs3901 Jun 20, 2026
e650330
Cleanup code
owjs3901 Jun 21, 2026
74cff02
Decrease reallocs
owjs3901 Jun 21, 2026
53f81e6
Rm clippy warning
owjs3901 Jun 21, 2026
ef3bb12
Optimize jni
owjs3901 Jun 21, 2026
14b03f4
Cleanup code
owjs3901 Jun 21, 2026
460083b
Split code
owjs3901 Jun 21, 2026
457092d
Improve
owjs3901 Jun 21, 2026
b5eea88
Fix jni
owjs3901 Jun 21, 2026
9a6d7f8
Fix jni
owjs3901 Jun 22, 2026
26bb863
Fix jni
owjs3901 Jun 22, 2026
8d60e88
Fix jni
owjs3901 Jun 22, 2026
e7f8c8a
Optimize compiletime
owjs3901 Jun 23, 2026
cad83ac
Fix bugs
owjs3901 Jun 23, 2026
50f48aa
Impl route key
owjs3901 Jun 23, 2026
4030b6c
Optimize
owjs3901 Jun 24, 2026
311988a
retry-now#0002: collapse WireHeaderReader.apply byte[]/ByteBuffer dup…
owjs3901 Jun 24, 2026
3edb8aa
retry-now#0005: eliminate per-response containsConnectionHeaderKey pr…
owjs3901 Jun 25, 2026
4011c45
retry-now#0006: add no-multi-value fast path to copyDecodedHeaders
owjs3901 Jun 25, 2026
fbd0853
retry-now#0009: amortise per-char ASCII bound checks in VesperaWireCo…
owjs3901 Jun 25, 2026
2eb4e08
retry-now#0014: drop per-response int[1]+lambda by returning status f…
owjs3901 Jun 25, 2026
53b7357
retry-now#0015: zero-copy request HeaderValue via from_maybe_shared
owjs3901 Jun 25, 2026
7a16cd2
retry-now#0017: extract build_axum_request_inner helper from internal.rs
owjs3901 Jun 25, 2026
d33f21d
retry-now#0019: extract parse_wire_header_len shared validation helper
owjs3901 Jun 25, 2026
a3365e0
retry-now#0023: inline WIRE_VERSION=1 in response wire-header literal
owjs3901 Jun 25, 2026
a310c9e
retry-now#0026: pre-bake metadata segment for ResponseMetadata::curre…
owjs3901 Jun 25, 2026
d183b41
retry-now#0027: specialize HTTP status writer in response wire header
owjs3901 Jun 25, 2026
87de97f
retry-now#0030: reuse vespera_inprocess::check_ingress_cap from JNI
owjs3901 Jun 26, 2026
67cb9f6
retry-now#0031: drop redundant Arc<AtomicBool> in JNI streaming-with-…
owjs3901 Jun 26, 2026
fa1f174
retry-now#0037: dedupe extend+merge+check in process_export_app cache…
owjs3901 Jun 26, 2026
05760dd
retry-now#0038: gate parse_struct_def on cheap byte-scan in parse_typ…
owjs3901 Jun 26, 2026
4018ae8
retry-now#0039: drop wasted Vec<String> alloc in is_serde_json_value_…
owjs3901 Jun 26, 2026
731d623
retry-now#0040: dedup Span::call_site().local_file() in process_deriv…
owjs3901 Jun 26, 2026
74ec133
retry-now#0041: dedupe try_extract_schema_constraints walks across #[…
owjs3901 Jun 26, 2026
0f5fa5b
retry-now#0042: skip empty BTreeMap/Vec alloc in struct schema constr…
owjs3901 Jun 26, 2026
ed30499
retry-now#0043: extract shared crate_path_to helper in router_codegen…
owjs3901 Jun 26, 2026
72b937f
retry-now#0044: short-circuit process_default_functions when no defau…
owjs3901 Jun 26, 2026
f164e2d
retry-now#0046: consolidate duplicate extract_doc_comment into parser…
owjs3901 Jun 26, 2026
4a7bd70
retry-now#0047: collapse duplicate parse_type_to_schema_ref wrapper i…
owjs3901 Jun 26, 2026
1bf165f
retry-now#0048: remove redundant ident_str.clone() in parse_type_impl
owjs3901 Jun 26, 2026
5172c08
retry-now#0049: short-circuit cron-discovery when CRON_STORAGE is empty
owjs3901 Jun 26, 2026
4b7735a
retry-now#0050: drop wasted to_string() alloc in collect_leaf_custom_…
owjs3901 Jun 26, 2026
2dbcbb7
retry-now#0001: batch - split schema_impl.rs tests to sidecar, fix st…
owjs3901 Jun 27, 2026
80acb35
retry-now#0002: batch - push-in-place HeaderMap Multi collapse, drop …
owjs3901 Jun 27, 2026
319fcb7
retry-now#0003: dedupe inlined panic_wire / runtime_unavailable_wire …
owjs3901 Jun 27, 2026
274ff2e
retry-now#0004: batch - drop dead peek helper, flatten RequestProduce…
owjs3901 Jun 27, 2026
93ebbfa
retry-now#0005: batch - split defaults tests sidecar, extract multipa…
owjs3901 Jun 27, 2026
20a15c3
retry-now#0006: batch — bulk-copy verbatim runs in escape parser, ded…
owjs3901 Jun 27, 2026
ae0e281
retry-now#0007: batch -- router_oneshot helper, path_bytes_for_owned …
owjs3901 Jun 27, 2026
e518c7b
retry-now#0008: unify six header.iter().map adapters via WireRequestH…
owjs3901 Jun 27, 2026
a7a21ed
retry-now#0009: batch — drop unreachable!() panic site, factor header…
owjs3901 Jun 27, 2026
53819d5
retry-now#0010: batch — DRY parse_method_or_405 helper, extract owned…
owjs3901 Jun 27, 2026
3883b08
retry-now#0011: batch — must_use on DirectWriteResult/StreamOutcome +…
owjs3901 Jun 27, 2026
d83ae90
retry-now#0012: batch -- collect_header_map keys+get_all, dedupe cold…
owjs3901 Jun 27, 2026
744ac38
retry-now#0013: batch — warn on invalid VESPERA_RUNTIME_WORKERS, cent…
owjs3901 Jun 27, 2026
50b66d0
retry-now#0014: batch -- centralise HEADER_TOO_LARGE_MSG, add invalid…
owjs3901 Jun 28, 2026
617b998
retry-now#0015: batch -- collapse short-form ESCAPE arms, dedup Slice…
owjs3901 Jun 28, 2026
6f894e2
retry-now#0016: batch -- dedupe STACK_CAP across wire arms, replace S…
owjs3901 Jun 28, 2026
3930dd6
retry-now#0017: batch -- JNI streaming-with-header runtime-unavailabl…
owjs3901 Jun 28, 2026
c91ab96
retry-now#0018: batch --- extract header_value_as_str + compose_path_…
owjs3901 Jul 2, 2026
dd5e261
retry-now#0019: batch -- panic-proof dispatch() text-envelope API, ex…
owjs3901 Jul 2, 2026
b99f512
retry-now#0020: batch -- extract JNI panic-guard helpers, inline max_…
owjs3901 Jul 2, 2026
68e1295
retry-now#0021: batch — cache flags.failed Acquire load, lock INLINE_…
owjs3901 Jul 2, 2026
3af5b1b
retry-now#0022: batch dedup — split_once hoist media-type, header_val…
owjs3901 Jul 2, 2026
ad134e1
retry-now#0023: use OnceLock::get_or_init for JNI method_cache (1 kept)
owjs3901 Jul 2, 2026
29ccb79
retry-now#0025: batch - hoist streaming producer catch_unwind, let-el…
owjs3901 Jul 2, 2026
c7ac28f
retry-now#0026: batch — dedupe panic-header block, dedupe env-var lit…
owjs3901 Jul 2, 2026
1cf7837
Merge branch 'main' into improve-performance-2
owjs3901 Jul 5, 2026
55a9bfa
Update
owjs3901 Aug 3, 2026
1b722b6
retry-now#0001: batch — Split examples/axum-example/tests/integration…
owjs3901 Aug 4, 2026
79692f6
Merge branch 'improve-performance-2' of https://github.com/dev-five-g…
owjs3901 Aug 4, 2026
ed6787f
retry-now#0001: batch — Split tests/streaming_with_header.rs (1118 li…
owjs3901 Aug 4, 2026
b3f4f83
retry-now#0002: batch — Skip per-name HeaderMap hash lookups in the r…
owjs3901 Aug 5, 2026
2b7909a
retry-now#0003: batch — Remove the dead `path_lookup_epoch` field and…
owjs3901 Aug 5, 2026
877dd51
retry-now#0004: batch — collector build_route_path unification, wire.…
owjs3901 Aug 6, 2026
985a448
retry-now#0005: batch — Unify the unicode-escape surrogate grammar sh…
owjs3901 Aug 7, 2026
484873e
retry-now#0006: batch — collect_header_map: single-pass fast path whe…
owjs3901 Aug 7, 2026
4fc5ac9
retry-now#0007: batch — Dedup the per-file take-or-clone block in the…
owjs3901 Aug 7, 2026
9414fbc
retry-now#0008: batch — Correct root AGENTS.md: frontend workspace (a…
owjs3901 Aug 7, 2026
f3cc0a1
retry-now#0010: batch — Extract the shared ingress-cap + wire-split p…
owjs3901 Aug 8, 2026
868916c
retry-now#0011: batch — vespera_jni: unify the three copies of the pe…
owjs3901 Aug 8, 2026
65c6d11
retry-now#0012: batch — Remove the five redundant #[cfg(not(tarpaulin…
owjs3901 Aug 8, 2026
fad0a9c
retry-now#0013: batch — error_wire: hoist the fixed content-type Head…
owjs3901 Aug 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions .changepacks/changepack_log_openapi-3-1-schema-output.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"changes":{"Cargo.toml":"Minor","crates/vespera_core/Cargo.toml":"Minor","crates/vespera_macro/Cargo.toml":"Minor","crates/vespera_inprocess/Cargo.toml":"Minor","crates/vespera_jni/Cargo.toml":"Minor","crates/vespera/Cargo.toml":"Minor","libs/vespera-bridge/build.gradle.kts":"Minor"},"note":"BREAKING (0.x minor): OpenAPI schema output is now strict OpenAPI 3.1 / JSON Schema 2020-12: nullable schemas serialize as type:[...,\"null\"] or nullable $ref anyOf, and schema-level #[schema(example = ...)] serializes as examples:[...] instead of singular example. SecurityScheme now includes OAuth/OpenID fields flows and openIdConnectUrl. vespera-bridge 0.2.0 DecodedResponse.body() returns a read-only ByteBuffer; use bodyBytes() for an owned byte[] copy.","date":"2026-06-20T00:00:00.000Z"}
1 change: 1 addition & 0 deletions .changepacks/changepack_log_release-0-2-0-bridge.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"changes":{"Cargo.toml":"Minor","libs/vespera-bridge/build.gradle.kts":"Minor","libs/vespera-bridge-gradle-plugin/build.gradle.kts":"Minor"},"note":"0.2.0 / 0.3.0 release — BREAKING (0.x minor): DecodedResponse.body() returns read-only ByteBuffer (bodyBytes() copies on demand); SmartDispatchModeResolver is the autoconfigured default (DIRECT ~2.2µs / SYNC ~3.2µs for small requests, opt out via vespera.bridge.dispatch-mode=bidirectional-streaming); Gradle plugin now also publishes to the Plugin Portal. Perf: JMethodID+GlobalRef caching for streaming closures, daemon-attached dispatchAsync completion, lazy bidirectional request-pull (spawn on first body poll), JsonGenerator wire-header encoding, zero-copy get_byte_array_region input conversion. Rust: Validated 422 envelope via derive(Serialize) (byte-identical, snapshot-locked), per-invocation fs::metadata epoch caching in vespera_macro, collector clone elimination. See libs/vespera-bridge/docs/jni-before-after-2026-06-11.md for measured numbers.","date":"2026-06-12T13:00:00.000Z"}
3 changes: 2 additions & 1 deletion .changepacks/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
]
},
"publish": {
"java": "./gradlew publishToMavenCentral --stacktrace"
"java": "./gradlew publishToMavenCentral --stacktrace",
"libs/vespera-bridge-gradle-plugin/build.gradle.kts": "./gradlew publishToMavenCentral publishPlugins --stacktrace"
}
}
84 changes: 80 additions & 4 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
image: xd009642/tarpaulin:develop-nightly
options: --security-opt seccomp=unconfined
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
Expand All @@ -39,6 +39,11 @@ jobs:
run: cargo clippy --all-targets --all-features -- -D warnings
- name: Test Deploy
run: cargo publish --dry-run
- name: Doctest
# tarpaulin's --all-targets / default run never compiles doc
# tests, which let a never-passing doctest land unnoticed —
# run them explicitly before the (slow) coverage step.
run: cargo test --workspace --doc
- name: Test
run: |
# rust coverage issue
Expand All @@ -53,7 +58,7 @@ jobs:
cargo fmt
cargo tarpaulin --out Lcov Stdout --engine llvm
- name: Upload to codecov.io
uses: codecov/codecov-action@v6
uses: codecov/codecov-action@v7
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true
Expand All @@ -64,7 +69,9 @@ jobs:
changepacks:
name: changepacks
runs-on: ubuntu-latest
needs: test
# jni-e2e gates publishing: a release must never ship with a broken
# JNI dispatch path on any supported OS.
needs: [test, jni-e2e]
permissions:
# create pull request comments
pull-requests: write
Expand All @@ -76,7 +83,7 @@ jobs:
# Publish to GitHub Packages
packages: write
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: actions/setup-java@v5
with:
distribution: 'temurin'
Expand All @@ -101,6 +108,75 @@ jobs:
# GPG signing (in-memory key, no keyring file)
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.GPG_SIGNING_KEY }}
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.GPG_SIGNING_PASSWORD }}
# Gradle Plugin Portal credentials (read natively by
# com.gradle.plugin-publish for the `publishPlugins` task)
GRADLE_PUBLISH_KEY: ${{ secrets.GRADLE_PUBLISH_KEY }}
GRADLE_PUBLISH_SECRET: ${{ secrets.GRADLE_PUBLISH_SECRET }}
outputs:
changepacks: ${{ steps.changepacks.outputs.changepacks }}
release_assets_urls: ${{ steps.changepacks.outputs.release_assets_urls }}

# JNI end-to-end tests — builds the rust-jni-demo cdylib, publishes the
# vespera-bridge JAR to mavenLocal (so the demo-app Gradle plugin can
# resolve kr.devfive:vespera-bridge:0.1.1), then runs the full
# :demo-app:test suite (StreamingClosureStressTest + JNI dispatch tests)
# across all three target host OSes. This is the project's only Java/JNI
# coverage gate — until now the workflow ran zero JNI tests.
#
# Runs unconditionally on every push/PR (matching the existing CI job's
# style — no per-job paths-filter). The whole workflow already inherits
# the workflow-level `paths-ignore` for docs-only changes.
jni-e2e:
name: JNI E2E (${{ matrix.os }})
runs-on: ${{ matrix.os }}
timeout-minutes: 25
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
- uses: actions/checkout@v7
- uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '17'
cache: 'gradle'
- uses: actions-rust-lang/setup-rust-toolchain@v1
- name: Build rust-jni-demo cdylib (release)
# The vespera-bridge Gradle plugin's bundleNativeLib task copies
# this cdylib from target/release into demo-app's resources, so it
# must exist before `:demo-app:test` (processResources) runs.
run: cargo build -p rust-jni-demo --release
- name: Make gradlew executable (unix)
if: runner.os != 'Windows'
run: |
chmod +x libs/vespera-bridge/gradlew
chmod +x libs/vespera-bridge-gradle-plugin/gradlew
chmod +x examples/rust-jni-demo/java/gradlew
- name: Publish vespera-bridge Gradle plugin to mavenLocal
# demo-app's plugins block resolves kr.devfive.vespera-bridge from
# mavenLocal (settings.gradle.kts pluginManagement) — the plugin is
# not on the Gradle Plugin Portal.
shell: bash
working-directory: libs/vespera-bridge-gradle-plugin
run: ./gradlew publishToMavenLocal --console=plain --no-daemon
- name: Publish vespera-bridge to mavenLocal
# demo-app resolves kr.devfive:vespera-bridge:0.1.1 from mavenLocal
# (see examples/rust-jni-demo/java/demo-app/build.gradle.kts —
# bridgeVersion.set("0.1.1")).
shell: bash
working-directory: libs/vespera-bridge
run: ./gradlew publishToMavenLocal --console=plain --no-daemon
- name: Run demo-app JNI E2E tests
# Includes StreamingClosureStressTest (1000 × 1 MiB SHA256
# bidirectional round-trip). Bench knobs are NOT propagated —
# gated bench tests stay skipped in CI.
shell: bash
working-directory: examples/rust-jni-demo/java
run: ./gradlew :demo-app:test --console=plain --no-daemon
- name: Upload demo-app test results
if: always()
uses: actions/upload-artifact@v7
with:
name: jni-e2e-${{ matrix.os }}-test-results
path: examples/rust-jni-demo/java/demo-app/build/test-results/test/*.xml
112 changes: 112 additions & 0 deletions .github/workflows/bench.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
name: Bench

# Criterion regression gate for the in-process dispatch hot path.
#
# - push to main: runs the gated bench groups and saves the results as
# the `main` criterion baseline in the actions cache.
# - pull_request: restores the latest main baseline and compares; the
# job FAILS when any bench regresses by more than 10% mean change
# AND the 95% confidence interval lower bound exceeds +5% (the
# double condition filters shared-runner noise).
#
# Gated groups are the stable per-request paths (wire_path,
# headers_path, request_headers_path, resolve_path, dispatch_path). The
# streaming and contended groups are noisier (spawn_blocking / scheduler
# timing) and the router_path setup micro-bench is low-signal, so those
# are validated locally instead — see PERF_REPORT.md.
#
# This TIMING gate fires only at a loose ±10% (shared-runner drift), so it
# catches BIG regressions. Small, deterministic ALLOCATION regressions are
# caught noise-free by the `alloc_budget` integration test (a counting
# global allocator asserting exact per-dispatch allocation budgets) in the
# normal `cargo test` job — the two gates are complementary.

on:
push:
branches:
- main
paths:
- 'crates/**'
- 'Cargo.toml'
- 'Cargo.lock'
- '.github/workflows/bench.yml'
pull_request:
paths:
- 'crates/**'
- 'Cargo.toml'
- 'Cargo.lock'
- '.github/workflows/bench.yml'

concurrency:
group: bench-${{ github.ref }}
cancel-in-progress: true

env:
BENCH_FILTER: 'wire_path|headers_path|request_headers_path|resolve_path|dispatch_path'

jobs:
bench:
name: Criterion regression gate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7

- uses: actions-rust-lang/setup-rust-toolchain@v1

- name: Restore criterion baseline (latest main)
id: restore-baseline
uses: actions/cache/restore@v6
with:
path: target/criterion
key: bench-baseline-${{ runner.os }}-${{ github.sha }}
restore-keys: |
bench-baseline-${{ runner.os }}-

- name: Run benches and save main baseline
if: github.event_name == 'push'
run: |
cargo bench -p vespera_inprocess --bench dispatch -- \
--save-baseline main "${BENCH_FILTER}"

- name: Save criterion baseline cache
if: github.event_name == 'push'
uses: actions/cache/save@v6
with:
path: target/criterion
key: bench-baseline-${{ runner.os }}-${{ github.sha }}

- name: Compare against main baseline
if: github.event_name == 'pull_request'
run: |
if [ ! -d target/criterion ] || ! find target/criterion -maxdepth 4 -type d -name main | grep -q .; then
echo "::notice::No main baseline in cache yet — running benches without a gate."
cargo bench -p vespera_inprocess --bench dispatch -- "${BENCH_FILTER}"
exit 0
fi
cargo bench -p vespera_inprocess --bench dispatch -- \
--baseline main "${BENCH_FILTER}"

- name: Enforce regression gate
if: github.event_name == 'pull_request'
run: |
shopt -s nullglob
fail=0
found=0
while IFS= read -r f; do
found=1
mean=$(jq -r '.mean.point_estimate' "$f")
lower=$(jq -r '.mean.confidence_interval.lower_bound' "$f")
bench=$(dirname "$(dirname "$f")")
bench=${bench#target/criterion/}
printf '%s: mean %+.2f%% (CI lower %+.2f%%)\n' \
"$bench" "$(awk -v v="$mean" 'BEGIN{print v*100}')" \
"$(awk -v v="$lower" 'BEGIN{print v*100}')"
if awk -v m="$mean" -v l="$lower" 'BEGIN{exit !(m > 0.10 && l > 0.05)}'; then
echo "::error::Performance regression: ${bench} mean change exceeds +10% with CI lower bound > +5%"
fail=1
fi
done < <(find target/criterion -path '*/change/estimates.json')
if [ "$found" -eq 0 ]; then
echo "::notice::No change estimates found (first run against this baseline?) — nothing to gate."
fi
exit $fail
8 changes: 4 additions & 4 deletions .github/workflows/deploy-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,26 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7

- name: Install bun
uses: oven-sh/setup-bun@v2

- name: Cache bun dependencies
uses: actions/cache@v5
uses: actions/cache@v6
with:
path: ~/.bun/install/cache
key: bun-${{ runner.os }}-${{ hashFiles('**/bun.lock') }}
restore-keys: |
bun-${{ runner.os }}-

- name: Install Node.js
uses: actions/setup-node@v6
uses: actions/setup-node@v7
with:
node-version: 22

- name: Cache Next.js build
uses: actions/cache@v5
uses: actions/cache@v6
with:
path: |
apps/landing/.next/cache
Expand Down
98 changes: 98 additions & 0 deletions .github/workflows/jni-bench.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
name: JNI Bench (nightly)

# Informational JNI / perf benchmark run — NOT a regression gate.
#
# Most of the in-process & JNI performance work lives on the Java side
# (dispatch modes, daemon-env attach caching, direct buffers, mimalloc),
# which the criterion gate in bench.yml does NOT cover. Shared GitHub
# runners are far too noisy to threshold absolute ns/op, so this job runs
# the gated *BenchTest suite nightly purely to RECORD the numbers
# (printed to the job summary + uploaded as artifacts) so a human can spot
# drift over time. It never fails on a slow number — see PERF_REPORT.md
# for the locally-measured baselines.

on:
schedule:
# 06:00 UTC daily (~15:00 KST)
- cron: '0 6 * * *'
workflow_dispatch:

concurrency:
group: jni-bench-${{ github.ref }}
cancel-in-progress: true

jobs:
jni-bench:
name: JNI Bench (ubuntu-latest)
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v7
- uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '17'
cache: 'gradle'
- uses: actions-rust-lang/setup-rust-toolchain@v1
- name: Build rust-jni-demo cdylib (release)
# mimalloc is opt-in; the bench numbers reflect the default
# allocator unless the cdylib is built with --features mimalloc.
run: cargo build -p rust-jni-demo --release
- name: Make gradlew executable
run: |
chmod +x libs/vespera-bridge/gradlew
chmod +x libs/vespera-bridge-gradle-plugin/gradlew
chmod +x examples/rust-jni-demo/java/gradlew
- name: Publish vespera-bridge Gradle plugin to mavenLocal
working-directory: libs/vespera-bridge-gradle-plugin
run: ./gradlew publishToMavenLocal --console=plain --no-daemon
- name: Publish vespera-bridge to mavenLocal
working-directory: libs/vespera-bridge
run: ./gradlew publishToMavenLocal --console=plain --no-daemon
- name: Run JNI benchmarks (informational — never gates)
# The bench tests are gated behind -Dvespera.bench=true (the
# demo-app test task forwards that system property into the forked
# test JVM). This step is allowed to fail without failing the job:
# a flaky bench number must never break the nightly run.
continue-on-error: true
working-directory: examples/rust-jni-demo/java
run: |
./gradlew :demo-app:test -Dvespera.bench=true \
--tests 'kr.go.demo.*BenchTest' \
--console=plain --no-daemon
- name: Summarise bench results
if: always()
run: |
{
echo '## JNI bench results'
echo ''
echo '> **Watch the ratios, not the absolute ns/op.** The latency bench'
echo '> measures every mode *interleaved* (round-robin blocks, median of'
echo '> 100), so the cross-mode ratios (`async_vs_sync`, `direct_vs_sync`,'
echo '> `resp_only_vs_bidi`) are the noise-robust regression signal — they'
echo '> stay stable run-to-run even when absolute numbers drift ±10% on a'
echo '> shared runner. A ratio moving materially = a real regression.'
echo ''
echo '### Noise-robust ratios'
echo '```'
grep -hoE 'VESPERA_BENCH summary[^<]*' \
examples/rust-jni-demo/java/demo-app/build/test-results/test/*.xml \
| sort -u || echo '(no ratio summary captured)'
echo '```'
echo '### All bench lines'
echo '```'
# Bench lines (VESPERA_BENCH / ALLOC / CONC / JFR_LOAD) are
# captured in the JUnit XML <system-out>; pull them out for a
# quick at-a-glance view in the run summary.
grep -hoE 'VESPERA_(BENCH|ALLOC|CONC|JFR_LOAD)[^<]*' \
examples/rust-jni-demo/java/demo-app/build/test-results/test/*.xml \
| sort -u || echo '(no bench lines captured)'
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
- name: Upload bench results
if: always()
uses: actions/upload-artifact@v7
with:
name: jni-bench-results
path: examples/rust-jni-demo/java/demo-app/build/test-results/test/*.xml
if-no-files-found: warn
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ node_modules

# Generated OpenAPI artifacts at workspace root
/openapi*.json
.retry-now/
Loading
Loading