mirror of
https://github.com/therealaleph/MasterHttpRelayVPN-RUST.git
synced 2026-05-18 07:44:47 +03:00
v1.6.4: fix Full-mode L7 muxer not batching ops (#231)
The batch-build loop blocked on a 30 ms timeout for the first message, then drained whatever else was in the channel via try_recv() and fired the batch. Under any non-bursty workload, the channel queue was always empty by the time the first op woke us up — so every "batch" had exactly one op, defeating the entire batching premise. Reporter (w0l4i) saw `batch: 1 ops → ..., rtt=6.3 s` repeating in logs even under high concurrency. Fix: after the first op lands, hold the buffer open for an 8 ms coalescing window. Concurrent ops (parallel fetches, HTTP/2 stream openings, etc.) now accumulate into the same batch. 8 ms is rounding error against the 2–7 s Apps Script RTT we're amortizing, and restores the multi-op-per-batch behavior the rest of the code already supports (MAX_BATCH_OPS=50, MAX_BATCH_PAYLOAD_BYTES=4 MiB). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Generated
+1
-1
@@ -2186,7 +2186,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mhrv-rs"
|
name = "mhrv-rs"
|
||||||
version = "1.6.3"
|
version = "1.6.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "mhrv-rs"
|
name = "mhrv-rs"
|
||||||
version = "1.6.3"
|
version = "1.6.4"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "Rust port of MasterHttpRelayVPN -- DPI bypass via Google Apps Script relay with domain fronting"
|
description = "Rust port of MasterHttpRelayVPN -- DPI bypass via Google Apps Script relay with domain fronting"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ android {
|
|||||||
applicationId = "com.therealaleph.mhrv"
|
applicationId = "com.therealaleph.mhrv"
|
||||||
minSdk = 24 // Android 7.0 — covers 99%+ of live devices.
|
minSdk = 24 // Android 7.0 — covers 99%+ of live devices.
|
||||||
targetSdk = 34
|
targetSdk = 34
|
||||||
versionCode = 142
|
versionCode = 143
|
||||||
versionName = "1.6.3"
|
versionName = "1.6.4"
|
||||||
|
|
||||||
// Ship all four mainstream Android ABIs:
|
// Ship all four mainstream Android ABIs:
|
||||||
// - arm64-v8a — 95%+ of real-world Android phones since 2019
|
// - arm64-v8a — 95%+ of real-world Android phones since 2019
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<!-- see docs/changelog/v1.1.0.md for the file format: Persian, then `---`, then English. -->
|
||||||
|
• رفع باگ "L7 multiplexer در Full mode batch نمیکنه" ([#231](https://github.com/therealaleph/MasterHttpRelayVPN-RUST/issues/231)): در حالت Full، انتظار میرفت که چند op به یک batch HTTP request به Apps Script ترکیب بشن (`batch: 5 ops` یا `batch: 10 ops`)، ولی log نشون میداد همیشه `batch: 1 ops` — یعنی هر op جدا یه round-trip Apps Script میگرفت (که هر کدوم 2 تا 7 ثانیه طول میکشن). علت: loop دریافت پیام بلافاصله بعد از اولین message با `try_recv()` (non-blocking) صف رو drain میکرد، بدون pause برای جمعآوری بقیه ops. **Fix:** بعد از اولین op، یه پنجرهٔ ۸ میلیثانیهای باز میمونه تا opهای بعدی (مثل parallel fetches، HTTP/2 streams) همون batch رو پر کنن. ۸ms در مقابل ~۲ تا ۷ ثانیه RTT Apps Script اصلاً ناچیزه ولی efficiency batching رو برمیگردونه. ریپورت شده توسط w0l4i با log واضح
|
||||||
|
---
|
||||||
|
• Fix "L7 multiplexer not batching in Full mode" bug ([#231](https://github.com/therealaleph/MasterHttpRelayVPN-RUST/issues/231)): in `full` mode, multiple ops should coalesce into a single batched HTTP request to Apps Script (`batch: 5 ops` or `batch: 10 ops`), but logs showed `batch: 1 ops` consistently — each op got its own Apps Script round-trip (2-7 s each). Cause: the receive loop drained the channel via `try_recv()` (non-blocking) immediately after the first message arrived, with no window to let concurrent ops accumulate. **Fix:** after the first op lands, hold the buffer open for an 8 ms coalescing window so concurrent ops (parallel fetches, HTTP/2 stream openings, etc.) land in the same batch. 8 ms is rounding error against the ~2-7 s Apps Script RTT but restores the entire batching premise. Reported by w0l4i with a clean log snippet
|
||||||
+32
-6
@@ -55,6 +55,16 @@ const REPLY_TIMEOUT: Duration = Duration::from_secs(35);
|
|||||||
/// connect saves one Apps Script round-trip per new flow.
|
/// connect saves one Apps Script round-trip per new flow.
|
||||||
const CLIENT_FIRST_DATA_WAIT: Duration = Duration::from_millis(50);
|
const CLIENT_FIRST_DATA_WAIT: Duration = Duration::from_millis(50);
|
||||||
|
|
||||||
|
/// How long the muxer holds open the batch buffer after the first op
|
||||||
|
/// arrives, waiting for more ops to coalesce. Issue #231 — the previous
|
||||||
|
/// implementation drained `try_recv()` *immediately* after the first
|
||||||
|
/// message landed, so under any non-bursty workload every batch held
|
||||||
|
/// exactly one op (defeating the entire batching premise). 8 ms is small
|
||||||
|
/// vs the ~2-7 s Apps Script round-trip the batch is amortizing, but
|
||||||
|
/// long enough that concurrent HTTP/2 stream openings, parallel fetches,
|
||||||
|
/// or any other burst lands in the same batch.
|
||||||
|
const BATCH_COALESCE_WINDOW: Duration = Duration::from_millis(8);
|
||||||
|
|
||||||
/// Structured error code the tunnel-node returns when it doesn't know the
|
/// Structured error code the tunnel-node returns when it doesn't know the
|
||||||
/// op (version mismatch). Must match `tunnel-node/src/main.rs`.
|
/// op (version mismatch). Must match `tunnel-node/src/main.rs`.
|
||||||
const CODE_UNSUPPORTED_OP: &str = "UNSUPPORTED_OP";
|
const CODE_UNSUPPORTED_OP: &str = "UNSUPPORTED_OP";
|
||||||
@@ -319,13 +329,29 @@ async fn mux_loop(mut rx: mpsc::Receiver<MuxMsg>, fronter: Arc<DomainFronter>) {
|
|||||||
|
|
||||||
loop {
|
loop {
|
||||||
let mut msgs = Vec::new();
|
let mut msgs = Vec::new();
|
||||||
match tokio::time::timeout(Duration::from_millis(30), rx.recv()).await {
|
// Block on the first message — no point waking up to find an empty
|
||||||
Ok(Some(msg)) => msgs.push(msg),
|
// queue. Once the first op lands, we hold open BATCH_COALESCE_WINDOW
|
||||||
Ok(None) => break,
|
// so concurrent ops (parallel fetches, HTTP/2 stream openings, etc.)
|
||||||
Err(_) => continue,
|
// land in the same batch instead of getting a fresh round-trip each.
|
||||||
|
match rx.recv().await {
|
||||||
|
Some(msg) => msgs.push(msg),
|
||||||
|
None => break,
|
||||||
}
|
}
|
||||||
while let Ok(msg) = rx.try_recv() {
|
let deadline = tokio::time::Instant::now() + BATCH_COALESCE_WINDOW;
|
||||||
msgs.push(msg);
|
loop {
|
||||||
|
// Drain anything that's already queued without waiting.
|
||||||
|
while let Ok(msg) = rx.try_recv() {
|
||||||
|
msgs.push(msg);
|
||||||
|
}
|
||||||
|
let now = tokio::time::Instant::now();
|
||||||
|
if now >= deadline {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
match tokio::time::timeout(deadline - now, rx.recv()).await {
|
||||||
|
Ok(Some(msg)) => msgs.push(msg),
|
||||||
|
Ok(None) => return,
|
||||||
|
Err(_) => break,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Split: plain connects go parallel, data-bearing ops get batched.
|
// Split: plain connects go parallel, data-bearing ops get batched.
|
||||||
|
|||||||
Reference in New Issue
Block a user