Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,19 @@ All notable changes to this project are documented in this file. The format is
based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and releases
follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [0.1.1] - 2026-08-14

### Changed

- Reduced authenticated XHTTP hot-path allocation, locking, address-resolution,
reference-count, and session teardown overhead using profile-guided changes.
- Cancelled orphan-session grace timers promptly and skipped them entirely for the
normal download-first request order, substantially reducing transient RSS growth.
- Added a sustained PID-scoped `perf` driver, focused allocation reference
microbenchmarks, and a bilingual hotspot optimization report.

## [0.1.0] - 2026-08-13

### Added
Expand All @@ -26,3 +39,5 @@ follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- Added bounded request/session/target controls and fail-closed memory accounting.

[0.1.0]: https://github.com/jacek4yang/rust-xhttp/releases/tag/v0.1.0
[0.1.1]: https://github.com/jacek4yang/rust-xhttp/compare/v0.1.0...v0.1.1
[Unreleased]: https://github.com/jacek4yang/rust-xhttp/compare/v0.1.1...HEAD
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "rust-xhttp"
version = "0.1.0"
version = "0.1.1"
edition = "2024"
rust-version = "1.88"
description = "Pure-Rust XHTTP/VLESS server wire-compatible with the official Xray-core client (XHTTP packet-up + VLESS + VLESS-Encryption + Vision + XUDP)"
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,5 @@ welcome under [`CONTRIBUTING.md`](CONTRIBUTING.md).
| Configuration and deployment | [English](docs/configuration.md) | [简体中文](docs/configuration.zh-CN.md) |
| Benchmarks and evidence | [English](docs/benchmarks.md) | [简体中文](docs/benchmarks.zh-CN.md) |
| Performance and availability | [English](docs/performance-and-availability.md) | [简体中文](docs/performance-and-availability.zh-CN.md) |
| Hotspot optimization report | [English](docs/performance-hotspots.md) | [简体中文](docs/performance-hotspots.zh-CN.md) |
| Security policy | [English](SECURITY.md) | [简体中文](SECURITY.zh-CN.md) |
1 change: 1 addition & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ VLESS 协议。不支持 stream-up/stream-one,也不声称“不可检测”
| 配置与部署 | [English](docs/configuration.md) | [简体中文](docs/configuration.zh-CN.md) |
| Benchmark 与证据 | [English](docs/benchmarks.md) | [简体中文](docs/benchmarks.zh-CN.md) |
| 性能与可用性 | [English](docs/performance-and-availability.md) | [简体中文](docs/performance-and-availability.zh-CN.md) |
| 热点优化报告 | [English](docs/performance-hotspots.md) | [简体中文](docs/performance-hotspots.zh-CN.md) |
| 安全政策 | [English](SECURITY.md) | [简体中文](SECURITY.zh-CN.md) |

## 许可证
Expand Down
94 changes: 86 additions & 8 deletions benches/geo.rs
Original file line number Diff line number Diff line change
@@ -1,40 +1,118 @@
use criterion::{Criterion, criterion_group, criterion_main};
use http::{HeaderMap, Method, Uri};
use rust_xhttp::xhttp::{Meta, classify, extract_meta_from_path, host_matches, path_matches};
use rust_xhttp::vless::{User, Validator, process_uuid};
use rust_xhttp::xhttp::{
BorrowedMeta, ResponsePadding, classify_borrowed, extract_meta_from_path_borrowed,
extract_padding, extract_padding_len, generate_response_padding, host_matches,
is_padding_len_valid, is_padding_valid, path_matches,
};
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
use subtle::ConstantTimeEq;

fn bench_xhttp_path_classification(c: &mut Criterion) {
let uri: Uri = "/xhttp/session-123/184467440737095516".parse().unwrap();
c.bench_function("xhttp path meta classify", |b| {
b.iter(|| {
let meta = extract_meta_from_path("/xhttp/", &uri);
classify(&Method::POST, &meta)
let meta = extract_meta_from_path_borrowed("/xhttp/", &uri);
classify_borrowed(&Method::POST, &meta)
})
});
c.bench_function("xhttp allocating path reference", |b| {
b.iter(|| allocating_path_reference("/xhttp/", &uri))
});
}

fn bench_xhttp_host_and_path(c: &mut Criterion) {
let uri: Uri = "https://example.com/xhttp/session-123".parse().unwrap();
let mut headers = HeaderMap::new();
headers.insert(http::header::HOST, "example.com:443".parse().unwrap());
let meta = Meta {
session_id: "session-123".into(),
seq_str: String::new(),
let meta = BorrowedMeta {
session_id: "session-123",
seq_str: "",
};

c.bench_function("xhttp host path download classify", |b| {
b.iter(|| {
(
path_matches("/xhttp/", &uri),
host_matches("example.com", &headers, &uri),
classify(&Method::GET, &meta),
classify_borrowed(&Method::GET, &meta),
)
})
});
}

fn bench_xhttp_padding(c: &mut Criterion) {
let uri: Uri = "/xhttp/session-123/0".parse().unwrap();
let mut headers = HeaderMap::new();
headers.insert(
http::header::REFERER,
format!("https://example.com/?x_padding={}", "X".repeat(100))
.parse()
.unwrap(),
);
c.bench_function("xhttp request padding validate", |b| {
b.iter(|| is_padding_len_valid(extract_padding_len(&headers, &uri), 100, 1000))
});
c.bench_function("xhttp allocating padding reference", |b| {
b.iter(|| {
let padding = extract_padding(&headers, &uri);
is_padding_valid(&padding, 100, 1000)
})
});

let response_padding = ResponsePadding::new(100, 1000);
c.bench_function("xhttp cached response padding", |b| {
b.iter(|| response_padding.header_value())
});
c.bench_function("xhttp allocating response padding reference", |b| {
b.iter(|| {
let value = generate_response_padding(100, 1000);
value.parse::<http::HeaderValue>().unwrap()
})
});
}

fn bench_vless_user_lookup(c: &mut Criterion) {
let id = [7u8; 16];
let validator = Validator::new([User {
id,
email: "bench@example.com".into(),
flow: String::new(),
}]);
c.bench_function("vless lock-free user lookup", |b| {
b.iter(|| validator.get_shared(&id).unwrap())
});

let mut users = HashMap::new();
users.insert(process_uuid(id), validator.get(&id).unwrap());
let locked = RwLock::new(Arc::new(users));
c.bench_function("vless rwlock cloning lookup reference", |b| {
b.iter(|| {
let key = process_uuid(id);
let users = locked.read().unwrap().clone();
let candidate = users.get(&key).unwrap();
assert!(bool::from(candidate.id.ct_eq(&key)));
candidate.clone()
})
});
}

fn allocating_path_reference(base: &str, uri: &Uri) -> (String, u64) {
let rest = uri.path().strip_prefix(base).unwrap_or("");
let mut segments = rest.split('/');
let session_id = segments.next().unwrap_or("").to_string();
let seq_string = segments.next().unwrap_or("").to_string();
let seq = seq_string.parse().unwrap_or_default();
(session_id.clone(), seq)
}

criterion_group!(
benches,
bench_xhttp_path_classification,
bench_xhttp_host_and_path
bench_xhttp_host_and_path,
bench_xhttp_padding,
bench_vless_user_lookup
);
criterion_main!(benches);
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ VLESS-Encryption, verification, and troubleshooting.
| Configuration and deployment | [English](configuration.md) | [简体中文](configuration.zh-CN.md) |
| Benchmarks and raw evidence | [English](benchmarks.md) | [简体中文](benchmarks.zh-CN.md) |
| Performance and availability | [English](performance-and-availability.md) | [简体中文](performance-and-availability.zh-CN.md) |
| Hotspot optimization report | [English](performance-hotspots.md) | [简体中文](performance-hotspots.zh-CN.md) |
| Security policy | [English](../SECURITY.md) | [简体中文](../SECURITY.zh-CN.md) |

## Engineering notes
Expand Down
1 change: 1 addition & 0 deletions docs/index.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ ACME/手动证书、网站 fallback、VLESS-Encryption、验证和排错。
| 配置与部署 | [English](configuration.md) | [简体中文](configuration.zh-CN.md) |
| Benchmark 与原始证据 | [English](benchmarks.md) | [简体中文](benchmarks.zh-CN.md) |
| 性能与可用性 | [English](performance-and-availability.md) | [简体中文](performance-and-availability.zh-CN.md) |
| 热点优化报告 | [English](performance-hotspots.md) | [简体中文](performance-hotspots.zh-CN.md) |
| 安全政策 | [English](../SECURITY.md) | [简体中文](../SECURITY.zh-CN.md) |

## 工程说明
Expand Down
8 changes: 8 additions & 0 deletions docs/performance-and-availability.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
This note explains the current hot path, resource model, failure behavior, and
measurement limits. It complements the committed Xray comparison in
[Benchmarks](benchmarks.md).
The sampling method, allocation changes, and current microbenchmark evidence are
recorded in the [hotspot optimization report](performance-hotspots.md).

## Hot-path design

Expand All @@ -15,6 +17,12 @@ measurement limits. It complements the committed Xray comparison in
precomputed. Conditional GETs return 304 without reading a file.
- The session table is sharded, and counters are relaxed atomics. Target
concurrency uses a semaphore rather than unbounded task creation.
- Request path/session metadata is borrowed from parsed HTTP values. Single-frame
body uploads remain reference-counted `Bytes`, response padding is lazily cached,
and user-table reads use `ArcSwap` rather than a read lock.
- Download-created sessions skip orphan grace timers entirely; timers created for
upload-first sessions are cancelled as soon as the download opens or the session
ends. This prevents completed sessions from retaining timer tasks for the full TTL.
- Packet reorder queues and per-session/global byte budgets reserve capacity
before accepting payload memory. Oversized work fails early.
- TCP_NODELAY, keepalive, a 4096 listen backlog, and `SO_REUSEPORT` are enabled
Expand Down
5 changes: 5 additions & 0 deletions docs/performance-and-availability.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

本文说明当前热路径、资源模型、故障行为和测量边界;Xray 对比原始证据见
[Benchmark](benchmarks.zh-CN.md)。
采样方法、分配优化和当前微基准证据见[热点优化报告](performance-hotspots.zh-CN.md)。

## 热路径设计

Expand All @@ -13,6 +14,10 @@
Last-Modified 和路由别名预先计算;条件 GET 无需读盘即可返回 304;
- session table 分片,计数器使用 relaxed atomic;目标并发通过 semaphore 限制,
不会无限创建任务;
- request path/session 元数据直接借用已解析的 HTTP 值;单 frame body upload 保持为
引用计数 `Bytes`,响应 padding 延迟缓存,用户表读取通过 `ArcSwap` 避免读锁;
- download 先到达时完全不创建孤立 session grace timer;upload 先到达时创建的 timer
会在 download 打开或 session 结束时取消,已完成 session 不再滞留整个 TTL;
- packet 乱序队列和单 session/全局字节预算在接受 payload 内存前预留容量;
- 默认启用 TCP_NODELAY、keepalive、4096 listen backlog,以及受支持 Linux 上的
`SO_REUSEPORT`;
Expand Down
94 changes: 94 additions & 0 deletions docs/performance-hotspots.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Hotspot Optimization Report

English | [简体中文](performance-hotspots.zh-CN.md)

This report records the 2026-08-14 profile-guided optimization pass. It separates
repeatable function-level evidence from end-to-end measurements that are sensitive to
host load.

## Reproduction

Build a release binary with symbols, then attach `perf` only to the temporary
rust-xhttp child process:

```bash
CARGO_PROFILE_RELEASE_DEBUG=1 \
CARGO_PROFILE_RELEASE_STRIP=false \
cargo build --release --locked

DURATION=15 CONCURRENCY=64 PAYLOAD_BYTES=4096 scripts/profile.sh
```

The sustained driver verifies every VLESS/XHTTP echo response. `profile.sh` writes
ignored local artifacts under `docs/profile/`: workload JSON, `perf.data`, and a flat
top-symbol report. It uses `sudo -n perf record --pid <rust-xhttp-pid>` because this
host's `perf_event_paranoid` setting blocks unprivileged attachment; it never samples
the entire host.

Run the focused Criterion suite with:

```bash
cargo bench --bench geo -- --noplot
```

## Observed hotspots

The initial 15-second raw XHTTP sample attributed substantial aggregate cost to
allocation/free, Hyper/Tokio connection processing, session insertion/removal, response
padding construction, query parsing, and timer-wheel work. Network syscalls dominate
short HTTP/1.1 connections, so the application changes target repeated fixed costs rather
than claiming those syscalls can be removed.

The pass made these changes:

- XHTTP path metadata now borrows URI slices instead of allocating two strings and
cloning the session ID during classification.
- Padding validation counts decoded bytes without constructing the padding string.
- Response padding keeps Xray's random uniform length selection but lazily caches valid
`HeaderValue` instances for ordinary ranges.
- A single-frame Hyper upload body is forwarded as its existing `Bytes`; only fragmented
or mixed header/cookie/body placement needs concatenation.
- VLESS user snapshots use `ArcSwap`; the server hot-path lookup returns `Arc<User>` without
taking an `RwLock` or cloning email/flow strings, while the original public owned lookup
remains compatible.
- IPv4/IPv6 targets connect directly as `SocketAddr`, avoiding address formatting and a
redundant resolver path.
- New sessions acquire their shard once rather than using a redundant double-checked
lock. The precomputed session hash is reused during download teardown.
- The normal download-first request order creates no grace task. Upload-first grace tasks
are aborted immediately after the download opens or the session is removed.
- Origin and production Dispatcher tasks share one outer `Arc`; connection/session
creation no longer clones every Arc-backed field separately.

## Focused results

On this four-core host, Criterion reported the following same-process comparisons. The
reference functions reproduce the replaced allocating/locking operations, which avoids
cross-run frequency and background-load bias.

| Kernel | Optimized | Replaced reference | Change |
| --- | ---: | ---: | ---: |
| Path extraction + classification | 62.7 ns | 103.2 ns | -39% |
| Request padding extraction + validation | 149.3 ns | 383.7 ns | -61% |
| Random response padding HeaderValue | 23.0 ns | 118.2 ns | -81% |
| VLESS user lookup | 73.2 ns | 90.3 ns | -19% |

An idle-window alternating A/B run made before the final outer-Arc reduction showed a
4.6% lower mean server CPU/op and an 81% reduction in workload-window RSS growth. The
Python driver was already the throughput bottleneck, so median throughput moved only
0.4%; this is not presented as a capacity result. The final macro rerun was rejected
because an unrelated release build began consuming the shared host during measurement.

## Interpretation and next work

The microbenchmarks support the local fixed-cost changes; they do not replace the
official Xray-client comparison in [Benchmarks](benchmarks.md). A publishable capacity
result still requires an otherwise idle pinned host, at least five repetitions, and the
same TLS/encryption/client mode for both candidates.

The remaining flat sample is dominated by allocator, Hyper/Tokio polling, socket setup,
and kernel TCP work from deliberately short HTTP/1.1 sessions. The next useful pass
should profile a long-lived official Xray HTTP/2 client separately, then evaluate buffer
reuse only if allocation stacks remain material there. A pool should not be introduced
solely from this short-connection workload because pool contention can regress the real
H2 path.
Loading